Files
Henrik Rydgård 2b946094a9 Warning fixes
2026-03-10 12:19:27 +01:00

186 lines
4.8 KiB
C++

#ifdef SHARED_LIBZIP
#include <zip.h>
#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<zip_int64_t>(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<zip_stat_t*>(data);
zip_stat_init(st);
st->valid = ZIP_STAT_SIZE;
st->size = static_cast<zip_uint64_t>(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;
}
}