SWI 0x11 — LZ77UnCompWram¶
- Entry:
0x000010FC(ARM) - Status: verified (hardware-checked 2026-07-08: cycle counts measured and output round-trip-verified on real GBA via the worker ROM)
Summary¶
Decompresses an LZ77-compressed stream into RAM using 8-bit writes. Suitable for any destination that permits byte writes (EWRAM, IWRAM, palette, etc.), but NOT for VRAM/OAM (which reject 8-bit writes) — use SWI 0x12 for those. Output is produced one byte at a time.
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | ptr | Source address pointing at the 4-byte compression header. |
| r1 | ptr | Destination address (byte-writable). |
Returns¶
No return value. r0/r1 are advanced past the consumed input/produced output.
Clobbered registers¶
r0–r3, ip (r12), lr. r4–r6 are saved/restored.
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0 advanced past the source stream; r1 landed a few bytes past the destination end (dst+0x105 for a 0x100-byte output); r3 = 0. r2, r4–r12, r13, and CPSR (flags and mode) came back bit-identical on every tested path. r11/r12 are explained by the SWI dispatcher (it pushes {r11, r12, lr} — see 10_irq_boot_and_iwram.md) and CPSR by the SPSR restore on return; r2 and r4–r10 must be preserved or restored by the routine itself — where a static note above claims r2 is destroyed, the hardware disagrees at the caller level. Raw data: results/clobber_audit_pass2.csv.
Algorithm¶
The 4-byte header word is read first. Bits 8–31 hold the decompressed size in
bytes; bits 0–7 (byte 0) are the type/reserved nibble field and are not
examined by this routine (see Edge cases). The shared header/region check
(0xba4) is invoked; on failure the routine returns immediately without writing.
Then the classic GBA LZ77 loop runs:
- A flags byte is read. Its 8 bits are processed MSB-first; each bit selects
the token type for the next unit.
- Flag bit = 0 (literal): copy one raw byte from source to destination.
Decrement remaining size by 1.
- Flag bit = 1 (back-reference): read two bytes. length = (byte0 >> 4) + 3
(so min match length 3, max 18). disp = ((byte0 & 0x0F) << 8) | byte1, and
distance = disp + 1 (range 1..4096). Copy length bytes, each read from
dest − distance and appended, one byte at a time. Because the copy is
byte-serial from the live output, overlapping runs (distance < length) work
and effectively replicate the referenced pattern. Remaining size is decreased
by length.
After each token the remaining-size counter is tested; the loop ends when it reaches 0 (or below). A fresh flags byte is fetched every 8 tokens.
Edge cases & known bugs¶
- Decompressed size 0: the shared header check returns "skip" and nothing is written.
- Type nibble not enforced: the routine never inspects byte 0 of the header. A wrong/garbage compression-type nibble is accepted; only the size field and the source-region check matter.
- Source-region protection: the shared check refuses to run if the source
pointer (after the header) — or the computed end pointer — lies in
0x00000000–0x01FFFFFF(BIOS region and its mirror). This is a BIOS read-protection measure. Sources in EWRAM/IWRAM/ROM pass. - Length overshoot: the per-token copy loop counts by
lengthindependently of the remaining-size counter. If a back-reference's length exceeds the bytes left, the full run is still written (overshooting the declared output size) before the loop notices size ≤ 0. -
First back-reference with distance 1 at output start would read the byte just before the destination (uninitialized) — malformed input only.
-
Hardware-confirmed (2026-07-08 anomaly tests,
results/cycles_phase3_anomaly.csv): odd declared size 255 writes exactly 255 bytes (byte-granular, no trailing write); streams with type nibble0x0and0xFdecode byte-identically to type0x1with identical cycle counts.
Cycle count¶
Hardware-measured net CPU cycles (worker-ROM harness, TM0/TM1 cascade at F/1,
13-cycle baseline subtracted; identical across 3 runs — see
02_hardware_verification_checklist.md § Measurement setup).
256-byte output, EWRAM src/dst:
| Stream | Cycles |
|---|---|
| mixed literals + back-references (200-byte stream) | 5445 |
| all-literal stream (292 bytes) | 6101 |
| all-literal, 64-byte output | 1589 |
| header with decompressed size 0 | 84 (immediate return) |
Open questions (need hardware verification)¶
- ~~Exact cycle timing of the byte-serial inner loop~~ — measured, see Cycle count.
- Behaviour if the destination is a non-byte-writable region (expected: corrupt writes — caller must pick the Vram variant).
GBATEK cross-reference¶
Agrees with GBATEK's LZ77 format (min length 3, distance 1..4096). Adds: the type nibble is not validated, and documents the source-region gate and the length-overshoot behaviour on malformed data.