2 * GRP support routines for PhysicsFS.
4 * This driver handles BUILD engine archives ("groupfiles"). This format
5 * (but not this driver) was put together by Ken Silverman.
7 * The format is simple enough. In Ken's words:
9 * What's the .GRP file format?
11 * The ".grp" file format is just a collection of a lot of files stored
12 * into 1 big one. I tried to make the format as simple as possible: The
13 * first 12 bytes contains my name, "KenSilverman". The next 4 bytes is
14 * the number of files that were compacted into the group file. Then for
15 * each file, there is a 16 byte structure, where the first 12 bytes are
16 * the filename, and the last 4 bytes are the file's size. The rest of
17 * the group file is just the raw data packed one after the other in the
18 * same order as the list of files.
20 * (That info is from http://www.advsys.net/ken/build.htm ...)
22 * Please see the file LICENSE.txt in the source's root directory.
24 * This file written by Ryan C. Gordon.
27 #if (defined PHYSFS_SUPPORTS_GRP)
34 #define __PHYSICSFS_INTERNAL__
35 #include "physfs_internal.h"
40 PHYSFS_uint32 startPos;
47 PHYSFS_sint64 last_mod_time;
48 PHYSFS_uint32 entryCount;
60 static void GRP_dirClose(dvoid *opaque)
62 GRPinfo *info = ((GRPinfo *) opaque);
63 allocator.Free(info->filename);
64 allocator.Free(info->entries);
69 static PHYSFS_sint64 GRP_read(fvoid *opaque, void *buffer,
70 PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
72 GRPfileinfo *finfo = (GRPfileinfo *) opaque;
73 GRPentry *entry = finfo->entry;
74 PHYSFS_uint32 bytesLeft = entry->size - finfo->curPos;
75 PHYSFS_uint32 objsLeft = (bytesLeft / objSize);
78 if (objsLeft < objCount)
81 rc = __PHYSFS_platformRead(finfo->handle, buffer, objSize, objCount);
83 finfo->curPos += (PHYSFS_uint32) (rc * objSize);
89 static PHYSFS_sint64 GRP_write(fvoid *opaque, const void *buffer,
90 PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
92 BAIL_MACRO(ERR_NOT_SUPPORTED, -1);
96 static int GRP_eof(fvoid *opaque)
98 GRPfileinfo *finfo = (GRPfileinfo *) opaque;
99 GRPentry *entry = finfo->entry;
100 return(finfo->curPos >= entry->size);
104 static PHYSFS_sint64 GRP_tell(fvoid *opaque)
106 return(((GRPfileinfo *) opaque)->curPos);
110 static int GRP_seek(fvoid *opaque, PHYSFS_uint64 offset)
112 GRPfileinfo *finfo = (GRPfileinfo *) opaque;
113 GRPentry *entry = finfo->entry;
116 BAIL_IF_MACRO(offset < 0, ERR_INVALID_ARGUMENT, 0);
117 BAIL_IF_MACRO(offset >= entry->size, ERR_PAST_EOF, 0);
118 rc = __PHYSFS_platformSeek(finfo->handle, entry->startPos + offset);
120 finfo->curPos = (PHYSFS_uint32) offset;
126 static PHYSFS_sint64 GRP_fileLength(fvoid *opaque)
128 GRPfileinfo *finfo = (GRPfileinfo *) opaque;
129 return((PHYSFS_sint64) finfo->entry->size);
130 } /* GRP_fileLength */
133 static int GRP_fileClose(fvoid *opaque)
135 GRPfileinfo *finfo = (GRPfileinfo *) opaque;
136 BAIL_IF_MACRO(!__PHYSFS_platformClose(finfo->handle), NULL, 0);
137 allocator.Free(finfo);
139 } /* GRP_fileClose */
142 static int grp_open(const char *filename, int forWriting,
143 void **fh, PHYSFS_uint32 *count)
145 PHYSFS_uint8 buf[12];
148 BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, 0);
150 *fh = __PHYSFS_platformOpenRead(filename);
151 BAIL_IF_MACRO(*fh == NULL, NULL, 0);
153 if (__PHYSFS_platformRead(*fh, buf, 12, 1) != 1)
156 if (memcmp(buf, "KenSilverman", 12) != 0)
158 __PHYSFS_setError(ERR_UNSUPPORTED_ARCHIVE);
162 if (__PHYSFS_platformRead(*fh, count, sizeof (PHYSFS_uint32), 1) != 1)
165 *count = PHYSFS_swapULE32(*count);
171 __PHYSFS_platformClose(*fh);
179 static int GRP_isArchive(const char *filename, int forWriting)
182 PHYSFS_uint32 fileCount;
183 int retval = grp_open(filename, forWriting, &fh, &fileCount);
186 __PHYSFS_platformClose(fh);
189 } /* GRP_isArchive */
192 static int grp_entry_cmp(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
194 GRPentry *a = (GRPentry *) _a;
195 return(strcmp(a[one].name, a[two].name));
196 } /* grp_entry_cmp */
199 static void grp_entry_swap(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
202 GRPentry *first = &(((GRPentry *) _a)[one]);
203 GRPentry *second = &(((GRPentry *) _a)[two]);
204 memcpy(&tmp, first, sizeof (GRPentry));
205 memcpy(first, second, sizeof (GRPentry));
206 memcpy(second, &tmp, sizeof (GRPentry));
207 } /* grp_entry_swap */
210 static int grp_load_entries(const char *name, int forWriting, GRPinfo *info)
213 PHYSFS_uint32 fileCount;
214 PHYSFS_uint32 location = 16; /* sizeof sig. */
218 BAIL_IF_MACRO(!grp_open(name, forWriting, &fh, &fileCount), NULL, 0);
219 info->entryCount = fileCount;
220 info->entries = (GRPentry *) allocator.Malloc(sizeof(GRPentry)*fileCount);
221 if (info->entries == NULL)
223 __PHYSFS_platformClose(fh);
224 BAIL_MACRO(ERR_OUT_OF_MEMORY, 0);
227 location += (16 * fileCount);
229 for (entry = info->entries; fileCount > 0; fileCount--, entry++)
231 if (__PHYSFS_platformRead(fh, &entry->name, 12, 1) != 1)
233 __PHYSFS_platformClose(fh);
237 entry->name[12] = '\0'; /* name isn't null-terminated in file. */
238 if ((ptr = strchr(entry->name, ' ')) != NULL)
239 *ptr = '\0'; /* trim extra spaces. */
241 if (__PHYSFS_platformRead(fh, &entry->size, 4, 1) != 1)
243 __PHYSFS_platformClose(fh);
247 entry->size = PHYSFS_swapULE32(entry->size);
248 entry->startPos = location;
249 location += entry->size;
252 __PHYSFS_platformClose(fh);
254 __PHYSFS_sort(info->entries, info->entryCount,
255 grp_entry_cmp, grp_entry_swap);
257 } /* grp_load_entries */
260 static void *GRP_openArchive(const char *name, int forWriting)
262 PHYSFS_sint64 modtime = __PHYSFS_platformGetLastModTime(name);
263 GRPinfo *info = (GRPinfo *) allocator.Malloc(sizeof (GRPinfo));
265 BAIL_IF_MACRO(info == NULL, ERR_OUT_OF_MEMORY, 0);
267 memset(info, '\0', sizeof (GRPinfo));
268 info->filename = (char *) allocator.Malloc(strlen(name) + 1);
269 GOTO_IF_MACRO(!info->filename, ERR_OUT_OF_MEMORY, GRP_openArchive_failed);
271 if (!grp_load_entries(name, forWriting, info))
272 goto GRP_openArchive_failed;
274 strcpy(info->filename, name);
275 info->last_mod_time = modtime;
279 GRP_openArchive_failed:
282 if (info->filename != NULL)
283 allocator.Free(info->filename);
284 if (info->entries != NULL)
285 allocator.Free(info->entries);
286 allocator.Free(info);
290 } /* GRP_openArchive */
293 static void GRP_enumerateFiles(dvoid *opaque, const char *dname,
294 int omitSymLinks, PHYSFS_EnumFilesCallback cb,
295 const char *origdir, void *callbackdata)
297 /* no directories in GRP files. */
300 GRPinfo *info = (GRPinfo *) opaque;
301 GRPentry *entry = info->entries;
302 PHYSFS_uint32 max = info->entryCount;
305 for (i = 0; i < max; i++, entry++)
306 cb(callbackdata, origdir, entry->name);
308 } /* GRP_enumerateFiles */
311 static GRPentry *grp_find_entry(GRPinfo *info, const char *name)
313 char *ptr = strchr(name, '.');
314 GRPentry *a = info->entries;
315 PHYSFS_sint32 lo = 0;
316 PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1);
317 PHYSFS_sint32 middle;
321 * Rule out filenames to avoid unneeded processing...no dirs,
322 * big filenames, or extensions > 3 chars.
324 BAIL_IF_MACRO((ptr) && (strlen(ptr) > 4), ERR_NO_SUCH_FILE, NULL);
325 BAIL_IF_MACRO(strlen(name) > 12, ERR_NO_SUCH_FILE, NULL);
326 BAIL_IF_MACRO(strchr(name, '/') != NULL, ERR_NO_SUCH_FILE, NULL);
330 middle = lo + ((hi - lo) / 2);
331 rc = strcmp(name, a[middle].name);
332 if (rc == 0) /* found it! */
340 BAIL_MACRO(ERR_NO_SUCH_FILE, NULL);
341 } /* grp_find_entry */
344 static int GRP_exists(dvoid *opaque, const char *name)
346 return(grp_find_entry((GRPinfo *) opaque, name) != NULL);
350 static int GRP_isDirectory(dvoid *opaque, const char *name, int *fileExists)
352 *fileExists = GRP_exists(opaque, name);
353 return(0); /* never directories in a groupfile. */
354 } /* GRP_isDirectory */
357 static int GRP_isSymLink(dvoid *opaque, const char *name, int *fileExists)
359 *fileExists = GRP_exists(opaque, name);
360 return(0); /* never symlinks in a groupfile. */
361 } /* GRP_isSymLink */
364 static PHYSFS_sint64 GRP_getLastModTime(dvoid *opaque,
368 GRPinfo *info = (GRPinfo *) opaque;
369 PHYSFS_sint64 retval = -1;
371 *fileExists = (grp_find_entry(info, name) != NULL);
372 if (*fileExists) /* use time of GRP itself in the physical filesystem. */
373 retval = info->last_mod_time;
376 } /* GRP_getLastModTime */
379 static fvoid *GRP_openRead(dvoid *opaque, const char *fnm, int *fileExists)
381 GRPinfo *info = (GRPinfo *) opaque;
385 entry = grp_find_entry(info, fnm);
386 *fileExists = (entry != NULL);
387 BAIL_IF_MACRO(entry == NULL, NULL, NULL);
389 finfo = (GRPfileinfo *) allocator.Malloc(sizeof (GRPfileinfo));
390 BAIL_IF_MACRO(finfo == NULL, ERR_OUT_OF_MEMORY, NULL);
392 finfo->handle = __PHYSFS_platformOpenRead(info->filename);
393 if ( (finfo->handle == NULL) ||
394 (!__PHYSFS_platformSeek(finfo->handle, entry->startPos)) )
396 allocator.Free(finfo);
401 finfo->entry = entry;
406 static fvoid *GRP_openWrite(dvoid *opaque, const char *name)
408 BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
409 } /* GRP_openWrite */
412 static fvoid *GRP_openAppend(dvoid *opaque, const char *name)
414 BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
415 } /* GRP_openAppend */
418 static int GRP_remove(dvoid *opaque, const char *name)
420 BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
424 static int GRP_mkdir(dvoid *opaque, const char *name)
426 BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
430 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_GRP =
433 GRP_ARCHIVE_DESCRIPTION,
434 "Ryan C. Gordon <icculus@icculus.org>",
435 "http://icculus.org/physfs/",
439 const PHYSFS_Archiver __PHYSFS_Archiver_GRP =
441 &__PHYSFS_ArchiveInfo_GRP,
442 GRP_isArchive, /* isArchive() method */
443 GRP_openArchive, /* openArchive() method */
444 GRP_enumerateFiles, /* enumerateFiles() method */
445 GRP_exists, /* exists() method */
446 GRP_isDirectory, /* isDirectory() method */
447 GRP_isSymLink, /* isSymLink() method */
448 GRP_getLastModTime, /* getLastModTime() method */
449 GRP_openRead, /* openRead() method */
450 GRP_openWrite, /* openWrite() method */
451 GRP_openAppend, /* openAppend() method */
452 GRP_remove, /* remove() method */
453 GRP_mkdir, /* mkdir() method */
454 GRP_dirClose, /* dirClose() method */
455 GRP_read, /* read() method */
456 GRP_write, /* write() method */
457 GRP_eof, /* eof() method */
458 GRP_tell, /* tell() method */
459 GRP_seek, /* seek() method */
460 GRP_fileLength, /* fileLength() method */
461 GRP_fileClose /* fileClose() method */
464 #endif /* defined PHYSFS_SUPPORTS_GRP */
466 /* end of grp.c ... */