diff --git a/cpmfile.json b/cpmfile.json index f574bf5668..616235f27a 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -333,6 +333,11 @@ "repo": "herumi/xbyak", "version": "v7.40.1" }, + "zbic": { + "hash": "fbe2f37986377d7f0d96ae3224c80b5971df6e8b6961f68061f1975ebb2e8cb78f07b90f014b007be4023dbf5513581504e7f7d61a5237cb2a8a7a61ae11e482", + "repo": "kinnay/zbic", + "version": "11b08f2712264bbed731545085cbd9702096ceb7" + }, "zlib": { "hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4", "min_version": "1.2", diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index a211b7f013..cb1deceb96 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -48,6 +48,11 @@ if (NOT TARGET stb::headers) add_library(stb::headers ALIAS stb) endif() +AddJsonPackage(NAME zbic DOWNLOAD_ONLY) +set(ZBIC_INCLUDE_DIR + "${zbic_SOURCE_DIR}/src" + PARENT_SCOPE) + # ItaniumDemangle (Windows only) if (WIN32 AND NOT TARGET LLVM::Demangle) add_library(demangle demangle/ItaniumDemangle.cpp) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 0517f0b9e1..5d1b30a549 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -137,6 +137,8 @@ add_library( uuid.cpp uuid.h vector_math.h + zbic_compression.cpp + zbic_compression.h zstd_compression.cpp zstd_compression.h fs/ryujinx_compat.h fs/ryujinx_compat.cpp @@ -147,6 +149,10 @@ add_library( net/net.h net/net.cpp container/unordered_map.h container/unordered_set.h) +set_source_files_properties(zbic_compression.cpp PROPERTIES + INCLUDE_DIRECTORIES "${ZBIC_INCLUDE_DIR}" + COMPILE_OPTIONS "$<$:-Wno-unused-function;-Wno-missing-declarations;-Wno-shadow>") + if(WIN32) target_sources(common PRIVATE windows/timer_resolution.cpp windows/timer_resolution.h) diff --git a/src/common/zbic_compression.cpp b/src/common/zbic_compression.cpp new file mode 100644 index 0000000000..5e0e4d947b --- /dev/null +++ b/src/common/zbic_compression.cpp @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "common/zbic_compression.h" + +#define ZSTD_ZBIC_SUPPORT 1 +#define ZSTDLIB_VISIBLE static +#define ZSTDLIB_HIDDEN static +#define ZSTDERRORLIB_VISIBLE static +#define ZSTDERRORLIB_HIDDEN static +#undef ZSTD_MULTITHREAD + +#if defined(__ANDROID__) +#undef _GNU_SOURCE +#endif + +#include "zstd.h" +#define g_ZSTD_threading_useless_symbol g_ZSTD_zbic_threading_useless_symbol +#include "zstd.c" +#undef g_ZSTD_threading_useless_symbol + +namespace Common::Compression { + +bool IsZBIC(std::span src) { + if (src.size() < sizeof(u32)) { + return false; + } + u32 magic = 0; + std::memcpy(&magic, src.data(), sizeof(u32)); + return magic == ZSTD_MAGICNUMBER; // 0x4349425A ("ZBIC") +} + +int DecompressDataZBIC(std::span dst, std::span src) { + if (dst.empty() || src.empty()) { + return -1; + } + const size_t res = ZSTD_decompress(dst.data(), dst.size(), src.data(), src.size()); + if (ZSTD_isError(res)) { + return -1; + } + return static_cast(res); +} + +} // namespace Common::Compression diff --git a/src/common/zbic_compression.h b/src/common/zbic_compression.h new file mode 100644 index 0000000000..c36a559388 --- /dev/null +++ b/src/common/zbic_compression.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "common/common_types.h" + +namespace Common::Compression { + +[[nodiscard]] bool IsZBIC(std::span src); + +[[nodiscard]] int DecompressDataZBIC(std::span dst, std::span src); + +} // namespace Common::Compression diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 0a3dc5885b..ea1d1c9ecd 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -7,12 +7,14 @@ #include #include #include +#include #include #include "common/common_funcs.h" #include "common/hex_util.h" #include "common/logging.h" #include "common/lz4_compression.h" +#include "common/zbic_compression.h" #include "common/settings.h" #include "common/swap.h" #include "core/core.h" @@ -104,11 +106,36 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: for (std::size_t i = 0; i < nso_header.segments.size(); ++i) { nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset); if (nso_header.IsSegmentCompressed(i)) { - int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]); - ASSERT(r == int(nso_header.segments[i].size)); - std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size); + if (nso_header.IsZBICCompressed()) { + // ZBIC compression + const int r = Common::Compression::DecompressDataZBIC( + std::span{decompressed_size}.first(nso_header.segments[i].size), + std::span{compressed_data}.first(nso_header.segments_compressed_size[i]) + ); + ASSERT(r > 0); + } else { + // LZ4 compression + int r = Common::Compression::DecompressDataLZ4( + decompressed_size.data(), + nso_header.segments[i].size, + compressed_data.data(), + nso_header.segments_compressed_size[i] + ); + ASSERT(r == int(nso_header.segments[i].size)); + } + + std::memcpy( + codeset.memory.data() + module_start + nso_header.segments[i].location, + decompressed_size.data(), + nso_header.segments[i].size + ); } else { - std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size); + // Not compressed + std::memcpy( + codeset.memory.data() + module_start + nso_header.segments[i].location, + compressed_data.data(), + nso_header.segments[i].size + ); } codeset.segments[i].addr = module_start + nso_header.segments[i].location; codeset.segments[i].offset = module_start + nso_header.segments[i].location; diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 6356697e33..4a373edd07 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -58,6 +61,9 @@ struct NSOHeader { std::array segment_hashes; bool IsSegmentCompressed(size_t segment_num) const; + bool IsZBICCompressed() const { + return ((flags >> 7) & 1) != 0; + } }; static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); static_assert(std::is_trivially_copyable_v, "NSOHeader must be trivially copyable.");