]> git.xonotic.org Git - xonotic/gmqcc.git/blob - pak.c
Fixes to the PAK utility.
[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 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 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 bool pak_exists(pak_file_t *pak, const char *file, pak_directory_t **dir) {
231     size_t itr;
232
233     if (!pak || !file)
234         return false;
235
236     /*
237      * We could technically use a hashtable here.  But I don't think
238      * the lookup complexity is a performance concern.  This may be
239      * O(n) lookup.  But meh?
240      */    
241     for (itr = 0; itr < vec_size(pak->directories); itr++) {
242         if (!strcmp(pak->directories[itr].name, file)) {
243             /*
244              * Store back a pointer to the directory that matches
245              * the request if requested (NULL is not allowed).
246              */   
247             if (dir) {
248                 *dir = &(pak->directories[itr]);
249             }
250             return true;
251         }
252     }
253
254     return false;
255 }
256
257 /*
258  * Extraction abilities.  These work as you expect them to.
259  */ 
260 bool pak_extract_one(pak_file_t *pak, const char *file) {
261     pak_directory_t *dir = NULL;
262     unsigned char   *dat = NULL;
263     FILE            *out;
264
265     if (!pak_exists(pak, file, &dir)) {
266         return false;
267     }
268
269     if (!(dat = (unsigned char *)mem_a(dir->len))) {
270         return false;
271     }
272
273     /*
274      * Generate the directory structure / tree that will be required
275      * to store the extracted file.
276      */   
277     pak_tree_build(file);
278
279     /*
280      * Now create the file, if this operation fails.  Then abort
281      * It shouldn't fail though.
282      */   
283     if (!(out = file_open(file, "wb"))) {
284         mem_d(dat);
285         return false;
286     }
287
288
289     /* read */
290     file_seek (pak->handle, dir->pos, SEEK_SET);
291     file_read (dat, 1, dir->len, pak->handle);
292
293     /* write */
294     file_write(dat, 1, dir->len, out);
295
296     /* close */
297     file_close(out);
298
299     /* free */
300     mem_d(dat);
301
302     return true;
303 }
304
305 bool pak_extract_all(pak_file_t *pak) {
306     size_t itr;
307     for (itr = 0; itr < vec_size(pak->directories); itr++) {
308         if (!pak_extract_one(pak, pak->directories[itr].name))
309             return false;
310     }
311
312     return true;
313 }
314
315 /*
316  * Insertion functions (the opposite of extraction).  Yes for generating
317  * PAKs.
318  */
319 bool pak_insert_one(pak_file_t *pak, const char *file) {
320     pak_directory_t dir;
321     unsigned char  *dat;
322     FILE           *fp;
323
324     /*
325      * We don't allow insertion on files that already exist within the
326      * pak file.  Weird shit can happen if we allow that ;). We also
327      * don't allow insertion if the pak isn't opened in write mode.  
328      */ 
329     if (!pak || !file || !pak->insert || pak_exists(pak, file, NULL))
330         return false;
331
332     if (!(fp = fopen(file, "rb")))
333         return false;
334
335     /*
336      * Calculate the total file length, since it will be wrote to
337      * the directory entry, and the actual contents of the file
338      * to the PAK file itself.
339      */
340     file_seek(fp, 0, SEEK_END);
341     dir.len = ftell(fp);
342     file_seek(fp, 0, SEEK_SET);
343
344     dir.pos = ftell(pak->handle);
345
346     /*
347      * Allocate some memory for loading in the data that will be
348      * redirected into the PAK file.
349      */   
350     if (!(dat = (unsigned char *)mem_a(dir.len))) {
351         file_close(fp);
352         return false;
353     }
354
355     file_read (dat, dir.len, 1, fp);
356     file_close(fp);
357     file_write(dat, dir.len, 1, pak->handle);
358
359     return true;
360 }
361
362 /*
363  * Like pak_insert_one, except this collects files in all directories
364  * from a root directory, and inserts them all.
365  */  
366 bool pak_insert_all(pak_file_t *pak, const char *dir) {
367     DIR           *dp;
368     struct dirent *dirp;
369
370     if (!(pak->insert))
371         return false;
372
373     if (!(dp = opendir(dir)))
374         return false;
375
376     while ((dirp = readdir(dp))) {
377         if (!(pak_insert_one(pak, dirp->d_name))) {
378             closedir(dp);
379             return false;
380         }
381     }
382
383     closedir(dp);
384     return true;
385 }
386
387 bool pak_close(pak_file_t *pak) {
388     size_t itr;
389
390     if (!pak)
391         return false;
392
393     /*
394      * In insert mode we need to patch the header, and write
395      * our directory entries at the end of the file.
396      */  
397     if (pak->insert) {
398         pak->header.dirlen = vec_size(pak->directories) * 64;
399         pak->header.diroff = ftell(pak->handle);
400
401         /* patch header */ 
402         file_seek (pak->handle, 0, SEEK_SET);
403         file_write(&(pak->header), sizeof(pak_header_t), 1, pak->handle);
404
405         /* write directories */
406         file_seek (pak->handle, pak->header.diroff, SEEK_SET);
407
408         for (itr = 0; itr < vec_size(pak->directories); itr++) {
409             file_write(&(pak->directories[itr]), sizeof(pak_directory_t), 1, pak->handle);
410         }
411     }
412
413     vec_free  (pak->directories);
414     file_close(pak->handle);
415     mem_d     (pak);
416
417     return true;
418 }
419
420 /* test extraction */
421 #if 0
422 int main() {
423     pak_file_t *pak = pak_open_read("pak0.pak");
424     if (!pak) abort();
425
426     pak_extract_all(pak);
427
428     return 0;
429 }
430 #endif