Upload 2.0.2
[physicsfs] / archivers / hog.c
1 /*
2  * HOG support routines for PhysicsFS.
3  *
4  * This driver handles Descent I/II HOG archives.
5  *
6  * The format is very simple:
7  *
8  *   The file always starts with the 3-byte signature "DHF" (Descent
9  *   HOG file). After that the files of a HOG are just attached after
10  *   another, divided by a 17 bytes header, which specifies the name
11  *   and length (in bytes) of the forthcoming file! So you just read
12  *   the header with its information of how big the following file is,
13  *   and then skip exact that number of bytes to get to the next file
14  *   in that HOG.
15  *
16  *    char sig[3] = {'D', 'H', 'F'}; // "DHF"=Descent HOG File
17  *
18  *    struct {
19  *     char file_name[13]; // Filename, padded to 13 bytes with 0s
20  *     int file_size; // filesize in bytes
21  *     char data[file_size]; // The file data
22  *    } FILE_STRUCT; // Repeated until the end of the file.
23  *
24  * (That info is from http://www.descent2.com/ddn/specs/hog/)
25  *
26  * Please see the file LICENSE.txt in the source's root directory.
27  *
28  *  This file written by Bradley Bell.
29  *  Based on grp.c by Ryan C. Gordon.
30  */
31
32 #if (defined PHYSFS_SUPPORTS_HOG)
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include "physfs.h"
38
39 #define __PHYSICSFS_INTERNAL__
40 #include "physfs_internal.h"
41
42 /*
43  * One HOGentry is kept for each file in an open HOG archive.
44  */
45 typedef struct
46 {
47     char name[13];
48     PHYSFS_uint32 startPos;
49     PHYSFS_uint32 size;
50 } HOGentry;
51
52 /*
53  * One HOGinfo is kept for each open HOG archive.
54  */
55 typedef struct
56 {
57     char *filename;
58     PHYSFS_sint64 last_mod_time;
59     PHYSFS_uint32 entryCount;
60     HOGentry *entries;
61 } HOGinfo;
62
63 /*
64  * One HOGfileinfo is kept for each open file in a HOG archive.
65  */
66 typedef struct
67 {
68     void *handle;
69     HOGentry *entry;
70     PHYSFS_uint32 curPos;
71 } HOGfileinfo;
72
73
74 static void HOG_dirClose(dvoid *opaque)
75 {
76     HOGinfo *info = ((HOGinfo *) opaque);
77     allocator.Free(info->filename);
78     allocator.Free(info->entries);
79     allocator.Free(info);
80 } /* HOG_dirClose */
81
82
83 static PHYSFS_sint64 HOG_read(fvoid *opaque, void *buffer,
84                               PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
85 {
86     HOGfileinfo *finfo = (HOGfileinfo *) opaque;
87     HOGentry *entry = finfo->entry;
88     PHYSFS_uint32 bytesLeft = entry->size - finfo->curPos;
89     PHYSFS_uint32 objsLeft = (bytesLeft / objSize);
90     PHYSFS_sint64 rc;
91
92     if (objsLeft < objCount)
93         objCount = objsLeft;
94
95     rc = __PHYSFS_platformRead(finfo->handle, buffer, objSize, objCount);
96     if (rc > 0)
97         finfo->curPos += (PHYSFS_uint32) (rc * objSize);
98
99     return(rc);
100 } /* HOG_read */
101
102
103 static PHYSFS_sint64 HOG_write(fvoid *opaque, const void *buffer,
104                                PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
105 {
106     BAIL_MACRO(ERR_NOT_SUPPORTED, -1);
107 } /* HOG_write */
108
109
110 static int HOG_eof(fvoid *opaque)
111 {
112     HOGfileinfo *finfo = (HOGfileinfo *) opaque;
113     HOGentry *entry = finfo->entry;
114     return(finfo->curPos >= entry->size);
115 } /* HOG_eof */
116
117
118 static PHYSFS_sint64 HOG_tell(fvoid *opaque)
119 {
120     return(((HOGfileinfo *) opaque)->curPos);
121 } /* HOG_tell */
122
123
124 static int HOG_seek(fvoid *opaque, PHYSFS_uint64 offset)
125 {
126     HOGfileinfo *finfo = (HOGfileinfo *) opaque;
127     HOGentry *entry = finfo->entry;
128     int rc;
129
130     BAIL_IF_MACRO(offset < 0, ERR_INVALID_ARGUMENT, 0);
131     BAIL_IF_MACRO(offset >= entry->size, ERR_PAST_EOF, 0);
132     rc = __PHYSFS_platformSeek(finfo->handle, entry->startPos + offset);
133     if (rc)
134         finfo->curPos = (PHYSFS_uint32) offset;
135
136     return(rc);
137 } /* HOG_seek */
138
139
140 static PHYSFS_sint64 HOG_fileLength(fvoid *opaque)
141 {
142     HOGfileinfo *finfo = (HOGfileinfo *) opaque;
143     return((PHYSFS_sint64) finfo->entry->size);
144 } /* HOG_fileLength */
145
146
147 static int HOG_fileClose(fvoid *opaque)
148 {
149     HOGfileinfo *finfo = (HOGfileinfo *) opaque;
150     BAIL_IF_MACRO(!__PHYSFS_platformClose(finfo->handle), NULL, 0);
151     allocator.Free(finfo);
152     return(1);
153 } /* HOG_fileClose */
154
155
156 static int hog_open(const char *filename, int forWriting,
157                     void **fh, PHYSFS_uint32 *count)
158 {
159     PHYSFS_uint8 buf[13];
160     PHYSFS_uint32 size;
161     PHYSFS_sint64 pos;
162
163     *count = 0;
164
165     *fh = NULL;
166     BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, 0);
167
168     *fh = __PHYSFS_platformOpenRead(filename);
169     BAIL_IF_MACRO(*fh == NULL, NULL, 0);
170
171     if (__PHYSFS_platformRead(*fh, buf, 3, 1) != 1)
172         goto openHog_failed;
173
174     if (memcmp(buf, "DHF", 3) != 0)
175     {
176         __PHYSFS_setError(ERR_UNSUPPORTED_ARCHIVE);
177         goto openHog_failed;
178     } /* if */
179
180     while (1)
181     {
182         if (__PHYSFS_platformRead(*fh, buf, 13, 1) != 1)
183             break; /* eof here is ok */
184
185         if (__PHYSFS_platformRead(*fh, &size, 4, 1) != 1)
186             goto openHog_failed;
187
188         size = PHYSFS_swapULE32(size);
189
190         (*count)++;
191
192         /* Skip over entry... */
193         pos = __PHYSFS_platformTell(*fh);
194         if (pos == -1)
195             goto openHog_failed;
196         if (!__PHYSFS_platformSeek(*fh, pos + size))
197             goto openHog_failed;
198     } /* while */
199
200     /* Rewind to start of entries... */
201     if (!__PHYSFS_platformSeek(*fh, 3))
202         goto openHog_failed;
203
204     return(1);
205
206 openHog_failed:
207     if (*fh != NULL)
208         __PHYSFS_platformClose(*fh);
209
210     *count = -1;
211     *fh = NULL;
212     return(0);
213 } /* hog_open */
214
215
216 static int HOG_isArchive(const char *filename, int forWriting)
217 {
218     void *fh;
219     PHYSFS_uint32 fileCount;
220     int retval = hog_open(filename, forWriting, &fh, &fileCount);
221
222     if (fh != NULL)
223         __PHYSFS_platformClose(fh);
224
225     return(retval);
226 } /* HOG_isArchive */
227
228
229 static int hog_entry_cmp(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
230 {
231     if (one != two)
232     {
233         const HOGentry *a = (const HOGentry *) _a;
234         return(__PHYSFS_stricmpASCII(a[one].name, a[two].name));
235     } /* if */
236
237     return 0;
238 } /* hog_entry_cmp */
239
240
241 static void hog_entry_swap(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
242 {
243     if (one != two)
244     {
245         HOGentry tmp;
246         HOGentry *first = &(((HOGentry *) _a)[one]);
247         HOGentry *second = &(((HOGentry *) _a)[two]);
248         memcpy(&tmp, first, sizeof (HOGentry));
249         memcpy(first, second, sizeof (HOGentry));
250         memcpy(second, &tmp, sizeof (HOGentry));
251     } /* if */
252 } /* hog_entry_swap */
253
254
255 static int hog_load_entries(const char *name, int forWriting, HOGinfo *info)
256 {
257     void *fh = NULL;
258     PHYSFS_uint32 fileCount;
259     HOGentry *entry;
260
261     BAIL_IF_MACRO(!hog_open(name, forWriting, &fh, &fileCount), NULL, 0);
262     info->entryCount = fileCount;
263     info->entries = (HOGentry *) allocator.Malloc(sizeof(HOGentry)*fileCount);
264     if (info->entries == NULL)
265     {
266         __PHYSFS_platformClose(fh);
267         BAIL_MACRO(ERR_OUT_OF_MEMORY, 0);
268     } /* if */
269
270     for (entry = info->entries; fileCount > 0; fileCount--, entry++)
271     {
272         if (__PHYSFS_platformRead(fh, &entry->name, 13, 1) != 1)
273         {
274             __PHYSFS_platformClose(fh);
275             return(0);
276         } /* if */
277
278         if (__PHYSFS_platformRead(fh, &entry->size, 4, 1) != 1)
279         {
280             __PHYSFS_platformClose(fh);
281             return(0);
282         } /* if */
283
284         entry->size = PHYSFS_swapULE32(entry->size);
285         entry->startPos = (unsigned int) __PHYSFS_platformTell(fh);
286         if (entry->startPos == -1)
287         {
288             __PHYSFS_platformClose(fh);
289             return(0);
290         }
291
292         /* Skip over entry */
293         if (!__PHYSFS_platformSeek(fh, entry->startPos + entry->size))
294         {
295             __PHYSFS_platformClose(fh);
296             return(0);
297         }
298     } /* for */
299
300     __PHYSFS_platformClose(fh);
301
302     __PHYSFS_sort(info->entries, info->entryCount,
303                   hog_entry_cmp, hog_entry_swap);
304     return(1);
305 } /* hog_load_entries */
306
307
308 static void *HOG_openArchive(const char *name, int forWriting)
309 {
310     PHYSFS_sint64 modtime = __PHYSFS_platformGetLastModTime(name);
311     HOGinfo *info = (HOGinfo *) allocator.Malloc(sizeof (HOGinfo));
312
313     BAIL_IF_MACRO(info == NULL, ERR_OUT_OF_MEMORY, 0);
314     memset(info, '\0', sizeof (HOGinfo));
315     info->filename = (char *) allocator.Malloc(strlen(name) + 1);
316     GOTO_IF_MACRO(!info->filename, ERR_OUT_OF_MEMORY, HOG_openArchive_failed);
317
318     if (!hog_load_entries(name, forWriting, info))
319         goto HOG_openArchive_failed;
320
321     strcpy(info->filename, name);
322     info->last_mod_time = modtime;
323
324     return(info);
325
326 HOG_openArchive_failed:
327     if (info != NULL)
328     {
329         if (info->filename != NULL)
330             allocator.Free(info->filename);
331         if (info->entries != NULL)
332             allocator.Free(info->entries);
333         allocator.Free(info);
334     } /* if */
335
336     return(NULL);
337 } /* HOG_openArchive */
338
339
340 static void HOG_enumerateFiles(dvoid *opaque, const char *dname,
341                                int omitSymLinks, PHYSFS_EnumFilesCallback cb,
342                                const char *origdir, void *callbackdata)
343 {
344     /* no directories in HOG files. */
345     if (*dname == '\0')
346     {
347         HOGinfo *info = (HOGinfo *) opaque;
348         HOGentry *entry = info->entries;
349         PHYSFS_uint32 max = info->entryCount;
350         PHYSFS_uint32 i;
351
352         for (i = 0; i < max; i++, entry++)
353             cb(callbackdata, origdir, entry->name);
354     } /* if */
355 } /* HOG_enumerateFiles */
356
357
358 static HOGentry *hog_find_entry(HOGinfo *info, const char *name)
359 {
360     char *ptr = strchr(name, '.');
361     HOGentry *a = info->entries;
362     PHYSFS_sint32 lo = 0;
363     PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1);
364     PHYSFS_sint32 middle;
365     int rc;
366
367     /*
368      * Rule out filenames to avoid unneeded processing...no dirs,
369      *   big filenames, or extensions > 3 chars.
370      */
371     BAIL_IF_MACRO((ptr) && (strlen(ptr) > 4), ERR_NO_SUCH_FILE, NULL);
372     BAIL_IF_MACRO(strlen(name) > 12, ERR_NO_SUCH_FILE, NULL);
373     BAIL_IF_MACRO(strchr(name, '/') != NULL, ERR_NO_SUCH_FILE, NULL);
374
375     while (lo <= hi)
376     {
377         middle = lo + ((hi - lo) / 2);
378         rc = __PHYSFS_stricmpASCII(name, a[middle].name);
379         if (rc == 0)  /* found it! */
380             return(&a[middle]);
381         else if (rc > 0)
382             lo = middle + 1;
383         else
384             hi = middle - 1;
385     } /* while */
386
387     BAIL_MACRO(ERR_NO_SUCH_FILE, NULL);
388 } /* hog_find_entry */
389
390
391 static int HOG_exists(dvoid *opaque, const char *name)
392 {
393     return(hog_find_entry(((HOGinfo *) opaque), name) != NULL);
394 } /* HOG_exists */
395
396
397 static int HOG_isDirectory(dvoid *opaque, const char *name, int *fileExists)
398 {
399     *fileExists = HOG_exists(opaque, name);
400     return(0);  /* never directories in a groupfile. */
401 } /* HOG_isDirectory */
402
403
404 static int HOG_isSymLink(dvoid *opaque, const char *name, int *fileExists)
405 {
406     *fileExists = HOG_exists(opaque, name);
407     return(0);  /* never symlinks in a groupfile. */
408 } /* HOG_isSymLink */
409
410
411 static PHYSFS_sint64 HOG_getLastModTime(dvoid *opaque,
412                                         const char *name,
413                                         int *fileExists)
414 {
415     HOGinfo *info = ((HOGinfo *) opaque);
416     PHYSFS_sint64 retval = -1;
417
418     *fileExists = (hog_find_entry(info, name) != NULL);
419     if (*fileExists)  /* use time of HOG itself in the physical filesystem. */
420         retval = info->last_mod_time;
421
422     return(retval);
423 } /* HOG_getLastModTime */
424
425
426 static fvoid *HOG_openRead(dvoid *opaque, const char *fnm, int *fileExists)
427 {
428     HOGinfo *info = ((HOGinfo *) opaque);
429     HOGfileinfo *finfo;
430     HOGentry *entry;
431
432     entry = hog_find_entry(info, fnm);
433     *fileExists = (entry != NULL);
434     BAIL_IF_MACRO(entry == NULL, NULL, NULL);
435
436     finfo = (HOGfileinfo *) allocator.Malloc(sizeof (HOGfileinfo));
437     BAIL_IF_MACRO(finfo == NULL, ERR_OUT_OF_MEMORY, NULL);
438
439     finfo->handle = __PHYSFS_platformOpenRead(info->filename);
440     if ( (finfo->handle == NULL) ||
441          (!__PHYSFS_platformSeek(finfo->handle, entry->startPos)) )
442     {
443         allocator.Free(finfo);
444         return(NULL);
445     } /* if */
446
447     finfo->curPos = 0;
448     finfo->entry = entry;
449     return(finfo);
450 } /* HOG_openRead */
451
452
453 static fvoid *HOG_openWrite(dvoid *opaque, const char *name)
454 {
455     BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
456 } /* HOG_openWrite */
457
458
459 static fvoid *HOG_openAppend(dvoid *opaque, const char *name)
460 {
461     BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
462 } /* HOG_openAppend */
463
464
465 static int HOG_remove(dvoid *opaque, const char *name)
466 {
467     BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
468 } /* HOG_remove */
469
470
471 static int HOG_mkdir(dvoid *opaque, const char *name)
472 {
473     BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
474 } /* HOG_mkdir */
475
476
477 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_HOG =
478 {
479     "HOG",
480     HOG_ARCHIVE_DESCRIPTION,
481     "Bradley Bell <btb@icculus.org>",
482     "http://icculus.org/physfs/",
483 };
484
485
486 const PHYSFS_Archiver __PHYSFS_Archiver_HOG =
487 {
488     &__PHYSFS_ArchiveInfo_HOG,
489     HOG_isArchive,          /* isArchive() method      */
490     HOG_openArchive,        /* openArchive() method    */
491     HOG_enumerateFiles,     /* enumerateFiles() method */
492     HOG_exists,             /* exists() method         */
493     HOG_isDirectory,        /* isDirectory() method    */
494     HOG_isSymLink,          /* isSymLink() method      */
495     HOG_getLastModTime,     /* getLastModTime() method */
496     HOG_openRead,           /* openRead() method       */
497     HOG_openWrite,          /* openWrite() method      */
498     HOG_openAppend,         /* openAppend() method     */
499     HOG_remove,             /* remove() method         */
500     HOG_mkdir,              /* mkdir() method          */
501     HOG_dirClose,           /* dirClose() method       */
502     HOG_read,               /* read() method           */
503     HOG_write,              /* write() method          */
504     HOG_eof,                /* eof() method            */
505     HOG_tell,               /* tell() method           */
506     HOG_seek,               /* seek() method           */
507     HOG_fileLength,         /* fileLength() method     */
508     HOG_fileClose           /* fileClose() method      */
509 };
510
511 #endif  /* defined PHYSFS_SUPPORTS_HOG */
512
513 /* end of hog.c ... */
514