Upload 2.0.2
[physicsfs] / archivers / wad.c
1 /*
2  * WAD support routines for PhysicsFS.
3  *
4  * This driver handles DOOM engine archives ("wads"). 
5  * This format (but not this driver) was designed by id Software for use
6  *  with the DOOM engine.
7  * The specs of the format are from the unofficial doom specs v1.666
8  * found here: http://www.gamers.org/dhs/helpdocs/dmsp1666.html
9  * The format of the archive: (from the specs)
10  *
11  *  A WAD file has three parts:
12  *  (1) a twelve-byte header
13  *  (2) one or more "lumps"
14  *  (3) a directory or "info table" that contains the names, offsets, and
15  *      sizes of all the lumps in the WAD
16  *
17  *  The header consists of three four-byte parts:
18  *    (a) an ASCII string which must be either "IWAD" or "PWAD"
19  *    (b) a 4-byte (long) integer which is the number of lumps in the wad
20  *    (c) a long integer which is the file offset to the start of
21  *    the directory
22  *
23  *  The directory has one 16-byte entry for every lump. Each entry consists
24  *  of three parts:
25  *
26  *    (a) a long integer, the file offset to the start of the lump
27  *    (b) a long integer, the size of the lump in bytes
28  *    (c) an 8-byte ASCII string, the name of the lump, padded with zeros.
29  *        For example, the "DEMO1" entry in hexadecimal would be
30  *        (44 45 4D 4F 31 00 00 00)
31  * 
32  * Note that there is no way to tell if an opened WAD archive is a
33  *  IWAD or PWAD with this archiver.
34  * I couldn't think of a way to provide that information, without being too
35  *  hacky.
36  * I don't think it's really that important though.
37  *
38  *
39  * Please see the file LICENSE.txt in the source's root directory.
40  *
41  * This file written by Travis Wells, based on the GRP archiver by
42  *  Ryan C. Gordon.
43  */
44
45 #if (defined PHYSFS_SUPPORTS_WAD)
46
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include "physfs.h"
51
52 #define __PHYSICSFS_INTERNAL__
53 #include "physfs_internal.h"
54
55 typedef struct
56 {
57     char name[18];
58     PHYSFS_uint32 startPos;
59     PHYSFS_uint32 size;
60 } WADentry;
61
62 typedef struct
63 {
64     char *filename;
65     PHYSFS_sint64 last_mod_time;
66     PHYSFS_uint32 entryCount;
67     PHYSFS_uint32 entryOffset;
68     WADentry *entries;
69 } WADinfo;
70
71 typedef struct
72 {
73     void *handle;
74     WADentry *entry;
75     PHYSFS_uint32 curPos;
76 } WADfileinfo;
77
78
79 static void WAD_dirClose(dvoid *opaque)
80 {
81     WADinfo *info = ((WADinfo *) opaque);
82     allocator.Free(info->filename);
83     allocator.Free(info->entries);
84     allocator.Free(info);
85 } /* WAD_dirClose */
86
87
88 static PHYSFS_sint64 WAD_read(fvoid *opaque, void *buffer,
89                               PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
90 {
91     WADfileinfo *finfo = (WADfileinfo *) opaque;
92     WADentry *entry = finfo->entry;
93     PHYSFS_uint32 bytesLeft = entry->size - finfo->curPos;
94     PHYSFS_uint32 objsLeft = (bytesLeft / objSize);
95     PHYSFS_sint64 rc;
96
97     if (objsLeft < objCount)
98         objCount = objsLeft;
99
100     rc = __PHYSFS_platformRead(finfo->handle, buffer, objSize, objCount);
101     if (rc > 0)
102         finfo->curPos += (PHYSFS_uint32) (rc * objSize);
103
104     return(rc);
105 } /* WAD_read */
106
107
108 static PHYSFS_sint64 WAD_write(fvoid *opaque, const void *buffer,
109                                PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
110 {
111     BAIL_MACRO(ERR_NOT_SUPPORTED, -1);
112 } /* WAD_write */
113
114
115 static int WAD_eof(fvoid *opaque)
116 {
117     WADfileinfo *finfo = (WADfileinfo *) opaque;
118     WADentry *entry = finfo->entry;
119     return(finfo->curPos >= entry->size);
120 } /* WAD_eof */
121
122
123 static PHYSFS_sint64 WAD_tell(fvoid *opaque)
124 {
125     return(((WADfileinfo *) opaque)->curPos);
126 } /* WAD_tell */
127
128
129 static int WAD_seek(fvoid *opaque, PHYSFS_uint64 offset)
130 {
131     WADfileinfo *finfo = (WADfileinfo *) opaque;
132     WADentry *entry = finfo->entry;
133     int rc;
134
135     BAIL_IF_MACRO(offset < 0, ERR_INVALID_ARGUMENT, 0);
136     BAIL_IF_MACRO(offset >= entry->size, ERR_PAST_EOF, 0);
137     rc = __PHYSFS_platformSeek(finfo->handle, entry->startPos + offset);
138     if (rc)
139         finfo->curPos = (PHYSFS_uint32) offset;
140
141     return(rc);
142 } /* WAD_seek */
143
144
145 static PHYSFS_sint64 WAD_fileLength(fvoid *opaque)
146 {
147     WADfileinfo *finfo = (WADfileinfo *) opaque;
148     return((PHYSFS_sint64) finfo->entry->size);
149 } /* WAD_fileLength */
150
151
152 static int WAD_fileClose(fvoid *opaque)
153 {
154     WADfileinfo *finfo = (WADfileinfo *) opaque;
155     BAIL_IF_MACRO(!__PHYSFS_platformClose(finfo->handle), NULL, 0);
156     allocator.Free(finfo);
157     return(1);
158 } /* WAD_fileClose */
159
160
161 static int wad_open(const char *filename, int forWriting,
162                     void **fh, PHYSFS_uint32 *count,PHYSFS_uint32 *offset)
163 {
164     PHYSFS_uint8 buf[4];
165
166     *fh = NULL;
167     BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, 0);
168
169     *fh = __PHYSFS_platformOpenRead(filename);
170     BAIL_IF_MACRO(*fh == NULL, NULL, 0);
171     
172     if (__PHYSFS_platformRead(*fh, buf, 4, 1) != 1)
173         goto openWad_failed;
174
175     if (memcmp(buf, "IWAD", 4) != 0 && memcmp(buf, "PWAD", 4) != 0)
176     {
177         __PHYSFS_setError(ERR_UNSUPPORTED_ARCHIVE);
178         goto openWad_failed;
179     } /* if */
180
181     if (__PHYSFS_platformRead(*fh, count, sizeof (PHYSFS_uint32), 1) != 1)
182         goto openWad_failed;
183
184     *count = PHYSFS_swapULE32(*count);
185
186     if (__PHYSFS_platformRead(*fh, offset, sizeof (PHYSFS_uint32), 1) != 1)
187         goto openWad_failed;
188
189     *offset = PHYSFS_swapULE32(*offset);
190
191     return(1);
192
193 openWad_failed:
194     if (*fh != NULL)
195         __PHYSFS_platformClose(*fh);
196
197     *count = -1;
198     *fh = NULL;
199     return(0);
200 } /* wad_open */
201
202
203 static int WAD_isArchive(const char *filename, int forWriting)
204 {
205     void *fh;
206     PHYSFS_uint32 fileCount,offset;
207     int retval = wad_open(filename, forWriting, &fh, &fileCount,&offset);
208
209     if (fh != NULL)
210         __PHYSFS_platformClose(fh);
211
212     return(retval);
213 } /* WAD_isArchive */
214
215
216 static int wad_entry_cmp(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
217 {
218     if (one != two)
219     {
220         const WADentry *a = (const WADentry *) _a;
221         return(strcmp(a[one].name, a[two].name));
222     } /* if */
223
224     return 0;
225 } /* wad_entry_cmp */
226
227
228 static void wad_entry_swap(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
229 {
230     if (one != two)
231     {
232         WADentry tmp;
233         WADentry *first = &(((WADentry *) _a)[one]);
234         WADentry *second = &(((WADentry *) _a)[two]);
235         memcpy(&tmp, first, sizeof (WADentry));
236         memcpy(first, second, sizeof (WADentry));
237         memcpy(second, &tmp, sizeof (WADentry));
238     } /* if */
239 } /* wad_entry_swap */
240
241
242 static int wad_load_entries(const char *name, int forWriting, WADinfo *info)
243 {
244     void *fh = NULL;
245     PHYSFS_uint32 fileCount;
246     PHYSFS_uint32 directoryOffset;
247     WADentry *entry;
248     char lastDirectory[9];
249
250     lastDirectory[8] = 0; /* Make sure lastDirectory stays null-terminated. */
251
252     BAIL_IF_MACRO(!wad_open(name, forWriting, &fh, &fileCount,&directoryOffset), NULL, 0);
253     info->entryCount = fileCount;
254     info->entries = (WADentry *) allocator.Malloc(sizeof(WADentry)*fileCount);
255     if (info->entries == NULL)
256     {
257         __PHYSFS_platformClose(fh);
258         BAIL_MACRO(ERR_OUT_OF_MEMORY, 0);
259     } /* if */
260
261     __PHYSFS_platformSeek(fh,directoryOffset);
262
263     for (entry = info->entries; fileCount > 0; fileCount--, entry++)
264     {
265         if (__PHYSFS_platformRead(fh, &entry->startPos, 4, 1) != 1)
266         {
267             __PHYSFS_platformClose(fh);
268             return(0);
269         } /* if */
270         
271         if (__PHYSFS_platformRead(fh, &entry->size, 4, 1) != 1)
272         {
273             __PHYSFS_platformClose(fh);
274             return(0);
275         } /* if */
276
277         if (__PHYSFS_platformRead(fh, &entry->name, 8, 1) != 1)
278         {
279             __PHYSFS_platformClose(fh);
280             return(0);
281         } /* if */
282
283         entry->name[8] = '\0'; /* name might not be null-terminated in file. */
284         entry->size = PHYSFS_swapULE32(entry->size);
285         entry->startPos = PHYSFS_swapULE32(entry->startPos);
286     } /* for */
287
288     __PHYSFS_platformClose(fh);
289
290     __PHYSFS_sort(info->entries, info->entryCount,
291                   wad_entry_cmp, wad_entry_swap);
292     return(1);
293 } /* wad_load_entries */
294
295
296 static void *WAD_openArchive(const char *name, int forWriting)
297 {
298     PHYSFS_sint64 modtime = __PHYSFS_platformGetLastModTime(name);
299     WADinfo *info = (WADinfo *) allocator.Malloc(sizeof (WADinfo));
300
301     BAIL_IF_MACRO(info == NULL, ERR_OUT_OF_MEMORY, NULL);
302     memset(info, '\0', sizeof (WADinfo));
303
304     info->filename = (char *) allocator.Malloc(strlen(name) + 1);
305     GOTO_IF_MACRO(!info->filename, ERR_OUT_OF_MEMORY, WAD_openArchive_failed);
306
307     if (!wad_load_entries(name, forWriting, info))
308         goto WAD_openArchive_failed;
309
310     strcpy(info->filename, name);
311     info->last_mod_time = modtime;
312     return(info);
313
314 WAD_openArchive_failed:
315     if (info != NULL)
316     {
317         if (info->filename != NULL)
318             allocator.Free(info->filename);
319         if (info->entries != NULL)
320             allocator.Free(info->entries);
321         allocator.Free(info);
322     } /* if */
323
324     return(NULL);
325 } /* WAD_openArchive */
326
327
328 static void WAD_enumerateFiles(dvoid *opaque, const char *dname,
329                                int omitSymLinks, PHYSFS_EnumFilesCallback cb,
330                                const char *origdir, void *callbackdata)
331 {
332     WADinfo *info = ((WADinfo *) opaque);
333     WADentry *entry = info->entries;
334     PHYSFS_uint32 max = info->entryCount;
335     PHYSFS_uint32 i;
336     const char *name;
337     char *sep;
338
339     if (*dname == '\0')  /* root directory enumeration? */
340     {
341         for (i = 0; i < max; i++, entry++)
342         {
343             name = entry->name;
344             if (strchr(name, '/') == NULL)
345                 cb(callbackdata, origdir, name);
346         } /* for */
347     } /* if */
348     else
349     {
350         for (i = 0; i < max; i++, entry++)
351         {
352             name = entry->name;
353             sep = strchr(name, '/');
354             if (sep != NULL)
355             {
356                 if (strncmp(dname, name, (sep - name)) == 0)
357                     cb(callbackdata, origdir, sep + 1);
358             } /* if */
359         } /* for */
360     } /* else */
361 } /* WAD_enumerateFiles */
362
363
364 static WADentry *wad_find_entry(WADinfo *info, const char *name)
365 {
366     WADentry *a = info->entries;
367     PHYSFS_sint32 lo = 0;
368     PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1);
369     PHYSFS_sint32 middle;
370     int rc;
371
372     while (lo <= hi)
373     {
374         middle = lo + ((hi - lo) / 2);
375         rc = strcmp(name, a[middle].name);
376         if (rc == 0)  /* found it! */
377             return(&a[middle]);
378         else if (rc > 0)
379             lo = middle + 1;
380         else
381             hi = middle - 1;
382     } /* while */
383
384     BAIL_MACRO(ERR_NO_SUCH_FILE, NULL);
385 } /* wad_find_entry */
386
387
388 static int WAD_exists(dvoid *opaque, const char *name)
389 {
390     return(wad_find_entry(((WADinfo *) opaque), name) != NULL);
391 } /* WAD_exists */
392
393
394 static int WAD_isDirectory(dvoid *opaque, const char *name, int *fileExists)
395 {
396     WADentry *entry = wad_find_entry(((WADinfo *) opaque), name);
397     if (entry != NULL)
398     {
399         char *n;
400
401         *fileExists = 1;
402
403         /* Can't be a directory if it's a subdirectory. */
404         if (strchr(entry->name, '/') != NULL)
405             return(0);
406
407         /* Check if it matches "MAP??" or "E?M?" ... */
408         n = entry->name;
409         if ((n[0] == 'E' && n[2] == 'M') ||
410             (n[0] == 'M' && n[1] == 'A' && n[2] == 'P' && n[6] == 0))
411         {
412             return(1);
413         } /* if */
414         return(0);
415     } /* if */
416     else
417     {
418         *fileExists = 0;
419         return(0);
420     } /* else */
421 } /* WAD_isDirectory */
422
423
424 static int WAD_isSymLink(dvoid *opaque, const char *name, int *fileExists)
425 {
426     *fileExists = WAD_exists(opaque, name);
427     return(0);  /* never symlinks in a wad. */
428 } /* WAD_isSymLink */
429
430
431 static PHYSFS_sint64 WAD_getLastModTime(dvoid *opaque,
432                                         const char *name,
433                                         int *fileExists)
434 {
435     WADinfo *info = ((WADinfo *) opaque);
436     PHYSFS_sint64 retval = -1;
437
438     *fileExists = (wad_find_entry(info, name) != NULL);
439     if (*fileExists)  /* use time of WAD itself in the physical filesystem. */
440         retval = info->last_mod_time;
441
442     return(retval);
443 } /* WAD_getLastModTime */
444
445
446 static fvoid *WAD_openRead(dvoid *opaque, const char *fnm, int *fileExists)
447 {
448     WADinfo *info = ((WADinfo *) opaque);
449     WADfileinfo *finfo;
450     WADentry *entry;
451
452     entry = wad_find_entry(info, fnm);
453     *fileExists = (entry != NULL);
454     BAIL_IF_MACRO(entry == NULL, NULL, NULL);
455
456     finfo = (WADfileinfo *) allocator.Malloc(sizeof (WADfileinfo));
457     BAIL_IF_MACRO(finfo == NULL, ERR_OUT_OF_MEMORY, NULL);
458
459     finfo->handle = __PHYSFS_platformOpenRead(info->filename);
460     if ( (finfo->handle == NULL) ||
461          (!__PHYSFS_platformSeek(finfo->handle, entry->startPos)) )
462     {
463         allocator.Free(finfo);
464         return(NULL);
465     } /* if */
466
467     finfo->curPos = 0;
468     finfo->entry = entry;
469     return(finfo);
470 } /* WAD_openRead */
471
472
473 static fvoid *WAD_openWrite(dvoid *opaque, const char *name)
474 {
475     BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
476 } /* WAD_openWrite */
477
478
479 static fvoid *WAD_openAppend(dvoid *opaque, const char *name)
480 {
481     BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
482 } /* WAD_openAppend */
483
484
485 static int WAD_remove(dvoid *opaque, const char *name)
486 {
487     BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
488 } /* WAD_remove */
489
490
491 static int WAD_mkdir(dvoid *opaque, const char *name)
492 {
493     BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
494 } /* WAD_mkdir */
495
496
497 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_WAD =
498 {
499     "WAD",
500     WAD_ARCHIVE_DESCRIPTION,
501     "Travis Wells <traviswells@mchsi.com>",
502     "http://www.3dmm2.com/doom/",
503 };
504
505
506 const PHYSFS_Archiver __PHYSFS_Archiver_WAD =
507 {
508     &__PHYSFS_ArchiveInfo_WAD,
509     WAD_isArchive,          /* isArchive() method      */
510     WAD_openArchive,        /* openArchive() method    */
511     WAD_enumerateFiles,     /* enumerateFiles() method */
512     WAD_exists,             /* exists() method         */
513     WAD_isDirectory,        /* isDirectory() method    */
514     WAD_isSymLink,          /* isSymLink() method      */
515     WAD_getLastModTime,     /* getLastModTime() method */
516     WAD_openRead,           /* openRead() method       */
517     WAD_openWrite,          /* openWrite() method      */
518     WAD_openAppend,         /* openAppend() method     */
519     WAD_remove,             /* remove() method         */
520     WAD_mkdir,              /* mkdir() method          */
521     WAD_dirClose,           /* dirClose() method       */
522     WAD_read,               /* read() method           */
523     WAD_write,              /* write() method          */
524     WAD_eof,                /* eof() method            */
525     WAD_tell,               /* tell() method           */
526     WAD_seek,               /* seek() method           */
527     WAD_fileLength,         /* fileLength() method     */
528     WAD_fileClose           /* fileClose() method      */
529 };
530
531 #endif  /* defined PHYSFS_SUPPORTS_WAD */
532
533 /* end of wad.c ... */
534