From d2272ba9bac93094d95faaf3d766022765906b3f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 17 Jul 2014 02:45:17 -0700 Subject: [PATCH] Support reading from ZIPs --- CMakeLists.txt | 12 +- src/gba/gba-thread.c | 8 ++ src/util/vfs-zip.c | 292 +++++++++++++++++++++++++++++++++++++++++++ src/util/vfs.h | 4 + 4 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 src/util/vfs-zip.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f92fd281..9877b8856 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,16 @@ include_directories(${CMAKE_SOURCE_DIR}/src) add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}") +include(FindPkgConfig) +pkg_search_module(LIBZIP libzip) +if(LIBZIP_FOUND) + include_directories(${LIBZIP_INCLUDE_DIRS}) + list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES}) + add_definitions(-DENABLE_LIBZIP) +else() + message(WARNING "Could not find libzip for archive support") +endif() + if(WIN32) add_definitions(-D_WIN32_WINNT=0x0600) set(OS_LIB "${OS_LIB};Ws2_32") @@ -59,7 +69,7 @@ endif() source_group("ARM debugger" FILES ${DEBUGGER_SRC}) add_library(${BINARY_NAME} SHARED ${ARM_SRC} ${GBA_SRC} ${DEBUGGER_SRC} ${RENDERER_SRC} ${UTIL_SRC} ${OS_SRC}) -target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB}) +target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB}) if(BUILD_SDL) add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/sdl ${CMAKE_BINARY_DIR}/sdl) diff --git a/src/gba/gba-thread.c b/src/gba/gba-thread.c index a63a53773..8ec00c405 100644 --- a/src/gba/gba-thread.c +++ b/src/gba/gba-thread.c @@ -173,6 +173,9 @@ void GBAMapOptionsToContext(struct StartupOptions* opts, struct GBAThread* threa threadContext->gamedir = VDirOpen(opts->fname); } else { threadContext->rom = VFileOpen(opts->fname, O_RDONLY); +#if ENABLE_LIBZIP + threadContext->gamedir = VDirOpenZip(opts->fname, 0); +#endif } threadContext->fname = opts->fname; threadContext->bios = VFileOpen(opts->bios, O_RDONLY); @@ -198,6 +201,11 @@ bool GBAThreadStart(struct GBAThread* threadContext) { threadContext->rewindBuffer = 0; } + if (!GBAIsROM(threadContext->rom)) { + threadContext->rom->close(threadContext->rom); + threadContext->rom = 0; + } + if (threadContext->gamedir) { threadContext->gamedir->rewind(threadContext->gamedir); struct VDirEntry* dirent = threadContext->gamedir->listNext(threadContext->gamedir); diff --git a/src/util/vfs-zip.c b/src/util/vfs-zip.c new file mode 100644 index 000000000..4e39d897e --- /dev/null +++ b/src/util/vfs-zip.c @@ -0,0 +1,292 @@ +#include "util/vfs.h" + +#ifdef ENABLE_LIBZIP +#include + + +enum { + BLOCK_SIZE = 1024 +}; + +struct VDirEntryZip { + struct VDirEntry d; + struct zip* z; + zip_int64_t index; +}; + +struct VDirZip { + struct VDir d; + struct zip* z; + struct VDirEntryZip dirent; +}; + +struct VFileZip { + struct VFile d; + struct zip_file* zf; + void* buffer; + size_t offset; + size_t bufferSize; + size_t readSize; + size_t fileSize; +}; + +static bool _vfzClose(struct VFile* vf); +static off_t _vfzSeek(struct VFile* vf, off_t offset, int whence); +static ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size); +static ssize_t _vfzReadline(struct VFile* vf, char* buffer, size_t size); +static ssize_t _vfzWrite(struct VFile* vf, void* buffer, size_t size); +static void* _vfzMap(struct VFile* vf, size_t size, int flags); +static void _vfzUnmap(struct VFile* vf, void* memory, size_t size); +static void _vfzTruncate(struct VFile* vf, size_t size); + +static bool _vdzClose(struct VDir* vd); +static void _vdzRewind(struct VDir* vd); +static struct VDirEntry* _vdzListNext(struct VDir* vd); +static struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode); + +static const char* _vdezName(struct VDirEntry* vde); + +struct VDir* VDirOpenZip(const char* path, int flags) { + int zflags = 0; + if (flags & O_CREAT) { + zflags |= ZIP_CREATE; + } + if (flags & O_EXCL) { + zflags |= ZIP_EXCL; + } + + struct zip* z = zip_open(path, zflags, 0); + if (!z) { + return 0; + } + struct VDirZip* vd = malloc(sizeof(struct VDirZip)); + + vd->d.close = _vdzClose; + vd->d.rewind = _vdzRewind; + vd->d.listNext = _vdzListNext; + vd->d.openFile = _vdzOpenFile; + vd->z = z; + + vd->dirent.d.name = _vdezName; + vd->dirent.index = -1; + vd->dirent.z = z; + + return &vd->d; +} + +bool _vfzClose(struct VFile* vf) { + struct VFileZip* vfz = (struct VFileZip*) vf; + if (zip_fclose(vfz->zf) < 0) { + return false; + } + free(vfz); + return true; +} + +off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) { + struct VFileZip* vfz = (struct VFileZip*) vf; + + size_t position; + switch (whence) { + case SEEK_SET: + position = offset; + break; + case SEEK_CUR: + if (offset < 0 && ((vfz->offset < (size_t) -offset) || (offset == INT_MIN))) { + return -1; + } + position = vfz->offset + offset; + break; + case SEEK_END: + if (offset < 0 && ((vfz->fileSize < (size_t) -offset) || (offset == INT_MIN))) { + return -1; + } + position = vfz->fileSize + offset; + break; + } + + if (position <= vfz->offset) { + vfz->offset = position; + return position; + } + + if (position < vfz->fileSize) { + ssize_t read = vf->read(vf, 0, position - vfz->offset); + if (read < 0) { + return -1; + } + return vfz->offset; + } + + return -1; +} + +ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) { + struct VFileZip* vfz = (struct VFileZip*) vf; + + size_t bytesRead = 0; + if (!vfz->buffer) { + vfz->bufferSize = BLOCK_SIZE; + vfz->buffer = malloc(BLOCK_SIZE); + } + + while (bytesRead < size) { + if (vfz->offset < vfz->readSize) { + size_t diff = vfz->readSize - vfz->offset; + void* start = &((uint8_t*) vfz->buffer)[vfz->offset]; + if (diff > size) { + diff = size; + } + if (buffer) { + void* bufferOffset = &((uint8_t*) buffer)[bytesRead]; + memcpy(bufferOffset, start, diff); + } + vfz->offset += diff; + bytesRead += diff; + if (diff == size) { + break; + } + } + // offset == readSize + if (vfz->readSize == vfz->bufferSize) { + vfz->bufferSize *= 2; + if (vfz->bufferSize > vfz->fileSize) { + vfz->bufferSize = vfz->fileSize; + } + vfz->buffer = realloc(vfz->buffer, vfz->bufferSize); + } + if (vfz->readSize < vfz->bufferSize) { + void* start = &((uint8_t*) vfz->buffer)[vfz->readSize]; + size_t toRead = vfz->bufferSize - vfz->readSize; + if (toRead > BLOCK_SIZE) { + toRead = BLOCK_SIZE; + } + ssize_t zipRead = zip_fread(vfz->zf, start, toRead); + if (zipRead < 0) { + if (bytesRead == 0) { + return -1; + } + break; + } + if (zipRead == 0) { + break; + } + vfz->readSize += zipRead; + } else { + break; + } + } + return bytesRead; +} + +ssize_t _vfzReadline(struct VFile* vf, char* buffer, size_t size) { + size_t bytesRead = 0; + while (bytesRead < size - 1) { + size_t newRead = vf->read(vf, &buffer[bytesRead], 1); + bytesRead += newRead; + if (!newRead || buffer[bytesRead] == '\n') { + break; + } + } + return buffer[bytesRead] = '\0'; +} + +ssize_t _vfzWrite(struct VFile* vf, void* buffer, size_t size) { + // TODO + UNUSED(vf); + UNUSED(buffer); + UNUSED(size); + return -1; +} + +void* _vfzMap(struct VFile* vf, size_t size, int flags) { + struct VFileZip* vfz = (struct VFileZip*) vf; + + UNUSED(flags); + if (size > vfz->readSize) { + vf->read(vf, 0, size - vfz->readSize); + } + return vfz->buffer; +} + +void _vfzUnmap(struct VFile* vf, void* memory, size_t size) { + UNUSED(vf); + UNUSED(memory); + UNUSED(size); +} + +void _vfzTruncate(struct VFile* vf, size_t size) { + // TODO + UNUSED(vf); + UNUSED(size); +} + +bool _vdzClose(struct VDir* vd) { + struct VDirZip* vdz = (struct VDirZip*) vd; + if (zip_close(vdz->z) < 0) { + return false; + } + free(vdz); + return true; +} + +void _vdzRewind(struct VDir* vd) { + struct VDirZip* vdz = (struct VDirZip*) vd; + vdz->dirent.index = -1; +} + +struct VDirEntry* _vdzListNext(struct VDir* vd) { + struct VDirZip* vdz = (struct VDirZip*) vd; + zip_int64_t maxIndex = zip_get_num_entries(vdz->z, 0); + if (maxIndex <= vdz->dirent.index + 1) { + return 0; + } + ++vdz->dirent.index; + return &vdz->dirent.d; +} + +struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) { + UNUSED(mode); + // TODO: support truncating, appending and creating + + struct VDirZip* vdz = (struct VDirZip*) vd; + struct zip_stat s; + if (zip_stat(vdz->z, path, 0, &s) < 0) { + return 0; + } + + struct zip_file* zf = zip_fopen(vdz->z, path, 0); + if (!zf) { + return 0; + } + + struct VFileZip* vfz = malloc(sizeof(struct VFileZip)); + vfz->zf = zf; + vfz->buffer = 0; + vfz->offset = 0; + vfz->bufferSize = 0; + vfz->readSize = 0; + vfz->fileSize = s.size; + + vfz->d.close = _vfzClose; + vfz->d.seek = _vfzSeek; + vfz->d.read = _vfzRead; + vfz->d.readline = _vfzReadline; + vfz->d.write = _vfzWrite; + vfz->d.map = _vfzMap; + vfz->d.unmap = _vfzUnmap; + vfz->d.truncate = _vfzTruncate; + + return &vfz->d; +} + +const char* _vdezName(struct VDirEntry* vde) { + struct VDirEntryZip* vdez = (struct VDirEntryZip*) vde; + struct zip_stat s; + if (zip_stat_index(vdez->z, vdez->index, 0, &s) < 0) { + return 0; + } + return s.name; +} + +#endif diff --git a/src/util/vfs.h b/src/util/vfs.h index 05d6a5c68..c64f0a5eb 100644 --- a/src/util/vfs.h +++ b/src/util/vfs.h @@ -32,4 +32,8 @@ struct VFile* VFileFromFD(int fd); struct VDir* VDirOpen(const char* path); +#ifdef ENABLE_LIBZIP +struct VDir* VDirOpenZip(const char* path, int flags); +#endif + #endif