Skip to content

SWI 0x15 — RLUnCompVram

  • Entry: 0x000012C1 (THUMB, real entry 0x000012C0)
  • 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 RLE stream into a 16-bit-only destination (VRAM/OAM/palette). It decodes the same block format as the Wram variant but buffers two output bytes and writes them as one halfword.

Parameters

Reg In Meaning
r0 ptr Source address pointing at the 4-byte compression header.
r1 ptr Destination address (halfword-aligned, halfword-writable).

Returns

No return value.

Clobbered registers

r0–r3 and 12 bytes of stack scratch. 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

Header handling and block decoding match SWI 0x14: a length byte whose bit 7 selects uncompressed (count+1 literal bytes) or compressed (count+3 copies of one byte).

Output differs: each decoded byte is OR-ed into a 16-bit accumulator at the current byte slot; the slot toggles every byte; when it returns to the low slot (every second byte) the halfword is stored and the accumulator cleared. This keeps all destination writes 16-bit wide.

Edge cases & known bugs

  • Decompressed size 0: shared header check returns "skip"; nothing written.
  • Odd decompressed size: a store occurs only on a completed pair, so an odd final byte is left pending in the accumulator and is never written. RLE output for VRAM is normally even-sized; an odd declared size loses the last byte.
  • Type nibble not enforced; source-region protection identical to the other decompression SWIs (source in 0x00000000–0x01FFFFFF refused).
  • Overshoot of the declared size on a too-long block behaves as in the Wram variant (measured in logical bytes, then rounded down to whole halfwords by the pairing).

  • Odd-size drop hardware-confirmed (2026-07-08 anomaly tests, results/cycles_phase3_anomaly.csv): declared size 255 wrote bytes 0–253; byte 254 was never written (sentinel intact).

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).

Same 180-byte stream as SWI 0x14, 256-byte output (EWRAM): 5734 cycles (≈1.5× the Wram variant — halfword buffering).

Open questions (need hardware verification)

  • Whether any shipped asset declares an odd size for this SWI (would drop a byte).

GBATEK cross-reference

Matches GBATEK RLE format. Adds the halfword-buffering description and the dropped-final-byte behaviour on odd sizes.