From 17a721c8f7be7217d9ac28e6f4d8a3d67ed33be6 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 6 Sep 2026 16:14:31 +1000 Subject: [PATCH] ZipHelpers: Load libzip on-demand --- scripts/appimage/make-appimage.sh | 1 + src/core/cheats.cpp | 26 +++++++-- src/core/cheats.h | 3 + src/core/core.cpp | 3 + src/util/CMakeLists.txt | 18 +++--- src/util/dyn_libs.cpp | 22 ++++++++ src/util/dyn_libzip.h | 41 ++++++++++++++ src/util/util.props | 2 +- src/util/util.vcxproj | 1 + src/util/util.vcxproj.filters | 1 + src/util/zip_helpers.cpp | 93 ++++++++++++++++--------------- 11 files changed, 153 insertions(+), 58 deletions(-) create mode 100644 src/util/dyn_libzip.h diff --git a/scripts/appimage/make-appimage.sh b/scripts/appimage/make-appimage.sh index 1cd3607c1..49f47265a 100755 --- a/scripts/appimage/make-appimage.sh +++ b/scripts/appimage/make-appimage.sh @@ -43,6 +43,7 @@ declare -a MANUAL_LIBS=( "libwebpmux.so.3" "libwebp.so.7" "libz.so.1" + "libzip.so.5" "libdiscord-rpc.so" "libplutosvg.so.0" diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index b25f5d710..e52ba84f7 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -13,6 +13,7 @@ #include "host.h" #include "system.h" +#include "util/dyn_libzip.h" #include "util/imgui_manager.h" #include "util/translation.h" #include "util/zip_helpers.h" @@ -104,12 +105,13 @@ private: class CheatArchive { public: +#if defined(_DEBUG) || defined(_DEVEL) ~CheatArchive() { - // zip has to be destroyed before data - m_zip.reset(); - m_data.deallocate(); + DebugAssert(!m_zip); + DebugAssert(m_data.empty()); } +#endif ALWAYS_INLINE bool IsOpen() const { return static_cast(m_zip); } @@ -133,8 +135,8 @@ public: } m_data = std::move(data.value()); - m_zip = ZipHelpers::OpenManagedZipBuffer(m_data.data(), m_data.size(), 0, false, &error); - if (!m_zip) [[unlikely]] + if (!g_dyn_libzip.Open(&error) || + !(m_zip = ZipHelpers::OpenManagedZipBuffer(m_data.data(), m_data.size(), 0, false, &error))) [[unlikely]] { ERROR_LOG("Failed to open cheat archive {}: {}", name, error.GetDescription()); return false; @@ -152,6 +154,13 @@ public: return ret; } + void Close() + { + // zip has to be destroyed before data + m_zip.reset(); + m_data.deallocate(); + } + private: // Maybe counter-intuitive, but it ends up faster for reading a single game's cheats if we keep a // copy of the archive in memory, as opposed to reading from disk. @@ -465,6 +474,13 @@ void Cheats::EnumerateChtFiles(const std::string_view serial, std::optional && std::is_standard_layo static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); @@ -39,6 +41,7 @@ static constexpr int LIBPNG_MAJOR_VERSION = -1; static constexpr int LIBJPEG_MAJOR_VERSION = -1; static constexpr int LIBWEBP_MAJOR_VERSION = -1; static constexpr int LIBSDL_MAJOR_VERSION = -1; +static constexpr int LIBZIP_MAJOR_VERSION = -1; static constexpr int SQLITE_MAJOR_VERSION = -1; static constexpr int SPIRV_CROSS_MAJOR_VERSION = -1; #else @@ -47,6 +50,7 @@ static constexpr int PLUTOSVG_MAJOR_VERSION = 0; static constexpr int LIBPNG_MAJOR_VERSION = 16; static constexpr int LIBJPEG_MAJOR_VERSION = 62; static constexpr int LIBWEBP_MAJOR_VERSION = 7; +static constexpr int LIBZIP_MAJOR_VERSION = 5; static constexpr int LIBSDL_MAJOR_VERSION = 0; static constexpr int SQLITE_MAJOR_VERSION = 3; static constexpr int SPIRV_CROSS_MAJOR_VERSION = SPVC_C_API_VERSION_MAJOR; @@ -65,6 +69,8 @@ struct Locals std::once_flag libpng_init_flag; DynamicLibrary libwebp_library; std::once_flag libwebp_init_flag; + DynamicLibrary libzip_library; + std::once_flag libzip_init_flag; DynamicLibrary sdl_library; std::once_flag sdl_init_flag; DynamicLibrary sqlite_library; @@ -86,6 +92,7 @@ DynPlutoSvg g_dyn_plutosvg; DynLibJPEG g_dyn_libjpeg; DynLibWebP g_dyn_libwebp; DynLibPNG g_dyn_libpng; +DynLibZip g_dyn_libzip; DynSDL g_dyn_sdl; DynSqlite g_dyn_sqlite; DynShaderc g_dyn_shaderc; @@ -203,6 +210,21 @@ bool DynLibWebP::Open(Error* const error) s_libwebp_symbols, error); } +static const DynamicLibrary::SymbolTable s_libzip_symbols[] = { +#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_libzip.F}, + DYN_LIBZIP_FUNCTIONS(RESOLVE_SYMBOL) +#undef RESOLVE_SYMBOL +}; + +bool DynLibZip::Open(Error* const error) +{ + if (s_locals.libzip_library.IsOpen()) [[likely]] + return true; + + return LoadDynLib("zip", LIBZIP_MAJOR_VERSION, s_locals.libzip_library, s_locals.libzip_init_flag, s_libzip_symbols, + error); +} + static const DynamicLibrary::SymbolTable s_sdl_symbols[] = { #define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_sdl.F}, DYN_SDL_FUNCTIONS(RESOLVE_SYMBOL) diff --git a/src/util/dyn_libzip.h b/src/util/dyn_libzip.h new file mode 100644 index 000000000..68c3e3dd2 --- /dev/null +++ b/src/util/dyn_libzip.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include + +class Error; + +#define DYN_LIBZIP_FUNCTIONS(X) \ + X(zip_get_error) \ + X(zip_error_set) \ + X(zip_error_strerror) \ + X(zip_error_fini) \ + X(zip_open_from_source) \ + X(zip_source_file_create) \ + X(zip_source_function_create) \ + X(zip_source_buffer_create) \ + X(zip_source_free) \ + X(zip_close) \ + X(zip_discard) \ + X(zip_get_num_entries) \ + X(zip_get_name) \ + X(zip_name_locate) \ + X(zip_stat) \ + X(zip_stat_index) \ + X(zip_fopen) \ + X(zip_fopen_index) \ + X(zip_fread) \ + X(zip_fclose) + +struct DynLibZip +{ +#define ADD_FUNC(F) decltype(&::F) F; + DYN_LIBZIP_FUNCTIONS(ADD_FUNC) +#undef ADD_FUNC + + bool Open(Error* error); +}; + +extern DynLibZip g_dyn_libzip; diff --git a/src/util/util.props b/src/util/util.props index b09da5040..9de62fd22 100644 --- a/src/util/util.props +++ b/src/util/util.props @@ -25,7 +25,7 @@ %(AdditionalIncludeDirectories);$(DepsIncludeDir)spirv_cross - %(AdditionalDependencies);soundtouch.lib;zip.lib;zlib.lib;zstd.lib + %(AdditionalDependencies);soundtouch.lib;zlib.lib;zstd.lib diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index ec74b7614..e1a119044 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -5,6 +5,7 @@ + diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 8aec5cee3..290519989 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -89,6 +89,7 @@ + diff --git a/src/util/zip_helpers.cpp b/src/util/zip_helpers.cpp index 4b04eb9c3..21faa3ddf 100644 --- a/src/util/zip_helpers.cpp +++ b/src/util/zip_helpers.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "zip_helpers.h" +#include "dyn_libzip.h" #include "common/error.h" #include "common/file_system.h" @@ -9,7 +10,6 @@ #include "common/progress_callback.h" #include -#include LOG_CHANNEL(Ungrouped); @@ -18,7 +18,7 @@ void ZipHelpers::ZipFileDeleter::operator()(zip_file_t* zf) if (!zf) return; - zip_fclose(zf); + g_dyn_libzip.zip_fclose(zf); } void ZipHelpers::ZipDeleter::operator()(zip_t* zf) @@ -26,25 +26,25 @@ void ZipHelpers::ZipDeleter::operator()(zip_t* zf) if (!zf) return; - const int err = zip_close(zf); + const int err = g_dyn_libzip.zip_close(zf); if (err != 0) { ERROR_LOG("Failed to close zip file: {}", err); - zip_discard(zf); + g_dyn_libzip.zip_discard(zf); } } void ZipHelpers::SetErrorObject(Error* error, std::string_view msg, zip_error_t* ze, bool finalize /*= true*/) { - Error::SetStringFmt(error, "{}{}", msg, ze ? zip_error_strerror(ze) : "UNKNOWN"); + Error::SetStringFmt(error, "{}{}", msg, ze ? g_dyn_libzip.zip_error_strerror(ze) : "UNKNOWN"); if (finalize && ze) - zip_error_fini(ze); + g_dyn_libzip.zip_error_fini(ze); } ZipHelpers::ManagedZipT ZipHelpers::OpenManagedZipFile(const char* filename, int flags, Error* error /*= nullptr*/) { zip_error_t ze; - zip_source_t* zs = zip_source_file_create(filename, 0, 0, &ze); + zip_source_t* zs = g_dyn_libzip.zip_source_file_create(filename, 0, 0, &ze); zip_t* zip; if (!zs) { @@ -53,11 +53,11 @@ ZipHelpers::ManagedZipT ZipHelpers::OpenManagedZipFile(const char* filename, int } else { - if (!(zip = zip_open_from_source(zs, flags, &ze))) + if (!(zip = g_dyn_libzip.zip_open_from_source(zs, flags, &ze))) { // have to clean up source SetErrorObject(error, "zip_open_from_source() failed: {}", &ze); - zip_source_free(zs); + g_dyn_libzip.zip_source_free(zs); } } @@ -67,6 +67,9 @@ ZipHelpers::ManagedZipT ZipHelpers::OpenManagedZipFile(const char* filename, int namespace ZipHelpers { static zip_int64_t CFileSourceCallback(void* userdata, void* data, zip_uint64_t len, zip_source_cmd_t cmd) { +#define LOCAL_ZIP_SOURCE_GET_ARGS(type, data, len, error) \ + ((len) < sizeof(type) ? g_dyn_libzip.zip_error_set((error), ZIP_ER_INVAL, 0), (type*)NULL : (type*)(data)) + std::FILE* fp = static_cast(userdata); switch (cmd) { @@ -90,7 +93,7 @@ static zip_int64_t CFileSourceCallback(void* userdata, void* data, zip_uint64_t case ZIP_SOURCE_STAT: { FILESYSTEM_STAT_DATA st; - zip_stat_t* zst = ZIP_SOURCE_GET_ARGS(zip_stat_t, data, len, nullptr); + zip_stat_t* zst = LOCAL_ZIP_SOURCE_GET_ARGS(zip_stat_t, data, len, nullptr); if (!zst || !FileSystem::StatFile(fp, &st)) return -1; @@ -102,7 +105,7 @@ static zip_int64_t CFileSourceCallback(void* userdata, void* data, zip_uint64_t case ZIP_SOURCE_SEEK: { - const zip_source_args_seek_t* args = ZIP_SOURCE_GET_ARGS(zip_source_args_seek_t, data, len, nullptr); + const zip_source_args_seek_t* args = LOCAL_ZIP_SOURCE_GET_ARGS(zip_source_args_seek_t, data, len, nullptr); if (!args) return -1; @@ -128,6 +131,8 @@ static zip_int64_t CFileSourceCallback(void* userdata, void* data, zip_uint64_t default: return -1; } + +#undef LOCAL_ZIP_SOURCE_GET_ARGS } } // namespace ZipHelpers @@ -135,7 +140,7 @@ ZipHelpers::ManagedZipT ZipHelpers::OpenManagedZipCFile(std::FILE* fp, int flags { zip_error_t ze; zip_t* zip; - zip_source_t* zs = zip_source_function_create(&CFileSourceCallback, fp, &ze); + zip_source_t* zs = g_dyn_libzip.zip_source_function_create(&CFileSourceCallback, fp, &ze); if (!zs) { SetErrorObject(error, "zip_source_filep_create() failed: ", &ze); @@ -144,11 +149,11 @@ ZipHelpers::ManagedZipT ZipHelpers::OpenManagedZipCFile(std::FILE* fp, int flags } else { - if (!(zip = zip_open_from_source(zs, flags, &ze))) + if (!(zip = g_dyn_libzip.zip_open_from_source(zs, flags, &ze))) { // have to clean up source SetErrorObject(error, "zip_open_from_source() failed: {}", &ze); - zip_source_free(zs); + g_dyn_libzip.zip_source_free(zs); } } return ManagedZipT(zip); @@ -158,7 +163,7 @@ ZipHelpers::ManagedZipT ZipHelpers::OpenManagedZipBuffer(const void* buffer, siz Error* error /*= nullptr*/) { zip_error_t ze; - zip_source_t* zs = zip_source_buffer_create(buffer, size, free_buffer, &ze); + zip_source_t* zs = g_dyn_libzip.zip_source_buffer_create(buffer, size, free_buffer, &ze); zip_t* zip; if (!zs) { @@ -169,11 +174,11 @@ ZipHelpers::ManagedZipT ZipHelpers::OpenManagedZipBuffer(const void* buffer, siz } else { - if (!(zip = zip_open_from_source(zs, flags, &ze))) + if (!(zip = g_dyn_libzip.zip_open_from_source(zs, flags, &ze))) { // have to clean up source SetErrorObject(error, "zip_open_from_source() failed: {}", &ze); - zip_source_free(zs); + g_dyn_libzip.zip_source_free(zs); } } @@ -183,7 +188,7 @@ ZipHelpers::ManagedZipT ZipHelpers::OpenManagedZipBuffer(const void* buffer, siz std::vector ZipHelpers::ReadFileListInZip(zip_t* zip) { std::vector ret; - zip_int64_t num_entries = zip_get_num_entries(zip, 0); + zip_int64_t num_entries = g_dyn_libzip.zip_get_num_entries(zip, 0); if constexpr (sizeof(size_t) < sizeof(zip_int64_t)) num_entries = std::min(num_entries, static_cast(std::numeric_limits::max())); if (num_entries <= 0) @@ -192,7 +197,7 @@ std::vector ZipHelpers::ReadFileListInZip(zip_t* zip) ret.reserve(static_cast(num_entries)); for (zip_uint64_t i = 0; i < static_cast(num_entries); i++) { - const char* name = zip_get_name(zip, i, ZIP_FL_ENC_GUESS); + const char* name = g_dyn_libzip.zip_get_name(zip, i, ZIP_FL_ENC_GUESS); if (name) ret.emplace_back(name); } @@ -203,18 +208,18 @@ std::vector ZipHelpers::ReadFileListInZip(zip_t* zip) ZipHelpers::ManagedZipFileT ZipHelpers::OpenManagedFileInZip(zip_t* zip, const char* filename, u32 flags, Error* error /*= nullptr*/) { - zip_file_t* zf = zip_fopen(zip, filename, flags); + zip_file_t* zf = g_dyn_libzip.zip_fopen(zip, filename, flags); if (!zf) - SetErrorObject(error, "zip_fopen() failed: ", zip_get_error(zip), false); + SetErrorObject(error, "zip_fopen() failed: ", g_dyn_libzip.zip_get_error(zip), false); return ManagedZipFileT(zf); } ZipHelpers::ManagedZipFileT ZipHelpers::OpenManagedFileIndexInZip(zip_t* zip, u64 index, u32 flags, Error* error /*= nullptr*/) { - zip_file_t* zf = zip_fopen_index(zip, index, flags); + zip_file_t* zf = g_dyn_libzip.zip_fopen_index(zip, index, flags); if (!zf) - SetErrorObject(error, "zip_fopen_index() failed: ", zip_get_error(zip), false); + SetErrorObject(error, "zip_fopen_index() failed: ", g_dyn_libzip.zip_get_error(zip), false); return ManagedZipFileT(zf); } @@ -222,9 +227,9 @@ std::optional ZipHelpers::GetFileSizeInZip(zip_t* zip, const char* name, bo Error* error /*= nullptr*/) { zip_stat_t st; - if (zip_stat(zip, name, 0, &st) != 0) + if (g_dyn_libzip.zip_stat(zip, name, 0, &st) != 0) { - SetErrorObject(error, "zip_stat() failed: ", zip_get_error(zip)); + SetErrorObject(error, "zip_stat() failed: ", g_dyn_libzip.zip_get_error(zip)); return std::nullopt; } @@ -269,17 +274,17 @@ bool ZipHelpers::ExtractFileToDisk(zip_t* zip, const char* name, std::FILE* fp, { const int flags = case_sensitive ? 0 : ZIP_FL_NOCASE; - const zip_int64_t file_index = zip_name_locate(zip, name, flags); + const zip_int64_t file_index = g_dyn_libzip.zip_name_locate(zip, name, flags); if (file_index < 0) { - SetErrorObject(error, "zip_name_locate() failed: ", zip_get_error(zip), false); + SetErrorObject(error, "zip_name_locate() failed: ", g_dyn_libzip.zip_get_error(zip), false); return false; } - zip_file_t* zf = zip_fopen_index(zip, file_index, flags); + zip_file_t* zf = g_dyn_libzip.zip_fopen_index(zip, file_index, flags); if (!zf) { - SetErrorObject(error, "zip_fopen_index() failed: ", zip_get_error(zip), false); + SetErrorObject(error, "zip_fopen_index() failed: ", g_dyn_libzip.zip_get_error(zip), false); return false; } @@ -289,7 +294,7 @@ bool ZipHelpers::ExtractFileToDisk(zip_t* zip, const char* name, std::FILE* fp, { zip_stat_t zst; update_progress = - (zip_stat_index(zip, file_index, flags, &zst) == 0 && (zst.valid & ZIP_STAT_SIZE) && zst.size > 0); + (g_dyn_libzip.zip_stat_index(zip, file_index, flags, &zst) == 0 && (zst.valid & ZIP_STAT_SIZE) && zst.size > 0); if (update_progress) { progress->PushState(); @@ -299,12 +304,12 @@ bool ZipHelpers::ExtractFileToDisk(zip_t* zip, const char* name, std::FILE* fp, for (;;) { - const s64 read = zip_fread(zf, chunk_buffer.data(), chunk_size); + const s64 read = g_dyn_libzip.zip_fread(zf, chunk_buffer.data(), chunk_size); if (read < 0) { // read error - SetErrorObject(error, "zip_fread() failed: ", zip_get_error(zip), false); - zip_fclose(zf); + SetErrorObject(error, "zip_fread() failed: ", g_dyn_libzip.zip_get_error(zip), false); + g_dyn_libzip.zip_fclose(zf); if (update_progress) progress->PopState(); @@ -316,7 +321,7 @@ bool ZipHelpers::ExtractFileToDisk(zip_t* zip, const char* name, std::FILE* fp, { // write error Error::SetErrno(error, "fwrite() failed: ", errno); - zip_fclose(zf); + g_dyn_libzip.zip_fclose(zf); if (update_progress) progress->PopState(); @@ -335,7 +340,7 @@ bool ZipHelpers::ExtractFileToDisk(zip_t* zip, const char* name, std::FILE* fp, if (update_progress) progress->PopState(); - zip_fclose(zf); + g_dyn_libzip.zip_fclose(zf); if (std::fflush(fp) != 0) { @@ -355,34 +360,34 @@ static std::optional ReadFileInZipToContainer(zip_t* zip, const char* name, b const int flags = case_sensitive ? 0 : ZIP_FL_NOCASE; std::optional ret; - const zip_int64_t file_index = zip_name_locate(zip, name, flags); + const zip_int64_t file_index = g_dyn_libzip.zip_name_locate(zip, name, flags); if (file_index >= 0) { zip_stat_t zst; - if (zip_stat_index(zip, file_index, flags, &zst) == 0) + if (g_dyn_libzip.zip_stat_index(zip, file_index, flags, &zst) == 0) { - zip_file_t* zf = zip_fopen_index(zip, file_index, flags); + zip_file_t* zf = g_dyn_libzip.zip_fopen_index(zip, file_index, flags); if (zf) { ret = T(); ret->resize(static_cast(zst.size)); - if (zip_fread(zf, ret->data(), ret->size()) != static_cast(ret->size())) + if (g_dyn_libzip.zip_fread(zf, ret->data(), ret->size()) != static_cast(ret->size())) { - SetErrorObject(error, "zip_fread() failed: ", zip_get_error(zip), false); + SetErrorObject(error, "zip_fread() failed: ", g_dyn_libzip.zip_get_error(zip), false); ret.reset(); } - zip_fclose(zf); + g_dyn_libzip.zip_fclose(zf); } } else { - SetErrorObject(error, "zip_stat_index() failed: ", zip_get_error(zip), false); + SetErrorObject(error, "zip_stat_index() failed: ", g_dyn_libzip.zip_get_error(zip), false); } } else { - SetErrorObject(error, "zip_name_locate() failed: ", zip_get_error(zip), false); + SetErrorObject(error, "zip_name_locate() failed: ", g_dyn_libzip.zip_get_error(zip), false); } return ret; @@ -396,7 +401,7 @@ static std::optional ReadFileInZipToContainer(zip_file_t* file, u32 chunk_siz { const size_t pos = ret->size(); ret->resize(pos + chunk_size); - const s64 read = zip_fread(file, ret->data() + pos, chunk_size); + const s64 read = g_dyn_libzip.zip_fread(file, ret->data() + pos, chunk_size); if (read < 0) { // read error