#ifdef SHARED_LIBZIP #include #else #include "ext/libzip/zip.h" #endif #include "Core/FileLoaders/LocalFileLoader.h" #include "Core/FileLoaders/ZipFileLoader.h" ZipFileLoader::ZipFileLoader(FileLoader *sourceLoader) : ProxiedFileLoader(sourceLoader), zipArchive_(nullptr) { if (!backend_ || !backend_->Exists() || backend_->IsDirectory()) { return; } zip_error_t error{}; zip_source_t* zipSource = zip_source_function_create([](void* userdata, void* data, zip_uint64_t len, zip_source_cmd_t cmd) -> zip_int64_t { ZipFileLoader *loader = (ZipFileLoader *)userdata; return loader->ZipSourceCallback(data, len, cmd); }, this, &error); if (!zipSource) { ERROR_LOG(Log::IO, "Failed to create ZIP source: %s", zip_error_strerror(&error)); return; } zipArchive_ = zip_open_from_source(zipSource, ZIP_RDONLY, &error); if (!zipArchive_) { ERROR_LOG(Log::IO, "Failed to open ZIP archive: %s", zip_error_strerror(&error)); zip_source_free(zipSource); // zipArchive_ is already nullptr here. } } ZipFileLoader::~ZipFileLoader() { if (dataFile_) { zip_fclose(dataFile_); } if (zipArchive_) { zip_discard(zipArchive_); } if (data_) { free(data_); } } bool ZipFileLoader::Initialize(int fileIndex) { _dbg_assert_(!data_); if (!zipArchive_) { ERROR_LOG(Log::IO, "Cannot initialize: ZIP archive is null"); return false; } struct zip_stat zstat; int retval = zip_stat_index(zipArchive_, fileIndex, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat); if (retval < 0) { return false; } const char *name = zip_get_name(zipArchive_, fileIndex, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED); if (!name) { ERROR_LOG(Log::IO, "Failed to get name for file index %d", fileIndex); return false; } fileExtension_ = KeepIncludingLast(name, '.'); _dbg_assert_(zstat.index == fileIndex); dataFileSize_ = zstat.size; dataFile_ = zip_fopen_index(zipArchive_, zstat.index, ZIP_FL_UNCHANGED); if (!dataFile_) { ERROR_LOG(Log::IO, "Failed to open file index %d from ZIP", fileIndex); return false; } // Handle zero-size files if (dataFileSize_ == 0) { data_ = nullptr; return true; } data_ = (u8 *)malloc(dataFileSize_); if (!data_) { ERROR_LOG(Log::IO, "Failed to allocate %lld bytes for ZIP file data", (long long)dataFileSize_); zip_fclose(dataFile_); dataFile_ = nullptr; return false; } return true; } size_t ZipFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) { if (!dataFile_ || absolutePos < 0 || absolutePos >= dataFileSize_) { return 0; } if (absolutePos + (s64)bytes > dataFileSize_) { bytes = dataFileSize_ - absolutePos; } // Decompress until the requested point, filling up data_ as we go. TODO: Do on thread. while (dataReadPos_ < absolutePos + (s64)bytes) { int remaining = BLOCK_SIZE; if (dataReadPos_ + remaining > dataFileSize_) { remaining = (int)(dataFileSize_ - dataReadPos_); } zip_int64_t retval = zip_fread(dataFile_, data_ + dataReadPos_, remaining); if (retval < 0) { ERROR_LOG(Log::IO, "zip_fread failed with error code %lld", (long long)retval); return 0; } if (retval != remaining) { ERROR_LOG(Log::IO, "zip_fread: expected %d bytes, got %lld", remaining, (long long)retval); return 0; } dataReadPos_ += retval; } // Perform the read. memcpy(data, data_ + absolutePos, bytes); return bytes; } zip_int64_t ZipFileLoader::ZipSourceCallback(void *data, zip_uint64_t len, zip_source_cmd_t cmd) { switch (cmd) { case ZIP_SOURCE_OPEN: { zipReadPos_ = 0; return 0; } case ZIP_SOURCE_READ: { size_t readBytes = backend_->ReadAt(zipReadPos_, len, data); zipReadPos_ += readBytes; return static_cast(readBytes); } case ZIP_SOURCE_SEEK: { if (len < sizeof(zip_source_args_seek_t)) { return -1; } const zip_source_args_seek_t *seekArgs = (const zip_source_args_seek_t *)data; zip_int64_t new_offset; switch (seekArgs->whence) { case SEEK_SET: new_offset = seekArgs->offset; break; case SEEK_CUR: new_offset = zipReadPos_ + seekArgs->offset; break; case SEEK_END: new_offset = backend_->FileSize() + seekArgs->offset; break; default: return -1; } if (new_offset < 0 || new_offset > backend_->FileSize()) { return -1; } zipReadPos_ = new_offset; return 0; } case ZIP_SOURCE_TELL: return zipReadPos_; case ZIP_SOURCE_CLOSE: return 0; case ZIP_SOURCE_STAT: { if (len < sizeof(zip_stat_t)) { return -1; } zip_stat_t* st = static_cast(data); zip_stat_init(st); st->valid = ZIP_STAT_SIZE; st->size = static_cast(backend_->FileSize()); return sizeof(zip_stat_t); } case ZIP_SOURCE_ERROR: return -1; case ZIP_SOURCE_FREE: return 0; case ZIP_SOURCE_SUPPORTS: return zip_source_make_command_bitmap(ZIP_SOURCE_READ, ZIP_SOURCE_SEEK, ZIP_SOURCE_TELL, ZIP_SOURCE_OPEN, ZIP_SOURCE_CLOSE, ZIP_SOURCE_STAT, -1); default: return -1; } }