Nintendo's DS firmware boots an ARM9 binary that's compressed with a custom backward-LZ scheme commonly called BLZ ("Backwards LZ"). Unlike the LZ11 wrapper used for asset containers, BLZ is designed for in-place decompression of executable code: the compressed bytes occupy the high end of a buffer, and the decompressor walks backward through them, writing literal bytes and back-references into the same buffer toward lower addresses. The decompressed code ends up at the buffer base, and the BLZ footer at the high end is overwritten by the last few decompressed bytes.
Where BLZ is used in this ROM
| Surface | ROM offset | Compressed size | Decompressed size |
|---|---|---|---|
| Main ARM9 binary | 0x4000 | 564,488 bytes | 723,032 bytes (0xB0858) |
Overlay overlay_NNNN.bin (most) | per overlay table at 0x8DE00 | varies (see overlay table) | varies; runtime decompressed by engine |
Overlays use the same code-LZ variant but with a per-file
is_compressed flag in the overlay table. Flip that bit to
0 and store the overlay body uncompressed and the engine
will load it as plaintext β that's the same idea as the ARM9
CompressedStaticEnd trick, applied per overlay.
BLZ footer
The last 8 bytes of a BLZ-compressed payload form the footer the backward decompressor reads first:
| Offset (from end) | Size | Field |
|---|---|---|
-0x08 | 4 | add_size β bytes to grow the buffer past the compressed region (= decompressed length β compressed length). |
-0x04 | 3 | header_size β bytes from the buffer end that are footer + literal-tail (decompressor stops after consuming these). |
-0x01 | 1 | Reserved / pad. |
Decompressed-payload layout (the "module params" struct)
Once the BLZ payload is decompressed, the resulting ARM9 plaintext starts
with a small NitroSDK module params struct that the
secure-boot loader uses to set up .bss initialization and
decide whether to skip the compression step on subsequent loads. The
fields most relevant to patching are at the very start of the plaintext:
| Plaintext offset | Field | Notes |
|---|---|---|
+0x14 | AutoLoadDoneCallback | Set after .bss init runs. |
+0x1C | CompressedStaticEnd | Pointer to the end of the BLZ-compressed region in main RAM. If this equals StaticBSSStart, the loader treats the binary as already-decompressed and skips BLZ entirely. |
+0x20 | StaticBSSStart | Pointer to the start of .bss (zero-initialized region). |
+0x24 | StaticBSSEnd | Pointer to the end of .bss. |
The "plaintext ARM9" patcher trick
For string-pool edits like the 26-slot ARM9 menu pool (see ARM9 menu pool), re-running the BLZ encoder on every patch attempt is awkward β the encoder is fiddly, the compressed-size footprint shifts as strings change, and round-trip verification is slow. Easier: load the ARM9 as plaintext and skip BLZ entirely.
- Patch the module-params field
CompressedStaticEnd := StaticBSSStartin the ARM9 plaintext (in this ROM,StaticBSSStart = 0x020A87A0). - Store the entire decompressed ARM9 (723,032 bytes) verbatim at ROM offset
0x4000in place of the original 564,488-byte BLZ blob. - Update the NDS header's ARM9 size field to
0xB0858(the decompressed length). - Boot. The loader sees
CompressedStaticEnd == StaticBSSStart, decides "nothing to decompress," and copies the plaintext bytes verbatim to main RAM.
From that point on, editing the embedded string pool is just bytes-at-an-offset: locate the slot, write the UTF-16-LE EN string with NUL pad, done. The Nintendo SDK validation marker NitroCodeBE isn't present in this ROM (Konami stripped it), so there's no extra fix-up needed for that.
Re-compression research
The plaintext-ARM9 trick is the simple path. For overlays we keep an
alternative path open: re-LZ-compress the modified overlay binary using
ndspy.codeCompression.compress() and emit the re-compressed
blob with the original is_compressed bit still set β this
matches the Konami on-disk shape more closely than a plaintext-overlay
payload. The per-overlay fit/no-fit analysis lives at
notes/_lz_compression_research.py in the translation repo;
it round-trips an overlay through ndspy.codeCompression, simulates the
worst-case body (slot filled with placeholder x's) and an alternate
EN-shaped body, and reports whether the re-compressed size fits inside
the overlay's original on-disk slot.
Source
notes/arm9_passthrough_test_RESULTS.mdβ validation of the plaintext-ARM9 trick.notes/_lz_compression_research.pyβ overlay BLZ re-compression feasibility report (ndspy.codeCompression).src/translator/_patch_arm9_overlays.pyβ the patcher that uses the trick.arm9_full_plaintext.binβ the decompressed binary the patcher edits.