nsz-rs 0.3.0

Nintendo Switch NSP to NSZ zstd compression and back, interchangeable with nsz
Documentation

nsz-rs

Lossless zstd compression of Nintendo Switch NSP dumps into NSZ, and back, in Rust. Part of oxyROMon, as a library and the nszrs CLI. Its NSZs are interchangeable with those of nsz, which it began as a port of (see Credits).

Status

Feature Supported
NSZ → NSP decompression yes
NSP → NSZ, solid yes
NSP → NSZ, block (parallel) yes
XCI ↔ XCZ no
Titlekeys from .tik in the NSP yes
BKTR (update) section splitting yes
SHA-256 verification vs. CNMT yes

NCZ files produced by nsz-rs use the same on-disk format as nsz and can be decompressed by either tool. As in nsz, only Program and PublicData NCAs whose sections tile the file are compressed; everything else is copied verbatim. Both directions stream one member at a time, so memory use doesn't grow with the dump size. The output is written to <output>.part and renamed into place once complete, so a failed run leaves an existing output alone.

Compressing a rights-managed NCA without its ticket (or a title.keys entry) is an error rather than a silent uncompressed copy.

CLI

nszrs compresses NSPs and decompresses NSZs, telling which from each file's extension, and writes next to each one unless told otherwise:

cargo build --release -p nsz-rs
nszrs game.nsz                        # decompress, next to it
nszrs --long-distance -o out/ *.nsp   # solid compress, long-distance matching
nszrs -b 1048576 -o out/ game.nsp     # compress in 1 MiB blocks, in parallel
Flag Meaning
-o DIR output directory (default: next to each input)
-l N zstd level (default 18)
-b SIZE compress in independent blocks of SIZE bytes, a power of two in 16384..=4294967296, instead of one solid stream
--long-distance zstd long-distance matching
--fix-padding re-pad the PFS0 header to 0x20 alignment
-k PATH prod.keys (default ~/.switch/prod.keys)
--skip-key-check skip the CRC32 check of known keys

Decompression verifies every NCA against the CNMT and fails on a mismatch. Like every oxyROMon tool, nszrs draws oxyROMon's progress bar, reports each input on a line of its own, and exits non-zero when any of them failed. It refuses to write one input's output over another input, or over an earlier input's output.

Library

The CLI and its progress bar sit behind the default cli feature; embed the library without them:

nsz-rs = { version = "0.3", default-features = false }
use nsz_rs::keys::Keys;
use nsz_rs::pipeline::{Compression, compress_nsp, decompress_nsz};

// Keys are passed as a loader, called only if the container needs them.
let keys = || Keys::load("prod.keys", true);
let solid = Compression { level: 18, ldm: true, block_size_exponent: None };
// The last argument receives input bytes consumed; the calls add up to the
// input file size, ready to feed a progress bar.
compress_nsp("game.nsp".as_ref(), "game.nsz".as_ref(), keys, &solid, false, &mut |_| {})?;
// keys, fix_padding, verify, strict, progress
let report = decompress_nsz(
    "game.nsz".as_ref(), "game.nsp".as_ref(), keys, false, true, true, &mut |n| bar.inc(n),
)?;

You need your own prod.keys dumped from your own console, but only to compress NCAs or to verify against a CNMT: containers without NCAs (homebrew, for instance) and unverified decompression never load it.

Credits

nsz-rs began as a port of nsz by Nico Bosshard, which itself builds on NUT by Blake Warner. The NCZ format, the key derivation and the container handling all come from their work.

License

MIT, see LICENSE. The original nsz copyright notice is retained as the license requires.