SWI 0x14 — RLUnCompWram¶
- Entry:
0x00001279(THUMB, real entry0x00001278) - Status: verified (hardware-checked 2026-07-08: cycle counts measured and output round-trip-verified on real GBA via the worker ROM)
Summary¶
Decompresses a run-length-encoded (RLE) stream using 8-bit writes. For byte-writable destinations; use SWI 0x15 for VRAM/OAM.
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.
Clobbered registers¶
r0–r3. r4–r7 saved/restored.
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0/r1 advanced past stream/output; r3 = 0x170 leftover. 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; bits 8–31 are the decompressed size in bytes, byte 0 (type nibble) is ignored. The shared header/region check runs; on failure nothing is written.
The stream is a sequence of blocks, each led by a length byte:
- Bit 7 = 0 (uncompressed run): the low 7 bits give count, and
count + 1 raw bytes follow; each is copied verbatim to the output.
- Bit 7 = 1 (compressed run): the low 7 bits give count, one data byte
follows, and it is written count + 3 times.
So literal blocks are 1..128 bytes and compressed runs are 3..130 bytes. After each block the remaining size is decreased by the block's output length; the loop ends when remaining size reaches 0 (or below).
Edge cases & known bugs¶
- Decompressed size 0: shared header check returns "skip"; nothing written.
- Type nibble not enforced: byte 0 of the header is never inspected.
- Source-region protection: source (after header) or computed end pointer in
0x00000000–0x01FFFFFFis refused (BIOS read-protection). EWRAM/IWRAM/ROM sources pass. -
Overshoot: the inner copy/fill loop counts by the block length, not the remaining-size counter. A block whose length exceeds the bytes left is written in full, overshooting the declared size before the loop stops.
-
Hardware-confirmed (2026-07-08 anomaly tests,
results/cycles_phase3_anomaly.csv): odd declared size 255 writes exactly 255 bytes; a stream with type nibble0x1(LZ77's code) decodes identically to type0x3.
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).
Mixed runs + literals, 256-byte output from a 180-byte stream (EWRAM): 3778 cycles. Header with decompressed size 0: 94 cycles (immediate return).
Open questions (need hardware verification)¶
- ~~Inner-loop cycle timing~~ — measured, see Cycle count.
GBATEK cross-reference¶
Matches GBATEK RLE format (bit 7 flag; run length +3, literal length +1). Adds: type nibble not validated, source-region gate, and overshoot behaviour.