diff --git a/src/physfs.c b/src/physfs.c index abc7952..e617f11 100644 --- a/src/physfs.c +++ b/src/physfs.c @@ -451,6 +451,127 @@ createMemoryIo_failed: } /* __PHYSFS_createMemoryIo */ +/* PHYSFS_Io implementation for i/o to a PHYSFS_File... */ + +static PHYSFS_sint64 handleIo_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len) +{ + return PHYSFS_readBytes((PHYSFS_File *) io->opaque, buf, len); +} /* handleIo_read */ + +static PHYSFS_sint64 handleIo_write(PHYSFS_Io *io, const void *buffer, + PHYSFS_uint64 len) +{ + return PHYSFS_writeBytes((PHYSFS_File *) io->opaque, buffer, len); +} /* handleIo_write */ + +static int handleIo_seek(PHYSFS_Io *io, PHYSFS_uint64 offset) +{ + return PHYSFS_seek((PHYSFS_File *) io->opaque, offset); +} /* handleIo_seek */ + +static PHYSFS_sint64 handleIo_tell(PHYSFS_Io *io) +{ + return PHYSFS_tell((PHYSFS_File *) io->opaque); +} /* handleIo_tell */ + +static PHYSFS_sint64 handleIo_length(PHYSFS_Io *io) +{ + return PHYSFS_fileLength((PHYSFS_File *) io->opaque); +} /* handleIo_length */ + +static PHYSFS_Io *handleIo_duplicate(PHYSFS_Io *io) +{ + /* + * There's no duplicate at the PHYSFS_File level, so we break the + * abstraction. We're allowed to: we're physfs.c! + */ + FileHandle *origfh = (FileHandle *) io->opaque; + FileHandle *newfh = (FileHandle *) allocator.Malloc(sizeof (FileHandle)); + PHYSFS_Io *retval = NULL; + + GOTO_IF_MACRO(newfh == NULL, ERR_OUT_OF_MEMORY, handleIo_dupe_failed); + memset(newfh, '\0', sizeof (*newfh)); + + retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + GOTO_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, handleIo_dupe_failed); + +#if 0 /* we don't buffer the duplicate, at least not at the moment. */ + if (origfh->buffer != NULL) + { + newfh->buffer = (PHYSFS_uint8 *) allocator.Malloc(origfh->bufsize); + GOTO_IF_MACRO(!newfh->buffer, ERR_OUT_OF_MEMORY, handleIo_dupe_failed); + newfh->bufsize = origfh->bufsize; + } /* if */ +#endif + + newfh->io = origfh->io->duplicate(origfh->io); + GOTO_IF_MACRO(newfh->io == NULL, NULL, handleIo_dupe_failed); + + newfh->forReading = origfh->forReading; + newfh->dirHandle = origfh->dirHandle; + + __PHYSFS_platformGrabMutex(stateLock); + if (newfh->forReading) + { + newfh->next = openReadList; + openReadList = newfh; + } /* if */ + else + { + newfh->next = openWriteList; + openWriteList = newfh; + } /* else */ + __PHYSFS_platformReleaseMutex(stateLock); + + memcpy(retval, io, sizeof (PHYSFS_Io)); + retval->opaque = newfh; + return retval; + +handleIo_dupe_failed: + if (newfh) + { + if (newfh->io != NULL) newfh->io->destroy(newfh->io); + if (newfh->buffer != NULL) allocator.Free(newfh->buffer); + allocator.Free(newfh); + } /* if */ + + return NULL; +} /* handleIo_duplicate */ + +static int handleIo_flush(PHYSFS_Io *io) +{ + return PHYSFS_flush((PHYSFS_File *) io->opaque); +} /* handleIo_flush */ + +static void handleIo_destroy(PHYSFS_Io *io) +{ + if (io->opaque != NULL) + PHYSFS_close((PHYSFS_File *) io->opaque); + allocator.Free(io); +} /* handleIo_destroy */ + +static const PHYSFS_Io __PHYSFS_handleIoInterface = +{ + handleIo_read, + handleIo_write, + handleIo_seek, + handleIo_tell, + handleIo_length, + handleIo_duplicate, + handleIo_flush, + handleIo_destroy, + NULL +}; + +static PHYSFS_Io *__PHYSFS_createHandleIo(PHYSFS_File *f) +{ + PHYSFS_Io *io = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + BAIL_IF_MACRO(io == NULL, ERR_OUT_OF_MEMORY, NULL); + memcpy(io, &__PHYSFS_handleIoInterface, sizeof (*io)); + io->opaque = f; + return io; +} /* __PHYSFS_createHandleIo */ + /* functions ... */ @@ -1356,6 +1477,28 @@ int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len, void (*del)(void *), } /* PHYSFS_mountMemory */ +int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname, + const char *mountPoint, int appendToPath) +{ + int retval = 0; + PHYSFS_Io *io = NULL; + + BAIL_IF_MACRO(file == NULL, ERR_INVALID_ARGUMENT, 0); + + io = __PHYSFS_createHandleIo(file); + BAIL_IF_MACRO(io == NULL, NULL, 0); + retval = doMount(io, fname, mountPoint, appendToPath); + if (!retval) + { + /* docs say not to destruct in case of failure, so cheat. */ + io->opaque = NULL; + io->destroy(io); + } /* if */ + + return retval; +} /* PHYSFS_mountHandle */ + + int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath) { BAIL_IF_MACRO(newDir == NULL, ERR_INVALID_ARGUMENT, 0); diff --git a/src/physfs.h b/src/physfs.h index afc9bef..6804daf 100644 --- a/src/physfs.h +++ b/src/physfs.h @@ -2989,6 +2989,60 @@ PHYSFS_DECL int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len, const char *mountPoint, int appendToPath); +/** + * \fn int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname, const char *mountPoint, int appendToPath) + * \brief Add an archive, contained in a PHYSFS_File handle, to the search path. + * + * \warning Unless you have some special, low-level need, you should be using + * PHYSFS_mount() instead of this. + * + * \warning Archives-in-archives may be very slow! While a PHYSFS_File can + * seek even when the data is compressed, it may do so by rewinding + * to the start and decompressing everything before the seek point. + * Normal archive usage may do a lot of seeking behind the scenes. + * As such, you might find normal archive usage extremely painful + * if mounted this way. Plan accordingly: if you, say, have a + * self-extracting .zip file, and want to mount something in it, + * compress the contents of the inner archive and make sure the outer + * .zip file doesn't compress the inner archive too. + * + * This function operates just like PHYSFS_mount(), but takes a PHYSFS_File + * handle instead of a pathname. This handle contains all the data of the + * archive, and is used instead of a real file in the physical filesystem. + * The PHYSFS_File may be backed by a real file in the physical filesystem, + * but isn't necessarily. The most popular use for this is likely to mount + * archives stored inside other archives. + * + * (filename) is only used here to optimize archiver selection (if you name it + * XXXXX.zip, we might try the ZIP archiver first, for example). It doesn't + * need to refer to a real file at all, and can even be NULL. If the filename + * isn't helpful, the system will try every archiver until one works or none + * of them do. + * + * (file) must remain until the archive is unmounted. When the archive is + * unmounted, the system will call PHYSFS_close(file). If you need this + * handle to survive, you will have to wrap this in a PHYSFS_Io and use + * PHYSFS_mountIo() instead. + * + * If this function fails, PHYSFS_close(file) is not called. + * + * \param file The PHYSFS_File handle containing archive data. + * \param fname Filename that can represent this stream. Can be NULL. + * \param mountPoint Location in the interpolated tree that this archive + * will be "mounted", in platform-independent notation. + * NULL or "" is equivalent to "/". + * \param appendToPath nonzero to append to search path, zero to prepend. + * \return nonzero if added to path, zero on failure (bogus archive, etc). + * Specifics of the error can be gleaned from + * PHYSFS_getLastError(). + * + * \sa PHYSFS_unmount + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_getMountPoint + */ +PHYSFS_DECL int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname, + const char *mountPoint, int appendToPath); + /* Everything above this line is part of the PhysicsFS 2.1 API. */ diff --git a/test/test_physfs.c b/test/test_physfs.c index b0902a1..6b1121a 100644 --- a/test/test_physfs.c +++ b/test/test_physfs.c @@ -134,8 +134,14 @@ static void freeBuf(void *buf) free(buf); } /* freeBuf */ +typedef enum +{ + MNTTYPE_PATH, + MNTTYPE_MEMORY, + MNTTYPE_HANDLE +} MountType; -static int cmd_mount_internal(char *args, const int fromMem) +static int cmd_mount_internal(char *args, const MountType mnttype) { char *ptr; char *mntpoint = NULL; @@ -180,9 +186,24 @@ static int cmd_mount_internal(char *args, const int fromMem) /*printf("[%s], [%s], [%d]\n", args, mntpoint, appending);*/ - if (!fromMem) + if (mnttype == MNTTYPE_PATH) rc = PHYSFS_mount(args, mntpoint, appending); - else + + else if (mnttype == MNTTYPE_HANDLE) + { + PHYSFS_File *f = PHYSFS_openRead(args); + if (f == NULL) + { + printf("PHYSFS_openRead('%s') failed. reason: %s.\n", args, PHYSFS_getLastError()); + return 1; + } /* if */ + + rc = PHYSFS_mountHandle(f, args, mntpoint, appending); + if (!rc) + PHYSFS_close(f); + } /* else if */ + + else if (mnttype == MNTTYPE_MEMORY) { FILE *in = fopen(args, "rb"); void *buf = NULL; @@ -233,16 +254,22 @@ static int cmd_mount_internal(char *args, const int fromMem) static int cmd_mount(char *args) { - return cmd_mount_internal(args, 0); + return cmd_mount_internal(args, MNTTYPE_PATH); } /* cmd_mount */ static int cmd_mount_mem(char *args) { - return cmd_mount_internal(args, 1); + return cmd_mount_internal(args, MNTTYPE_MEMORY); } /* cmd_mount_mem */ +static int cmd_mount_handle(char *args) +{ + return cmd_mount_internal(args, MNTTYPE_HANDLE); +} /* cmd_mount_handle */ + + static int cmd_removearchive(char *args) { if (*args == '\"') @@ -1086,6 +1113,7 @@ static const command_info commands[] = { "addarchive", cmd_addarchive, 2, " " }, { "mount", cmd_mount, 3, " " }, { "mountmem", cmd_mount_mem, 3, " " }, + { "mounthandle", cmd_mount_handle, 3, " " }, { "removearchive", cmd_removearchive, 1, "" }, { "unmount", cmd_removearchive, 1, "" }, { "enumerate", cmd_enumerate, 1, "" },