aboutsummaryrefslogtreecommitdiff
path: root/src/core/loader/loader.cpp
diff options
context:
space:
mode:
authorPavelBARABANOV <pavelbarabanov94@gmail.com>2026-10-26 17:23:18 +0100
committercrueter <crueter@eden-emu.dev>2026-10-26 17:23:18 +0100
commit58968bd1274d8ebb1f159ad31500dde8d74abea6 (patch)
tree199d26b56c5dbc3f1ee198a10bd79d443989e668 /src/core/loader/loader.cpp
parent05f36d990ce5301b310871451201a72e4c52b4df (diff)
[core] Support block-compressed NSZ and XCZ (#4552)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md). - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- - Add NCZBLOCK (nsz -B / .xcz) reader: each read decodes only the covered blocks and re-encrypts CTR sections, no disk output. - Allow installing solid .nsz to NAND via DecompressNCZForInstall (lazy, incremental decode with frontier, O(n) instead of O(n^2)). - Accept BKTR updates with thousands of sections (MaxSectionCount = 0x10000). - Wire .nsz/.xcz into loader, registered cache, scanner, and NAND install (content_manager.h), and reject solid NCZ from the game list. - Bump the pinned zstd to 33618c8 in cpmfile.json, which includes the upstream AArch64 revert ([33618c89](https://github.com/facebook/zstd/commit/33618c89e511637f8a6ef955b3501766d71feb53)) for a ZSTD_decodeSequence branch optimization that could corrupt data on ARM64; without it, solid NCZ decoding on Android fails with "Data corruption detected". NCZ format and CTR counter semantics follow the reference nsz tool and its Rust port: - https://github.com/nicoboss/nsz (Decompressor.py, BlockDecompressorReader.py) - https://docs.rs/nsz-rs/0.3.0/src/nsz_rs/decompress.rs.html The last-block edge case (decompressed size is an exact multiple of block size) is handled as described in https://github.com/nicoboss/nsz/issues/210 Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4552 Reviewed-by: CamilleLaVey <camillelavey99@gmail.com> Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Diffstat (limited to 'src/core/loader/loader.cpp')
-rw-r--r--src/core/loader/loader.cpp48
1 files changed, 39 insertions, 9 deletions
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 90e23ccc11..92e2e2f602 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -82,23 +82,27 @@ std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, File
return nullptr;
}
- if (type == FileType::NSP) {
+ if (type == FileType::NSP || type == FileType::NSZ) {
auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
}
- if (type == FileType::XCI) {
+ if (type == FileType::XCI || type == FileType::XCZ) {
FileSys::XCI xci{file, program_id, program_index};
- if (xci.GetStatus() != ResultStatus::Success) {
- return nullptr;
+ if (xci.GetStatus() == ResultStatus::Success) {
+ auto secure_nsp = xci.GetSecurePartitionNSP();
+ if (secure_nsp != nullptr && secure_nsp->GetStatus() == ResultStatus::Success) {
+ return secure_nsp;
+ }
}
- auto secure_nsp = xci.GetSecurePartitionNSP();
- if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) {
- return nullptr;
+ // PFS0-shaped .xcz (NSZ-style container) does not parse as XCI; fall back to NSP.
+ if (type == FileType::XCZ) {
+ auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
+ return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
}
- return secure_nsp;
+ return nullptr;
}
return nullptr;
@@ -144,7 +148,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
}
bool IsContainerType(FileType type) {
- return type == FileType::NSP || type == FileType::XCI;
+ return type == FileType::NSP || type == FileType::NSZ || type == FileType::XCI ||
+ type == FileType::XCZ;
}
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id,
@@ -180,8 +185,12 @@ FileType GuessFromFilename(const std::string& name) {
return FileType::NCA;
else if (extension == "xci")
return FileType::XCI;
+ else if (extension == "xcz")
+ return FileType::XCZ;
else if (extension == "nsp")
return FileType::NSP;
+ else if (extension == "nsz")
+ return FileType::NSZ;
else if (extension == "kip")
return FileType::KIP;
return FileType::Unknown;
@@ -197,10 +206,14 @@ std::string GetFileTypeString(FileType type) {
return "NCA";
case FileType::XCI:
return "XCI";
+ case FileType::XCZ:
+ return "XCZ";
case FileType::NAX:
return "NAX";
case FileType::NSP:
return "NSP";
+ case FileType::NSZ:
+ return "NSZ";
case FileType::KIP:
return "KIP";
case FileType::DeconstructedRomDirectory:
@@ -326,12 +339,29 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
system.GetContentProvider(), program_id,
program_index);
+ // NCZ-compressed XCI container. XCI-shaped .xcz goes to the XCI loader, while
+ // PFS0-shaped (NSZ-style) .xcz containers fall back to the NSP loader.
+ case FileType::XCZ: {
+ FileSys::XCI probe{file, program_id, program_index};
+ if (probe.GetStatus() == ResultStatus::Success &&
+ probe.GetSecurePartitionNSP() != nullptr) {
+ return std::make_unique<AppLoader_XCI>(std::move(file),
+ system.GetFileSystemController(),
+ system.GetContentProvider(), program_id,
+ program_index);
+ }
+ return std::make_unique<AppLoader_NSP>(std::move(file), system.GetFileSystemController(),
+ system.GetContentProvider(), program_id,
+ program_index);
+ }
+
// NX NAX (NintendoAesXts) file format.
case FileType::NAX:
return std::make_unique<AppLoader_NAX>(std::move(file));
// NX NSP (Nintendo Submission Package) file format
case FileType::NSP:
+ case FileType::NSZ:
return std::make_unique<AppLoader_NSP>(std::move(file), system.GetFileSystemController(),
system.GetContentProvider(), program_id,
program_index);