aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cpmfile.json5
-rw-r--r--externals/CMakeLists.txt5
-rw-r--r--src/common/CMakeLists.txt6
-rw-r--r--src/common/zbic_compression.cpp46
-rw-r--r--src/common/zbic_compression.h15
-rw-r--r--src/core/loader/nso.cpp35
-rw-r--r--src/core/loader/nso.h6
7 files changed, 114 insertions, 4 deletions
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 "$<$<CXX_COMPILER_ID:Clang,GNU>:-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 <cstring>
+
+#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<const u8> 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<u8> dst, std::span<const u8> 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<int>(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 <span>
+#include "common/common_types.h"
+
+namespace Common::Compression {
+
+[[nodiscard]] bool IsZBIC(std::span<const u8> src);
+
+[[nodiscard]] int DecompressDataZBIC(std::span<u8> dst, std::span<const u8> 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 <algorithm>
#include <cinttypes>
#include <cstring>
+#include <span>
#include <vector>
#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<VAddr> 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<u8>{decompressed_size}.first(nso_header.segments[i].size),
+ std::span<const u8>{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<SHA256Hash, 3> 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>, "NSOHeader must be trivially copyable.");