Initial integration, lots of broken stuff
[supertux.git] / src / unison / physfs-1.1.1 / 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     WADentry *a = (WADentry *) _a;
219     return(strcmp(a[one].name, a[two].name));
220 } /* wad_entry_cmp */
221
222
223 static void wad_entry_swap(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
224 {
225     WADentry tmp;
226     WADentry *first = &(((WADentry *) _a)[one]);
227     WADentry *second = &(((WADentry *) _a)[two]);
228     memcpy(&tmp, first, sizeof (WADentry));
229     memcpy(first, second, sizeof (WADentry));
230     memcpy(second, &tmp, sizeof (WADentry));
231 } /* wad_entry_swap */
232
233
234 static int wad_load_entries(const char *name, int forWriting, WADinfo *info)
235 {
236     void *fh = NULL;
237     PHYSFS_uint32 fileCount;
238     PHYSFS_uint32 directoryOffset;
239     WADentry *entry;
240     char lastDirectory[9];
241
242     lastDirectory[8] = 0; /* Make sure lastDirectory stays null-terminated. */
243
244     BAIL_IF_MACRO(!wad_open(name, forWriting, &fh, &fileCount,&directoryOffset), NULL, 0);
245     info->entryCount = fileCount;
246     info->entries = (WADentry *) allocator.Malloc(sizeof(WADentry)*fileCount);
247     if (info->entries == NULL)
248     {
249         __PHYSFS_platformClose(fh);
250         BAIL_MACRO(ERR_OUT_OF_MEMORY, 0);
251     } /* if */
252
253     __PHYSFS_platformSeek(fh,directoryOffset);
254
255     for (entry = info->entries; fileCount > 0; fileCount--, entry++)
256     {
257         if (__PHYSFS_platformRead(fh, &entry->startPos, 4, 1) != 1)
258         {
259             __PHYSFS_platformClose(fh);
260             return(0);
261         } /* if */
262         
263         if (__PHYSFS_platformRead(fh, &entry->size, 4, 1) != 1)
264         {
265             __PHYSFS_platformClose(fh);
266             return(0);
267         } /* if */
268
269         if (__PHYSFS_platformRead(fh, &entry->name, 8, 1) != 1)
270         {
271             __PHYSFS_platformClose(fh);
272             return(0);
273         } /* if */
274
275         entry->name[8] = '\0'; /* name might not be null-terminated in file. */
276         entry->size = PHYSFS_swapULE32(entry->size);
277         entry->startPos = PHYSFS_swapULE32(entry->startPos);
278     } /* for */
279
280     __PHYSFS_platformClose(fh);
281
282     __PHYSFS_sort(info->entries, info->entryCount,
283                   wad_entry_cmp, wad_entry_swap);
284     return(1);
285 } /* wad_load_entries */
286
287
288 static void *WAD_openArchive(const char *name, int forWriting)
289 {
290     PHYSFS_sint64 modtime = __PHYSFS_platformGetLastModTime(name);
291     WADinfo *info = (WADinfo *) allocator.Malloc(sizeof (WADinfo));
292
293     BAIL_IF_MACRO(info == NULL, ERR_OUT_OF_MEMORY, NULL);
294     memset(info, '\0', sizeof (WADinfo));
295
296     info->filename = (char *) allocator.Malloc(strlen(name) + 1);
297     GOTO_IF_MACRO(!info->filename, ERR_OUT_OF_MEMORY, WAD_openArchive_failed);
298
299     if (!wad_load_entries(name, forWriting, info))
300         goto WAD_openArchive_failed;
301
302     strcpy(info->filename, name);
303     info->last_mod_time = modtime;
304     return(info);
305
306 WAD_openArchive_failed:
307     if (info != NULL)
308     {
309         if (info->filename != NULL)
310             allocator.Free(info->filename);
311         if (info->entries != NULL)
312             allocator.Free(info->entries);
313         allocator.Free(info);
314     } /* if */
315
316     return(NULL);
317 } /* WAD_openArchive */
318
319
320 static void WAD_enumerateFiles(dvoid *opaque, const char *dname,
321                                int omitSymLinks, PHYSFS_EnumFilesCallback cb,
322                                const char *origdir, void *callbackdata)
323 {
324     WADinfo *info = ((WADinfo *) opaque);
325     WADentry *entry = info->entries;
326     PHYSFS_uint32 max = info->entryCount;
327     PHYSFS_uint32 i;
328     const char *name;
329     char *sep;
330
331     if (*dname == '\0')  /* root directory enumeration? */
332     {
333         for (i = 0; i < max; i++, entry++)
334         {
335             name = entry->name;
336             if (strchr(name, '/') == NULL)
337                 cb(callbackdata, origdir, name);
338         } /* for */
339     } /* if */
340     else
341     {
342         for (i = 0; i < max; i++, entry++)
343         {
344             name = entry->name;
345             sep = strchr(name, '/');
346             if (sep != NULL)
347             {
348                 if (strncmp(dname, name, (sep - name)) == 0)
349                     cb(callbackdata, origdir, sep + 1);
350             } /* if */
351         } /* for */
352     } /* else */
353 } /* WAD_enumerateFiles */
354
355
356 static WADentry *wad_find_entry(WADinfo *info, const char *name)
357 {
358     WADentry *a = info->entries;
359     PHYSFS_sint32 lo = 0;
360     PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1);
361     PHYSFS_sint32 middle;
362     int rc;
363
364     while (lo <= hi)
365     {
366         middle = lo + ((hi - lo) / 2);
367         rc = strcmp(name, a[middle].name);
368         if (rc == 0)  /* found it! */
369             return(&a[middle]);
370         else if (rc > 0)
371             lo = middle + 1;
372         else
373             hi = middle - 1;
374     } /* while */
375
376     BAIL_MACRO(ERR_NO_SUCH_FILE, NULL);
377 } /* wad_find_entry */
378
379
380 static int WAD_exists(dvoid *opaque, const char *name)
381 {
382     return(wad_find_entry(((WADinfo *) opaque), name) != NULL);
383 } /* WAD_exists */
384
385
386 static int WAD_isDirectory(dvoid *opaque, const char *name, int *fileExists)
387 {
388     WADentry *entry = wad_find_entry(((WADinfo *) opaque), name);
389     if (entry != NULL)
390     {
391         char *n;
392
393         *fileExists = 1;
394
395         /* Can't be a directory if it's a subdirectory. */
396         if (strchr(entry->name, '/') != NULL)
397             return(0);
398
399         /* Check if it matches "MAP??" or "E?M?" ... */
400         n = entry->name;
401         if ((n[0] == 'E' && n[2] == 'M') ||
402             (n[0] == 'M' && n[1] == 'A' && n[2] == 'P' && n[6] == 0))
403         {
404             return(1);
405         } /* if */
406         return(0);
407     } /* if */
408     else
409     {
410         *fileExists = 0;
411         return(0);
412     } /* else */
413 } /* WAD_isDirectory */
414
415
416 static int WAD_isSymLink(dvoid *opaque, const char *name, int *fileExists)
417 {
418     *fileExists = WAD_exists(opaque, name);
419     return(0);  /* never symlinks in a wad. */
420 } /* WAD_isSymLink */
421
422
423 static PHYSFS_sint64 WAD_getLastModTime(dvoid *opaque,
424                                         const char *name,
425                                         int *fileExists)
426 {
427     WADinfo *info = ((WADinfo *) opaque);
428     PHYSFS_sint64 retval = -1;
429
430     *fileExists = (wad_find_entry(info, name) != NULL);
431     if (*fileExists)  /* use time of WAD itself in the physical filesystem. */
432         retval = info->last_mod_time;
433
434     return(retval);
435 } /* WAD_getLastModTime */
436
437
438 static fvoid *WAD_openRead(dvoid *opaque, const char *fnm, int *fileExists)
439 {
440     WADinfo *info = ((WADinfo *) opaque);
441     WADfileinfo *finfo;
442     WADentry *entry;
443
444     entry = wad_find_entry(info, fnm);
445     *fileExists = (entry != NULL);
446     BAIL_IF_MACRO(entry == NULL, NULL, NULL);
447
448     finfo = (WADfileinfo *) allocator.Malloc(sizeof (WADfileinfo));
449     BAIL_IF_MACRO(finfo == NULL, ERR_OUT_OF_MEMORY, NULL);
450
451     finfo->handle = __PHYSFS_platformOpenRead(info->filename);
452     if ( (finfo->handle == NULL) ||
453          (!__PHYSFS_platformSeek(finfo->handle, entry->startPos)) )
454     {
455         allocator.Free(finfo);
456         return(NULL);
457     } /* if */
458
459     finfo->curPos = 0;
460     finfo->entry = entry;
461     return(finfo);
462 } /* WAD_openRead */
463
464
465 static fvoid *WAD_openWrite(dvoid *opaque, const char *name)
466 {
467     BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
468 } /* WAD_openWrite */
469
470
471 static fvoid *WAD_openAppend(dvoid *opaque, const char *name)
472 {
473     BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
474 } /* WAD_openAppend */
475
476
477 static int WAD_remove(dvoid *opaque, const char *name)
478 {
479     BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
480 } /* WAD_remove */
481
482
483 static int WAD_mkdir(dvoid *opaque, const char *name)
484 {
485     BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
486 } /* WAD_mkdir */
487
488
489 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_WAD =
490 {
491     "WAD",
492     WAD_ARCHIVE_DESCRIPTION,
493     "Travis Wells <traviswells@mchsi.com>",
494     "http://www.3dmm2.com/doom/",
495 };
496
497
498 const PHYSFS_Archiver __PHYSFS_Archiver_WAD =
499 {
500     &__PHYSFS_ArchiveInfo_WAD,
501     WAD_isArchive,          /* isArchive() method      */
502     WAD_openArchive,        /* openArchive() method    */
503     WAD_enumerateFiles,     /* enumerateFiles() method */
504     WAD_exists,             /* exists() method         */
505     WAD_isDirectory,        /* isDirectory() method    */
506     WAD_isSymLink,          /* isSymLink() method      */
507     WAD_getLastModTime,     /* getLastModTime() method */
508     WAD_openRead,           /* openRead() method       */
509     WAD_openWrite,          /* openWrite() method      */
510     WAD_openAppend,         /* openAppend() method     */
511     WAD_remove,             /* remove() method         */
512     WAD_mkdir,              /* mkdir() method          */
513     WAD_dirClose,           /* dirClose() method       */
514     WAD_read,               /* read() method           */
515     WAD_write,              /* write() method          */
516     WAD_eof,                /* eof() method            */
517     WAD_tell,               /* tell() method           */
518     WAD_seek,               /* seek() method           */
519     WAD_fileLength,         /* fileLength() method     */
520     WAD_fileClose           /* fileClose() method      */
521 };
522
523 #endif  /* defined PHYSFS_SUPPORTS_WAD */
524
525 /* end of wad.c ... */
526