8fbda239dd848ee9e2aec1d9e455b9224a13fa33
[physicsfs] / archivers / mvl.c
1 /*
2  * MVL support routines for PhysicsFS.
3  *
4  * This driver handles Descent II Movielib archives.
5  *
6  * The file format of MVL is quite easy...
7  *
8  *   //MVL File format - Written by Heiko Herrmann
9  *   char sig[4] = {'D','M', 'V', 'L'}; // "DMVL"=Descent MoVie Library
10  *
11  *   int num_files; // the number of files in this MVL
12  *
13  *   struct {
14  *    char file_name[13]; // Filename, padded to 13 bytes with 0s
15  *    int file_size; // filesize in bytes
16  *   }DIR_STRUCT[num_files];
17  *
18  *   struct {
19  *    char data[file_size]; // The file data
20  *   }FILE_STRUCT[num_files];
21  *
22  * (That info is from http://www.descent2.com/ddn/specs/mvl/)
23  *
24  * Please see the file LICENSE.txt in the source's root directory.
25  *
26  *  This file written by Bradley Bell.
27  *  Based on grp.c by Ryan C. Gordon.
28  */
29
30 #if (defined PHYSFS_SUPPORTS_MVL)
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "physfs.h"
36
37 #define __PHYSICSFS_INTERNAL__
38 #include "physfs_internal.h"
39
40 typedef struct
41 {
42     char name[13];
43     PHYSFS_uint32 startPos;
44     PHYSFS_uint32 size;
45 } MVLentry;
46
47 typedef struct
48 {
49     char *filename;
50     PHYSFS_sint64 last_mod_time;
51     PHYSFS_uint32 entryCount;
52     MVLentry *entries;
53 } MVLinfo;
54
55 typedef struct
56 {
57     void *handle;
58     MVLentry *entry;
59     PHYSFS_uint32 curPos;
60 } MVLfileinfo;
61
62
63 static void MVL_dirClose(dvoid *opaque)
64 {
65     MVLinfo *info = ((MVLinfo *) opaque);
66     allocator.Free(info->filename);
67     allocator.Free(info->entries);
68     allocator.Free(info);
69 } /* MVL_dirClose */
70
71
72 static PHYSFS_sint64 MVL_read(fvoid *opaque, void *buffer,
73                               PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
74 {
75     MVLfileinfo *finfo = (MVLfileinfo *) opaque;
76     MVLentry *entry = finfo->entry;
77     PHYSFS_uint32 bytesLeft = entry->size - finfo->curPos;
78     PHYSFS_uint32 objsLeft = (bytesLeft / objSize);
79     PHYSFS_sint64 rc;
80
81     if (objsLeft < objCount)
82         objCount = objsLeft;
83
84     rc = __PHYSFS_platformRead(finfo->handle, buffer, objSize, objCount);
85     if (rc > 0)
86         finfo->curPos += (PHYSFS_uint32) (rc * objSize);
87
88     return(rc);
89 } /* MVL_read */
90
91
92 static PHYSFS_sint64 MVL_write(fvoid *opaque, const void *buffer,
93                                PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
94 {
95     BAIL_MACRO(ERR_NOT_SUPPORTED, -1);
96 } /* MVL_write */
97
98
99 static int MVL_eof(fvoid *opaque)
100 {
101     MVLfileinfo *finfo = (MVLfileinfo *) opaque;
102     MVLentry *entry = finfo->entry;
103     return(finfo->curPos >= entry->size);
104 } /* MVL_eof */
105
106
107 static PHYSFS_sint64 MVL_tell(fvoid *opaque)
108 {
109     return(((MVLfileinfo *) opaque)->curPos);
110 } /* MVL_tell */
111
112
113 static int MVL_seek(fvoid *opaque, PHYSFS_uint64 offset)
114 {
115     MVLfileinfo *finfo = (MVLfileinfo *) opaque;
116     MVLentry *entry = finfo->entry;
117     int rc;
118
119     BAIL_IF_MACRO(offset < 0, ERR_INVALID_ARGUMENT, 0);
120     BAIL_IF_MACRO(offset >= entry->size, ERR_PAST_EOF, 0);
121     rc = __PHYSFS_platformSeek(finfo->handle, entry->startPos + offset);
122     if (rc)
123         finfo->curPos = (PHYSFS_uint32) offset;
124
125     return(rc);
126 } /* MVL_seek */
127
128
129 static PHYSFS_sint64 MVL_fileLength(fvoid *opaque)
130 {
131     MVLfileinfo *finfo = (MVLfileinfo *) opaque;
132     return((PHYSFS_sint64) finfo->entry->size);
133 } /* MVL_fileLength */
134
135
136 static int MVL_fileClose(fvoid *opaque)
137 {
138     MVLfileinfo *finfo = (MVLfileinfo *) opaque;
139     BAIL_IF_MACRO(!__PHYSFS_platformClose(finfo->handle), NULL, 0);
140     allocator.Free(finfo);
141     return(1);
142 } /* MVL_fileClose */
143
144
145 static int mvl_open(const char *filename, int forWriting,
146                     void **fh, PHYSFS_uint32 *count)
147 {
148     PHYSFS_uint8 buf[4];
149
150     *fh = NULL;
151     BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, 0);
152
153     *fh = __PHYSFS_platformOpenRead(filename);
154     BAIL_IF_MACRO(*fh == NULL, NULL, 0);
155     
156     if (__PHYSFS_platformRead(*fh, buf, 4, 1) != 1)
157         goto openMvl_failed;
158
159     if (memcmp(buf, "DMVL", 4) != 0)
160     {
161         __PHYSFS_setError(ERR_UNSUPPORTED_ARCHIVE);
162         goto openMvl_failed;
163     } /* if */
164
165     if (__PHYSFS_platformRead(*fh, count, sizeof (PHYSFS_uint32), 1) != 1)
166         goto openMvl_failed;
167
168     *count = PHYSFS_swapULE32(*count);
169
170     return(1);
171
172 openMvl_failed:
173     if (*fh != NULL)
174         __PHYSFS_platformClose(*fh);
175
176     *count = -1;
177     *fh = NULL;
178     return(0);
179 } /* mvl_open */
180
181
182 static int MVL_isArchive(const char *filename, int forWriting)
183 {
184     void *fh;
185     PHYSFS_uint32 fileCount;
186     int retval = mvl_open(filename, forWriting, &fh, &fileCount);
187
188     if (fh != NULL)
189         __PHYSFS_platformClose(fh);
190
191     return(retval);
192 } /* MVL_isArchive */
193
194
195 static int mvl_entry_cmp(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
196 {
197     if (one != two)
198     {
199         const MVLentry *a = (const MVLentry *) _a;
200         return(strcmp(a[one].name, a[two].name));
201     } /* if */
202
203     return 0;
204 } /* mvl_entry_cmp */
205
206
207 static void mvl_entry_swap(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
208 {
209     if (one != two)
210     {
211         MVLentry tmp;
212         MVLentry *first = &(((MVLentry *) _a)[one]);
213         MVLentry *second = &(((MVLentry *) _a)[two]);
214         memcpy(&tmp, first, sizeof (MVLentry));
215         memcpy(first, second, sizeof (MVLentry));
216         memcpy(second, &tmp, sizeof (MVLentry));
217     } /* if */
218 } /* mvl_entry_swap */
219
220
221 static int mvl_load_entries(const char *name, int forWriting, MVLinfo *info)
222 {
223     void *fh = NULL;
224     PHYSFS_uint32 fileCount;
225     PHYSFS_uint32 location = 8;  /* sizeof sig. */
226     MVLentry *entry;
227
228     BAIL_IF_MACRO(!mvl_open(name, forWriting, &fh, &fileCount), NULL, 0);
229     info->entryCount = fileCount;
230     info->entries = (MVLentry *) allocator.Malloc(sizeof(MVLentry)*fileCount);
231     if (info->entries == NULL)
232     {
233         __PHYSFS_platformClose(fh);
234         BAIL_MACRO(ERR_OUT_OF_MEMORY, 0);
235     } /* if */
236
237     location += (17 * fileCount);
238
239     for (entry = info->entries; fileCount > 0; fileCount--, entry++)
240     {
241         if (__PHYSFS_platformRead(fh, &entry->name, 13, 1) != 1)
242         {
243             __PHYSFS_platformClose(fh);
244             return(0);
245         } /* if */
246
247         if (__PHYSFS_platformRead(fh, &entry->size, 4, 1) != 1)
248         {
249             __PHYSFS_platformClose(fh);
250             return(0);
251         } /* if */
252
253         entry->size = PHYSFS_swapULE32(entry->size);
254         entry->startPos = location;
255         location += entry->size;
256     } /* for */
257
258     __PHYSFS_platformClose(fh);
259
260     __PHYSFS_sort(info->entries, info->entryCount,
261                   mvl_entry_cmp, mvl_entry_swap);
262     return(1);
263 } /* mvl_load_entries */
264
265
266 static void *MVL_openArchive(const char *name, int forWriting)
267 {
268     PHYSFS_sint64 modtime = __PHYSFS_platformGetLastModTime(name);
269     MVLinfo *info = (MVLinfo *) allocator.Malloc(sizeof (MVLinfo));
270
271     BAIL_IF_MACRO(info == NULL, ERR_OUT_OF_MEMORY, NULL);
272     memset(info, '\0', sizeof (MVLinfo));
273
274     info->filename = (char *) allocator.Malloc(strlen(name) + 1);
275     GOTO_IF_MACRO(!info->filename, ERR_OUT_OF_MEMORY, MVL_openArchive_failed);
276     if (!mvl_load_entries(name, forWriting, info))
277         goto MVL_openArchive_failed;
278
279     strcpy(info->filename, name);
280     info->last_mod_time = modtime;
281     return(info);
282
283 MVL_openArchive_failed:
284     if (info != NULL)
285     {
286         if (info->filename != NULL)
287             allocator.Free(info->filename);
288         if (info->entries != NULL)
289             allocator.Free(info->entries);
290         allocator.Free(info);
291     } /* if */
292
293     return(NULL);
294 } /* MVL_openArchive */
295
296
297 static void MVL_enumerateFiles(dvoid *opaque, const char *dname,
298                                int omitSymLinks, PHYSFS_EnumFilesCallback cb,
299                                const char *origdir, void *callbackdata)
300 {
301     /* no directories in MVL files. */
302     if (*dname == '\0')
303     {
304         MVLinfo *info = ((MVLinfo *) opaque);
305         MVLentry *entry = info->entries;
306         PHYSFS_uint32 max = info->entryCount;
307         PHYSFS_uint32 i;
308
309         for (i = 0; i < max; i++, entry++)
310             cb(callbackdata, origdir, entry->name);
311     } /* if */
312 } /* MVL_enumerateFiles */
313
314
315 static MVLentry *mvl_find_entry(MVLinfo *info, const char *name)
316 {
317     char *ptr = strchr(name, '.');
318     MVLentry *a = info->entries;
319     PHYSFS_sint32 lo = 0;
320     PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1);
321     PHYSFS_sint32 middle;
322     int rc;
323
324     /*
325      * Rule out filenames to avoid unneeded processing...no dirs,
326      *   big filenames, or extensions > 3 chars.
327      */
328     BAIL_IF_MACRO((ptr) && (strlen(ptr) > 4), ERR_NO_SUCH_FILE, NULL);
329     BAIL_IF_MACRO(strlen(name) > 12, ERR_NO_SUCH_FILE, NULL);
330     BAIL_IF_MACRO(strchr(name, '/') != NULL, ERR_NO_SUCH_FILE, NULL);
331
332     while (lo <= hi)
333     {
334         middle = lo + ((hi - lo) / 2);
335         rc = __PHYSFS_stricmpASCII(name, a[middle].name);
336         if (rc == 0)  /* found it! */
337             return(&a[middle]);
338         else if (rc > 0)
339             lo = middle + 1;
340         else
341             hi = middle - 1;
342     } /* while */
343
344     BAIL_MACRO(ERR_NO_SUCH_FILE, NULL);
345 } /* mvl_find_entry */
346
347
348 static int MVL_exists(dvoid *opaque, const char *name)
349 {
350     return(mvl_find_entry(((MVLinfo *) opaque), name) != NULL);
351 } /* MVL_exists */
352
353
354 static int MVL_isDirectory(dvoid *opaque, const char *name, int *fileExists)
355 {
356     *fileExists = MVL_exists(opaque, name);
357     return(0);  /* never directories in a groupfile. */
358 } /* MVL_isDirectory */
359
360
361 static int MVL_isSymLink(dvoid *opaque, const char *name, int *fileExists)
362 {
363     *fileExists = MVL_exists(opaque, name);
364     return(0);  /* never symlinks in a groupfile. */
365 } /* MVL_isSymLink */
366
367
368 static PHYSFS_sint64 MVL_getLastModTime(dvoid *opaque,
369                                         const char *name,
370                                         int *fileExists)
371 {
372     MVLinfo *info = ((MVLinfo *) opaque);
373     PHYSFS_sint64 retval = -1;
374
375     *fileExists = (mvl_find_entry(info, name) != NULL);
376     if (*fileExists)  /* use time of MVL itself in the physical filesystem. */
377         retval = info->last_mod_time;
378
379     return(retval);
380 } /* MVL_getLastModTime */
381
382
383 static fvoid *MVL_openRead(dvoid *opaque, const char *fnm, int *fileExists)
384 {
385     MVLinfo *info = ((MVLinfo *) opaque);
386     MVLfileinfo *finfo;
387     MVLentry *entry;
388
389     entry = mvl_find_entry(info, fnm);
390     *fileExists = (entry != NULL);
391     BAIL_IF_MACRO(entry == NULL, NULL, NULL);
392
393     finfo = (MVLfileinfo *) allocator.Malloc(sizeof (MVLfileinfo));
394     BAIL_IF_MACRO(finfo == NULL, ERR_OUT_OF_MEMORY, NULL);
395
396     finfo->handle = __PHYSFS_platformOpenRead(info->filename);
397     if ( (finfo->handle == NULL) ||
398          (!__PHYSFS_platformSeek(finfo->handle, entry->startPos)) )
399     {
400         allocator.Free(finfo);
401         return(NULL);
402     } /* if */
403
404     finfo->curPos = 0;
405     finfo->entry = entry;
406     return(finfo);
407 } /* MVL_openRead */
408
409
410 static fvoid *MVL_openWrite(dvoid *opaque, const char *name)
411 {
412     BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
413 } /* MVL_openWrite */
414
415
416 static fvoid *MVL_openAppend(dvoid *opaque, const char *name)
417 {
418     BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
419 } /* MVL_openAppend */
420
421
422 static int MVL_remove(dvoid *opaque, const char *name)
423 {
424     BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
425 } /* MVL_remove */
426
427
428 static int MVL_mkdir(dvoid *opaque, const char *name)
429 {
430     BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
431 } /* MVL_mkdir */
432
433
434 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_MVL =
435 {
436     "MVL",
437     MVL_ARCHIVE_DESCRIPTION,
438     "Bradley Bell <btb@icculus.org>",
439     "http://icculus.org/physfs/",
440 };
441
442
443 const PHYSFS_Archiver __PHYSFS_Archiver_MVL =
444 {
445     &__PHYSFS_ArchiveInfo_MVL,
446     MVL_isArchive,          /* isArchive() method      */
447     MVL_openArchive,        /* openArchive() method    */
448     MVL_enumerateFiles,     /* enumerateFiles() method */
449     MVL_exists,             /* exists() method         */
450     MVL_isDirectory,        /* isDirectory() method    */
451     MVL_isSymLink,          /* isSymLink() method      */
452     MVL_getLastModTime,     /* getLastModTime() method */
453     MVL_openRead,           /* openRead() method       */
454     MVL_openWrite,          /* openWrite() method      */
455     MVL_openAppend,         /* openAppend() method     */
456     MVL_remove,             /* remove() method         */
457     MVL_mkdir,              /* mkdir() method          */
458     MVL_dirClose,           /* dirClose() method       */
459     MVL_read,               /* read() method           */
460     MVL_write,              /* write() method          */
461     MVL_eof,                /* eof() method            */
462     MVL_tell,               /* tell() method           */
463     MVL_seek,               /* seek() method           */
464     MVL_fileLength,         /* fileLength() method     */
465     MVL_fileClose           /* fileClose() method      */
466 };
467
468 #endif  /* defined PHYSFS_SUPPORTS_MVL */
469
470 /* end of mvl.c ... */
471