mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-29 02:41:51 +00:00
[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>
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user