From bad1d8f06191028bc05da626e7886dab28ec3ea4 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 9 Aug 2002 19:45:54 +0000 Subject: [PATCH] Initial add. --- archivers/qpak.c | 739 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 739 insertions(+) create mode 100644 archivers/qpak.c diff --git a/archivers/qpak.c b/archivers/qpak.c new file mode 100644 index 00000000..7fedb761 --- /dev/null +++ b/archivers/qpak.c @@ -0,0 +1,739 @@ +/* + * Quake PAK support routines for PhysicsFS. + * + * This driver handles id Software Quake PAK files. + * + * Please see the file LICENSE in the source's root directory. + * + * This file written by Ed Sinjiashvili. + */ + +#if HAVE_CONFIG_H +# include +#endif + +#if (defined PHYSFS_SUPPORTS_QPAK) +#include +#include +#include +#include +#include +#include +#include "physfs.h" + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#define QPAK_MAXDIRLEN 60 + +typedef struct +{ + char name[56]; + PHYSFS_uint32 offset; + PHYSFS_uint32 size; +} QPAKentry; + +typedef struct tagQPAKdirentry +{ + char *name; + QPAKentry *entry; + struct tagQPAKdirentry *next; +} QPAKdirentry; + +typedef struct QPAKDirectory +{ + char name[QPAK_MAXDIRLEN]; + + struct QPAKDirectory *dirs, *next; + + QPAKdirentry *files; +} QPAKdirectory; + +typedef struct +{ + void *handle; + char *filename; + PHYSFS_uint32 dirOffset; + PHYSFS_uint32 totalEntries; + QPAKentry *entries; + QPAKdirectory *root; +} QPAKinfo; + +typedef struct +{ + void *handle; + QPAKentry *entry; + PHYSFS_sint64 curPos; +} QPAKfileinfo; + + +static int QPAK_isArchive(const char *filename, int forWriting); +static DirHandle *QPAK_openArchive(const char *name, int forWriting); +static void QPAK_dirClose(DirHandle *h); +static LinkedStringList *QPAK_enumerateFiles(DirHandle *h, const char *dirname, + int omitSymLinks); +static int QPAK_exists(DirHandle *h, const char *name); +static int QPAK_isDirectory(DirHandle *h, const char *name); +static int QPAK_isSymLink(DirHandle *h, const char *name); +static PHYSFS_sint64 QPAK_getLastModTime(DirHandle *h, const char *name); +static FileHandle *QPAK_openRead(DirHandle *h, const char *name); + + +static PHYSFS_sint64 QPAK_read(FileHandle *handle, void *buffer, + PHYSFS_uint32 objSize, PHYSFS_uint32 objCount); +static int QPAK_eof(FileHandle *handle); +static PHYSFS_sint64 QPAK_tell(FileHandle *handle); +static int QPAK_seek(FileHandle *handle, PHYSFS_uint64 offset); +static PHYSFS_sint64 QPAK_fileLength(FileHandle *handle); +static int QPAK_fileClose(FileHandle *handle); + + +const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_QPAK = +{ + "PAK", + "Quake PAK file format", + "Ed Sinjiashvili ", + "http://icculus.org/physfs/", +}; + +static const FileFunctions __PHYSFS_FileFunctions_QPAK = +{ + QPAK_read, /* read() method */ + NULL, /* write() method */ + QPAK_eof, /* eof() method */ + QPAK_tell, /* tell() method */ + QPAK_seek, /* seek() method */ + QPAK_fileLength, /* fileLength() method */ + QPAK_fileClose /* fileClose() method */ +}; + +const DirFunctions __PHYSFS_DirFunctions_QPAK = +{ + &__PHYSFS_ArchiveInfo_QPAK, + QPAK_isArchive, /* isArchive() method */ + QPAK_openArchive, /* openArchive() method */ + QPAK_enumerateFiles, /* enumerateFiles() method */ + QPAK_exists, /* exists() method */ + QPAK_isDirectory, /* isDirectory() method */ + QPAK_isSymLink, /* isSymLink() method */ + QPAK_getLastModTime, /* getLastModTime() method */ + QPAK_openRead, /* openRead() method */ + NULL, /* openWrite() method */ + NULL, /* openAppend() method */ + NULL, /* remove() method */ + NULL, /* mkdir() method */ + QPAK_dirClose /* dirClose() method */ +}; + + +#define QPAK_MAGIC 0x4B434150 /* look like "PACK" in ascii. */ + + +/* + * Read an unsigned 32-bit int and swap to native byte order. + */ +static int readui32(void *in, PHYSFS_uint32 *val) +{ + PHYSFS_uint32 v; + BAIL_IF_MACRO(__PHYSFS_platformRead(in, &v, sizeof (v), 1) != 1, NULL, 0); + *val = PHYSFS_swapULE32(v); + return(1); +} /* readui32 */ + + +static int openQPak(const char *filename, int forWriting, void **fileHandle) +{ + PHYSFS_uint32 sig; + + *fileHandle = NULL; + BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, 0); + + *fileHandle = __PHYSFS_platformOpenRead(filename); + BAIL_IF_MACRO(*fileHandle == NULL, NULL, 0); + + if (!readui32(*fileHandle, &sig)) + goto openPak_failed; + + if (sig == QPAK_MAGIC) + { + __PHYSFS_setError(ERR_UNSUPPORTED_ARCHIVE); + goto openPak_failed; + } /* if */ + + return(1); + +openPak_failed: + if (*fileHandle != NULL) + __PHYSFS_platformClose(*fileHandle); + + *fileHandle = NULL; + return(0); +} /* openQPak */ + + +static int QPAK_isArchive(const char *filename, int forWriting) +{ + void *fileHandle; + int retval = openQPak(filename, forWriting, &fileHandle); + + if (fileHandle != NULL) + __PHYSFS_platformClose(fileHandle); + + return(retval); +} /* QPAK_isArchive */ + + +static void qpak_insertion_sort(QPAKentry *a, PHYSFS_uint32 lo, PHYSFS_uint32 hi) +{ + PHYSFS_uint32 i; + PHYSFS_uint32 j; + QPAKentry tmp; + + for (i = lo + 1; i <= hi; i++) + { + memcpy(&tmp, &a[i], sizeof (QPAKentry)); + j = i; + while ((j > lo) && (strcmp(a[j - 1].name, tmp.name) > 0)) + { + memcpy(&a[j], &a[j - 1], sizeof (QPAKentry)); + j--; + } /* while */ + memcpy(&a[j], &tmp, sizeof (QPAKentry)); + } /* for */ +} /* qpak_insertion_sort */ + + +static void qpak_sort_entries(QPAKentry *entries, PHYSFS_uint32 max) +{ + qpak_insertion_sort(entries, 0, max - 1); +} /* qpak_sort_entries */ + + +static int qpak_loadEntries(void *fh, int dirOffset, int numEntries, + QPAKentry *entries) +{ + PHYSFS_uint32 i; + + BAIL_IF_MACRO(__PHYSFS_platformSeek(fh, dirOffset) == 0, NULL, 0); + + for (i = 0; i < numEntries; i++, entries++) + { + PHYSFS_sint64 r = __PHYSFS_platformRead(fh, entries->name, 56, 1); + BAIL_IF_MACRO(r == 0, NULL, 0); + BAIL_IF_MACRO(!readui32(fh, &entries->offset), NULL, 0); + BAIL_IF_MACRO(!readui32(fh, &entries->size), NULL, 0); + } /* for */ + + return(1); +} /* qpak_loadEntries */ + + +static QPAKdirentry *qpak_newDirentry(char *name, QPAKentry *entry) +{ + QPAKdirentry *retval = (QPAKdirentry *) malloc(sizeof (QPAKdirentry)); + BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, 0); + + retval->name = name; + retval->entry = entry; + retval->next = NULL; + + return(retval); +} /* qpak_newDirentry */ + + +static void qpak_deleteDirentry(QPAKdirentry *e) +{ + while (e != NULL) + { + QPAKdirentry *next = e->next; + free(e); + e = next; + } /* while */ +} /* qpak_deleteDirentry */ + + +static QPAKdirectory *qpak_newDirectory(char *name) +{ + QPAKdirectory *dir = (QPAKdirectory *) malloc(sizeof (QPAKdirectory)); + BAIL_IF_MACRO(dir == NULL, ERR_OUT_OF_MEMORY, 0); + + strcpy(dir->name, name); + dir->dirs = NULL; + dir->next = NULL; + dir->files = NULL; + + return dir; +} /* qpak_newDirectory */ + + +static void qpak_deleteDirectory(QPAKdirectory *d) +{ + while (d != NULL) + { + QPAKdirectory *next = d->next; + qpak_deleteDirentry(d->files); + qpak_deleteDirectory(d->dirs); + free(d); + d = next; + } /* while */ +} /* qpak_deleteDirectory */ + + +static int qpak_addFile(QPAKdirectory *dir, char *name, QPAKentry *entry) +{ + QPAKdirentry *file = qpak_newDirentry(name, entry); + if (file == NULL) + return(0); + + /* !!! FIXME: Traversing a linkedlist gets slower with each added file. */ + if (dir->files == NULL) + { + dir->files = file; + } /* if */ + else + { + QPAKdirentry *tail = dir->files; + while (tail->next != NULL) + tail = tail->next; + + tail->next = file; + } /* else */ + + return(1); +} /* qpak_addFile */ + + +static QPAKdirectory *qpak_findDirectory(QPAKdirectory *root, const char *name) +{ + char *p = strchr(name, '/'); + + if (p == NULL) + { + QPAKdirectory *thisDir = root->dirs; + while (thisDir != NULL) + { + if (strcmp(thisDir->name, name) == 0) + return(thisDir); + thisDir = thisDir->next; + } /* while */ + } /* if */ + + else + { + char temp[QPAK_MAXDIRLEN]; + QPAKdirectory *thisDir = root->dirs; + + strncpy (temp, name, p - name); + temp[p - name] = '\0'; + + while (thisDir != NULL) + { + if (strcmp(thisDir->name, temp) == 0) + return(qpak_findDirectory(thisDir, p + 1)); + thisDir = thisDir->next; + } /* while */ + } /* else */ + + return(0); +} /* qpak_findDirectory */ + + +static QPAKdirectory *qpak_addDir(QPAKdirectory *dir, char *name) +{ + QPAKdirectory *newDir = qpak_findDirectory(dir, name); + if (newDir != 0) + return(newDir); + + newDir = qpak_newDirectory(name); + if (newDir == 0) + return 0; + + if (dir->dirs == NULL) + { + dir->dirs = newDir; + } /* if */ + + else + { + QPAKdirectory *tail = dir->dirs; + while (tail->next != NULL) + tail = tail->next; + + tail->next = newDir; + } /* else */ + + return(newDir); +} /* qpak_addDir */ + + +static int qpak_addEntry(QPAKdirectory *dir, char *name, QPAKentry *entry) +{ + char tempName[QPAK_MAXDIRLEN]; + QPAKdirectory *child; + char *p = strchr(name, '/'); + if (p == NULL) + return(qpak_addFile(dir, name, entry)); + + strncpy(tempName, name, p - name); + tempName[p - name] = '\0'; + + child = qpak_addDir(dir, tempName); + return(qpak_addEntry(child, p + 1, entry)); +} /* qpak_addEntry */ + + +static QPAKentry *qpak_findEntry(QPAKdirectory *root, const char *name) +{ + QPAKdirectory *dir = NULL; + QPAKdirentry *thisFile = NULL; + const char *t = strrchr (name, '/'); + + if (t == NULL) + { + dir = root; + t = name; + } /* if */ + + else + { + char temp[QPAK_MAXDIRLEN]; + strncpy(temp, name, t - name); + temp[t - name] = '\0'; + dir = qpak_findDirectory(root, temp); + t++; + } /* else */ + + if (dir == NULL) + return(0); + + thisFile = dir->files; + + while (thisFile != NULL) + { + if (strcmp(thisFile->name, t) == 0) + return(thisFile->entry); + + thisFile = thisFile->next; + } /* while */ + + return(0); +} /* qpak_findEntry */ + + +static int qpak_populateDirectories(QPAKentry *entries, int numEntries, + QPAKdirectory *root) +{ + PHYSFS_uint32 i; + QPAKentry *entry = entries; + + for (i = 0; i < numEntries; i++, entry++) + { + if (qpak_addEntry(root, entry->name, entry) == 0) + return(0); + } /* for */ + + return(1); +} /* qpak_populateDirectories */ + + +static void qpak_deletePakInfo (QPAKinfo *pakInfo) +{ + if (pakInfo->filename != NULL) + free(pakInfo->filename); + + if (pakInfo->entries != NULL) + free(pakInfo->entries); + + qpak_deleteDirectory(pakInfo->root); + + free(pakInfo); +} /* qpak_deletePakInfo */ + + +static DirHandle *QPAK_openArchive(const char *name, int forWriting) +{ + void *fh = NULL; + PHYSFS_uint32 dirOffset, dirLength; + QPAKinfo *pi; + DirHandle *retval; + + retval = (DirHandle *) malloc(sizeof (DirHandle)); + BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL); + + pi = (QPAKinfo *) malloc(sizeof (QPAKinfo)); + if (pi == NULL) + { + free(retval); + BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL); + } /* if */ + + retval->opaque = pi; + + pi->filename = (char *) malloc(strlen(name) + 1); + if (pi->filename == NULL) + { + __PHYSFS_setError(ERR_OUT_OF_MEMORY); + goto QPAK_openArchive_failed; + } /* if */ + + if (!openQPak(name, forWriting, &fh)) + goto QPAK_openArchive_failed; + + if (!readui32(fh, &dirOffset)) + goto QPAK_openArchive_failed; + + if (!readui32(fh, &dirLength)) + goto QPAK_openArchive_failed; + + if (__PHYSFS_platformFileLength(fh) < dirOffset + dirLength) + goto QPAK_openArchive_failed; + + strcpy(pi->filename, name); + pi->handle = fh; + pi->dirOffset = dirOffset; + pi->totalEntries = dirLength / 64; + + pi->entries = (QPAKentry *) malloc(pi->totalEntries * sizeof (QPAKentry)); + if (pi->entries == NULL) + { + __PHYSFS_setError(ERR_OUT_OF_MEMORY); + goto QPAK_openArchive_failed; + } /* if */ + + if (qpak_loadEntries(fh, dirOffset, pi->totalEntries, pi->entries) == 0) + goto QPAK_openArchive_failed; + + qpak_sort_entries(pi->entries, pi->totalEntries); + + pi->root = qpak_newDirectory(""); + if (pi->root == NULL) + goto QPAK_openArchive_failed; + + if (qpak_populateDirectories(pi->entries, pi->totalEntries, pi->root) == 0) + goto QPAK_openArchive_failed; + + retval->funcs = &__PHYSFS_DirFunctions_QPAK; + return(retval); + +QPAK_openArchive_failed: + if (retval != NULL) + { + if (retval->opaque != NULL) + qpak_deletePakInfo((QPAKinfo *) retval->opaque); + + free(retval); + } /* if */ + + if (fh != NULL) + __PHYSFS_platformClose(fh); + + return(0); +} /* QPAK_openArchive */ + + +static void QPAK_dirClose(DirHandle *dirHandle) +{ + QPAKinfo *info = (QPAKinfo *) dirHandle->opaque; + __PHYSFS_platformClose(info->handle); + free(info->filename); + free(info); + free(dirHandle); +} /* QPAK_dirClose */ + + +static LinkedStringList *QPAK_enumerateFiles(DirHandle *h, const char *dirname, + int omitSymLinks) +{ + LinkedStringList *retval = NULL, *p = NULL; + QPAKdirectory *dir; + QPAKinfo *info = (QPAKinfo *) h->opaque; + + if ((dirname == NULL) || (*dirname == '\0')) + dir = info->root; + else + dir = qpak_findDirectory(info->root, dirname); + + if (dir != NULL) + { + QPAKdirectory *child = dir->dirs; + QPAKdirentry *file = dir->files; + + while (child != NULL) + { + retval = __PHYSFS_addToLinkedStringList(retval, &p, child->name, -1); + child = child->next; + } /* while */ + + while (file != NULL) + { + retval = __PHYSFS_addToLinkedStringList(retval, &p, file->name, -1); + file = file->next; + } /* while */ + } /* if */ + + return(retval); +} /* QPAK_enumerateFiles */ + + +static int QPAK_exists(DirHandle *h, const char *name) +{ + QPAKinfo *driver = (QPAKinfo *) h->opaque; + + if ((name == NULL) || (*name == '\0')) + return(0); + + if (qpak_findDirectory(driver->root, name) != 0) + return(1); + + if (qpak_findEntry(driver->root, name) != 0) + return(1); + + return(0); +} /* QPAK_exists */ + + +static int QPAK_isDirectory(DirHandle *h, const char *name) +{ + QPAKinfo *info = (QPAKinfo *) h->opaque; + return(qpak_findDirectory(info->root, name) != 0); +} /* QPAK_isDirectory */ + + +static int QPAK_isSymLink(DirHandle *h, const char *name) +{ + return(0); /* we don't support symlinks for now */ +} /* QPAK_isSymlink */ + + +static PHYSFS_sint64 QPAK_getLastModTime(DirHandle *h, const char *name) +{ + return(__PHYSFS_platformGetLastModTime(((QPAKinfo *) h->opaque)->filename)); +} /* QPAK_getLastModTime */ + + +static void *qpak_getFileHandle(const char *name, QPAKentry *entry) +{ + void *retval = __PHYSFS_platformOpenRead(name); + if (retval == NULL) + return(NULL); + + if (!__PHYSFS_platformSeek(retval, entry->offset)) + { + __PHYSFS_platformClose(retval); + return(NULL); + } /* if */ + + return(retval); +} /* qpak_getFileHandle */ + + +static FileHandle *QPAK_openRead(DirHandle *h, const char *name) +{ + QPAKinfo *driver = (QPAKinfo *) h->opaque; + QPAKentry *entry = qpak_findEntry(driver->root, name); + QPAKfileinfo *fileDriver = 0; + FileHandle *result = 0; + + if (entry == NULL) + return(NULL); + + fileDriver = (QPAKfileinfo *) malloc(sizeof (QPAKfileinfo)); + BAIL_IF_MACRO(fileDriver == NULL, ERR_OUT_OF_MEMORY, NULL); + + fileDriver->handle = qpak_getFileHandle(driver->filename, entry); + if (fileDriver->handle == NULL) + { + free(fileDriver); + return(NULL); + } /* if */ + + fileDriver->entry = entry; + fileDriver->curPos = 0; + + result = (FileHandle *)malloc(sizeof (FileHandle)); + if (result == NULL) + { + __PHYSFS_platformClose(fileDriver->handle); + free(fileDriver); + BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL); + } /* if */ + + result->opaque = fileDriver; + result->dirHandle = h; + result->funcs = &__PHYSFS_FileFunctions_QPAK; + return(result); +} /* QPAK_openRead */ + + +static PHYSFS_sint64 QPAK_read(FileHandle *handle, void *buffer, + PHYSFS_uint32 objSize, PHYSFS_uint32 objCount) +{ + QPAKfileinfo *finfo = (QPAKfileinfo *) (handle->opaque); + QPAKentry *entry = finfo->entry; + PHYSFS_uint64 bytesLeft = entry->size - finfo->curPos; + PHYSFS_uint64 objsLeft = (bytesLeft / objSize); + PHYSFS_sint64 rc; + + if (objsLeft < objCount) + objCount = (PHYSFS_uint32) objsLeft; + + rc = __PHYSFS_platformRead(finfo->handle, buffer, objSize, objCount); + if (rc > 0) + finfo->curPos += (rc * objSize); + + return(rc); +} /* QPAK_read */ + + +static int QPAK_eof(FileHandle *handle) +{ + QPAKfileinfo *finfo = (QPAKfileinfo *) (handle->opaque); + QPAKentry *entry = finfo->entry; + + return(finfo->curPos >= (PHYSFS_sint64) entry->size); +} /* QPAK_eof */ + + +static PHYSFS_sint64 QPAK_tell(FileHandle *handle) +{ + return(((QPAKfileinfo *) handle->opaque)->curPos); +} /* QPAK_tell */ + + +static int QPAK_seek(FileHandle *handle, PHYSFS_uint64 offset) +{ + QPAKfileinfo *finfo = (QPAKfileinfo *) handle->opaque; + QPAKentry *entry = finfo->entry; + PHYSFS_uint64 newPos = entry->offset + offset; + int rc; + + BAIL_IF_MACRO(offset < 0, ERR_INVALID_ARGUMENT, 0); + BAIL_IF_MACRO(newPos > entry->offset + entry->size, ERR_PAST_EOF, 0); + rc = __PHYSFS_platformSeek(finfo->handle, newPos); + if (rc) + finfo->curPos = offset; + + return(rc); +} /* QPAK_seek */ + + +static PHYSFS_sint64 QPAK_fileLength(FileHandle *handle) +{ + return ((QPAKfileinfo *) handle->opaque)->entry->size; +} /* QPAK_fileLength */ + + +static int QPAK_fileClose(FileHandle *handle) +{ + QPAKfileinfo *finfo = (QPAKfileinfo *) handle->opaque; + BAIL_IF_MACRO(!__PHYSFS_platformClose(finfo->handle), NULL, 0); + free(finfo); + free(handle); + return(1); +} /* QPAK_fileClose */ + +#endif /* defined PHYSFS_SUPPORTS_QPAK */ + +/* end of qpak.c ... */ + +