]> git.xonotic.org Git - xonotic/gmqcc.git/blob - pak.c
fix build issue
[xonotic/gmqcc.git] / pak.c
1 /*
2  * Copyright (C) 2013
3  *     Dale Weiler 
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy of
6  * this software and associated documentation files (the "Software"), to deal in
7  * the Software without restriction, including without limitation the rights to
8  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is furnished to do
10  * so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in all
13  * copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 #include <sys/stat.h>
24 #include <dirent.h>
25 #include "gmqcc.h"  
26
27 typedef struct {
28     uint32_t magic;  /* "PACK" */
29
30     /*
31      * Offset to first directory entry in PAK file.  It's often
32      * best to store the directories at the end of the file opposed
33      * to the front, since it allows easy insertion without having
34      * to load the entire file into memory again.
35      */     
36     uint32_t diroff;
37     uint32_t dirlen;
38 } pak_header_t;
39
40 /*
41  * A directory, is sort of a "file entry".  The concept of
42  * a directory in Quake world is a "file entry/record". This
43  * describes a file (with directories/nested ones too in it's
44  * file name).  Hence it can be a file, file with directory, or
45  * file with directories.
46  */ 
47 typedef struct {
48     char     name[56];
49     uint32_t pos;
50     uint32_t len;
51 } pak_directory_t;
52
53 /*
54  * Used to get the next token from a string, where the
55  * strings themselfs are seperated by chracters from
56  * `sep`.  This is essentially strsep.
57  */   
58 static char *pak_tree_sep(char **str, const char *sep) {
59     char *beg = *str;
60     char *end;
61
62     if (!beg)
63         return NULL;
64
65     if (*(end = beg + strcspn(beg, sep)))
66         * end++ = '\0'; /* null terminate */
67     else
68           end   = 0;
69
70     *str = end;
71     return beg;
72 }
73
74 /*
75  * Used to spawn a directory when creating the pak directory structure/
76  * tree.  Think of this as mkdir(path, 0700).  We just cargo cult our
77  * own because _mkdir on windows is "illegal" for Windows8 Certification
78  * do to the requirement of SECURITY_ATTRIBUTES on everything.
79  */    
80 static bool pak_tree_spawn(const char *path) {
81 #ifdef _MSC_VER
82     return CreateDirectoryA(path, NULL); /* non-zero on success */
83 #else
84     return !!(mkdir(path, 0700));        /* zero on success     */
85 #endif
86 }
87
88 /*
89  * When given a string like "a/b/c/d/e/file"
90  * this function will handle the creation of
91  * the directory structure, included nested
92  * directories.
93  */
94 static void pak_tree_build(const char *entry) {
95     char *directory;
96     char *elements[28];
97     char *pathsplit;
98     char *token;
99
100     size_t itr;
101     size_t jtr;
102
103     pathsplit = mem_a(56);
104     directory = mem_a(56);
105
106     memset(pathsplit, 0, 56);
107     memset(directory, 0, 56);
108
109     strncpy(directory, entry, 56);
110     for (itr = 0; (token = strsep(&directory, "/")) != NULL; itr++) {
111         elements[itr] = token;
112     }
113
114     for (jtr = 0; jtr < itr - 1; jtr++) {
115         strcat(pathsplit, elements[jtr]);
116         strcat(pathsplit, "/");
117
118         pak_tree_spawn(pathsplit);
119     }
120
121     mem_d(pathsplit);
122     mem_d(directory);
123 }
124
125 typedef struct {
126     pak_directory_t *directories;
127     pak_header_t     header;
128     FILE            *handle;
129     bool             insert;
130 } pak_file_t;
131
132 static pak_file_t *pak_open_read(const char *file) {
133     pak_file_t *pak;
134     size_t      itr;
135
136     if (!(pak = mem_a(sizeof(pak_file_t))))
137         return NULL;
138
139     if (!(pak->handle = file_open(file, "rb"))) {
140         mem_d(pak);
141         return NULL;
142     }
143
144     pak->directories = NULL;
145     pak->insert      = false; /* read doesn't allow insert */
146
147     memset         (&pak->header, 0, sizeof(pak_header_t));
148     file_read      (&pak->header,    sizeof(pak_header_t), 1, pak->handle);
149     util_endianswap(&pak->header, 1, sizeof(pak_header_t));
150
151     /*
152      * Every PAK file has "PACK" stored as little endian data in the
153      * header.  If this data cannot compare (as checked here), it's
154      * probably not a PAK file.
155      */
156     if ((memcmp(&(pak->header.magic), (const void*)"PACK", sizeof(uint32_t)))) {
157         file_close(pak->handle);
158         mem_d     (pak);
159         return NULL;
160     }
161
162     /*
163      * Time to read in the directory handles and prepare the directories
164      * vector.  We're going to be reading some the file inwards soon.
165      */      
166     file_seek(pak->handle, pak->header.diroff, SEEK_SET);
167
168     /*
169      * Read in all directories from the PAK file. These are considered
170      * to be the "file entries".
171      */   
172     for (itr = 0; itr < pak->header.dirlen / 64; itr++) {
173         pak_directory_t dir;
174         file_read      (&dir, sizeof(pak_directory_t), 1, pak->handle);
175         /*util_endianswap(&dir, 1, sizeof(pak_directory_t));*/
176
177         vec_push(pak->directories, dir);
178     }
179     return pak;
180 }
181
182 static pak_file_t *pak_open_write(const char *file) {
183     pak_file_t *pak;
184
185     if (!(pak = mem_a(sizeof(pak_file_t))))
186         return NULL;
187
188     /*
189      * Generate the required directory structure / tree for
190      * writing this PAK file too.
191      */   
192     pak_tree_build(file);
193
194     if (!(pak->handle = file_open(file, "wb"))) {
195         /*
196          * The directory tree that was created, needs to be
197          * removed entierly if we failed to open a file.
198          */   
199         /* TODO backup directory clean */
200
201         return NULL;
202     }
203
204     memset(&(pak->header), 0, sizeof(pak_header_t));
205
206     /*
207      * We're in "insert" mode, we need to do things like header
208      * "patching" and writing the directories at the end of the
209      * file.
210      */
211     pak->insert = true;
212
213     /*
214      * A valid PAK file contains the magic "PACK" in it's header
215      * stored in little endian format.
216      */
217     memcpy(&(pak->header.magic), (const void*)"PACK", sizeof(uint32_t));
218
219     /*
220      * We need to write out the header since files will be wrote out to
221      * this even with directory entries, and that not wrote.  The header
222      * will need to be patched in later with a file_seek, and overwrite,
223      * we could use offsets and other trickery.  This is just easier.
224      */
225     file_write(&(pak->header), sizeof(pak_header_t), 1, pak->handle);
226
227     return pak;
228 }
229
230 pak_file_t *pak_open(const char *file, const char *mode) {
231     if (!file || !mode)
232         return NULL;
233
234     switch (*mode) {
235         case 'r': return pak_open_read (file);
236         case 'w': return pak_open_write(file);
237     }
238
239     return NULL;
240 }
241
242 bool pak_exists(pak_file_t *pak, const char *file, pak_directory_t **dir) {
243     size_t itr;
244
245     if (!pak || !file)
246         return false;
247
248     /*
249      * We could technically use a hashtable here.  But I don't think
250      * the lookup complexity is a performance concern.  This may be
251      * O(n) lookup.  But meh?
252      */    
253     for (itr = 0; itr < vec_size(pak->directories); itr++) {
254         if (!strcmp(pak->directories[itr].name, file)) {
255             /*
256              * Store back a pointer to the directory that matches
257              * the request if requested (NULL is not allowed).
258              */   
259             if (dir) {
260                 *dir = &(pak->directories[itr]);
261             }
262             return true;
263         }
264     }
265
266     return false;
267 }
268
269 /*
270  * Extraction abilities.  These work as you expect them to.
271  */ 
272 bool pak_extract_one(pak_file_t *pak, const char *file) {
273     pak_directory_t *dir = NULL;
274     unsigned char   *dat = NULL;
275     FILE            *out;
276
277     if (!pak_exists(pak, file, &dir)) {
278         return false;
279     }
280
281     if (!(dat = (unsigned char *)mem_a(dir->len))) {
282         return false;
283     }
284
285     /*
286      * Generate the directory structure / tree that will be required
287      * to store the extracted file.
288      */   
289     pak_tree_build(file);
290
291     /*
292      * Now create the file, if this operation fails.  Then abort
293      * It shouldn't fail though.
294      */   
295     if (!(out = file_open(file, "wb"))) {
296         mem_d(dat);
297         return false;
298     }
299
300
301     /* read */
302     file_seek (pak->handle, dir->pos, SEEK_SET);
303     file_read (dat, 1, dir->len, pak->handle);
304
305     /* write */
306     file_write(dat, 1, dir->len, out);
307
308     /* close */
309     file_close(out);
310
311     /* free */
312     mem_d(dat);
313
314     return true;
315 }
316
317 bool pak_extract_all(pak_file_t *pak, const char *dir) {
318     size_t itr;
319
320     if (!pak_tree_spawn(dir))
321         return false;
322
323     if (chdir(dir))
324         return false;
325
326     for (itr = 0; itr < vec_size(pak->directories); itr++) {
327         if (!pak_extract_one(pak, pak->directories[itr].name))
328             return false;
329     }
330
331     return true;
332 }
333
334 /*
335  * Insertion functions (the opposite of extraction).  Yes for generating
336  * PAKs.
337  */
338 bool pak_insert_one(pak_file_t *pak, const char *file) {
339     pak_directory_t dir;
340     unsigned char  *dat;
341     FILE           *fp;
342
343     /*
344      * We don't allow insertion on files that already exist within the
345      * pak file.  Weird shit can happen if we allow that ;). We also
346      * don't allow insertion if the pak isn't opened in write mode.  
347      */ 
348     if (!pak || !file || !pak->insert || pak_exists(pak, file, NULL))
349         return false;
350
351     if (!(fp = fopen(file, "rb")))
352         return false;
353
354     /*
355      * Calculate the total file length, since it will be wrote to
356      * the directory entry, and the actual contents of the file
357      * to the PAK file itself.
358      */
359     file_seek(fp, 0, SEEK_END);
360     dir.len = ftell(fp);
361     file_seek(fp, 0, SEEK_SET);
362
363     dir.pos = ftell(pak->handle);
364
365     /*
366      * We're limited to 56 bytes for a file name string, that INCLUDES
367      * the directory and '/' seperators.
368      */   
369     if (strlen(file) >= 56) {
370         file_close(fp);
371         return false;
372     }
373
374     strcpy(dir.name, file);
375
376     /*
377      * Allocate some memory for loading in the data that will be
378      * redirected into the PAK file.
379      */   
380     if (!(dat = (unsigned char *)mem_a(dir.len))) {
381         file_close(fp);
382         return false;
383     }
384
385     file_read (dat, dir.len, 1, fp);
386     file_close(fp);
387     file_write(dat, dir.len, 1, pak->handle);
388
389     /*
390      * Now add the directory to the directories vector, so pak_close
391      * can actually write it.
392      */
393     vec_push(pak->directories, dir);
394
395     return true;
396 }
397
398 /*
399  * Like pak_insert_one, except this collects files in all directories
400  * from a root directory, and inserts them all.
401  */  
402 bool pak_insert_all(pak_file_t *pak, const char *dir) {
403     DIR           *dp;
404     struct dirent *dirp;
405
406     if (!(pak->insert))
407         return false;
408
409     if (!(dp = opendir(dir)))
410         return false;
411
412     while ((dirp = readdir(dp))) {
413         if (!(pak_insert_one(pak, dirp->d_name))) {
414             closedir(dp);
415             return false;
416         }
417     }
418
419     closedir(dp);
420     return true;
421 }
422
423 bool pak_close(pak_file_t *pak) {
424     size_t itr;
425
426     if (!pak)
427         return false;
428
429     /*
430      * In insert mode we need to patch the header, and write
431      * our directory entries at the end of the file.
432      */  
433     if (pak->insert) {
434         pak->header.dirlen = vec_size(pak->directories) * 64;
435         pak->header.diroff = ftell(pak->handle);
436
437         /* patch header */ 
438         file_seek (pak->handle, 0, SEEK_SET);
439         file_write(&(pak->header), sizeof(pak_header_t), 1, pak->handle);
440
441         /* write directories */
442         file_seek (pak->handle, pak->header.diroff, SEEK_SET);
443
444         for (itr = 0; itr < vec_size(pak->directories); itr++) {
445             file_write(&(pak->directories[itr]), sizeof(pak_directory_t), 1, pak->handle);
446         }
447     }
448
449     vec_free  (pak->directories);
450     file_close(pak->handle);
451     mem_d     (pak);
452
453     return true;
454 }
455
456 #if 0
457 /* test extraction */
458 int main() {
459     pak_file_t *pak = pak_open("pak0.pak", "r");
460     if (!pak) abort();
461
462     pak_extract_all(pak, "foo/");
463
464     pak_close(pak);
465     return 0;
466 }
467 #endif
468