Skip to content

SWI 0x12 — LZ77UnCompVram

  • Entry: 0x00001194 (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 stream into a destination that only accepts 16-bit (or wider) writes, such as VRAM, OAM, or palette RAM. It reconstructs the output byte-by-byte logically but buffers two 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, ip (r12), lr. r4–r10 are saved/restored.

Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0 advanced past the source stream; r1 = dst+0x104 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

Header and flags handling are identical to the Wram variant (SWI 0x11): the same LZ77 token encoding — literal (flag 0) and back-reference length=(byte0>>4)+3, distance=((byte0&0x0F)<<8|byte1)+1 — is decoded.

The difference is output. A 16-bit accumulator and a byte-slot toggle (low byte / high byte) are maintained: - Each produced byte is OR-ed into the accumulator at the current slot (bit 0–7 for the first byte of a pair, bit 8–15 for the second). - The slot toggles after every byte. When it returns to the low slot (i.e. after every second byte) the full 16-bit accumulator is stored to the destination and the accumulator is cleared.

Because already-written output is only readable as halfwords, back-references read a halfword from memory and extract the wanted byte by computing the halfword offset (distance converted to an even byte offset) and the byte-select shift (which byte of that halfword), driven by the current output parity.

Edge cases & known bugs

  • Decompressed size 0: shared header check returns "skip"; nothing written.
  • Odd decompressed size: because a store happens only when the pair is complete, an odd final byte is left pending in the accumulator and is never written. VRAM output sizes are normally even, so this rarely bites, but a compressed blob declaring an odd size loses its last byte.
  • Back-reference into the not-yet-flushed current halfword: the referenced byte is fetched from memory, but the pending (unstored) byte of the current halfword lives only in the internal accumulator. A back-reference whose target falls inside the current, not-yet-stored halfword (e.g. distance 1 while the high byte is being assembled) reads stale VRAM instead of the intended byte. Correct encoders for the Vram variant must avoid such references. This is the key behavioural difference from the Wram variant. Hardware-captured: a disp=1, len=7 reference immediately after one literal (into pre-zeroed destination memory) produced 10 00 00 00 00 00 00 00 instead of eight 0x10 bytes — the literal survives via the accumulator flush, and every back-referenced byte reads the old memory contents (0x00), which then propagate.
  • Type nibble not enforced; source-region protection identical to SWI 0x11 (source in 0x00000000–0x01FFFFFF is refused).
  • Length overshoot on malformed input behaves as in the Wram variant.
  • Odd-size drop hardware-confirmed (2026-07-08 anomaly tests, results/cycles_phase3_anomaly.csv): declared size 255 wrote bytes 0–253 correctly and left byte 254 untouched (destination sentinel intact) — the pending odd byte is silently discarded, as predicted.

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 256-byte test vectors as SWI 0x11, EWRAM src/dst: mixed stream 7263; VRAM-safe stream (disp ≥ 2, 220 bytes) 7271 — ≈1.3× the Wram variant, the halfword buffering overhead. Round trip verified with the disp ≥ 2 stream.

Open questions (need hardware verification)

  • ~~Exact stale-read value of an in-pending-halfword back-reference~~ — captured: it reads whatever the destination memory held before (see the hardware-captured example in Edge cases); effectively "old destination contents", undefined from the decompressor's point of view.
  • Whether real compressed assets ever declare odd sizes for this SWI.

GBATEK cross-reference

Matches GBATEK's LZ77 format. Adds precise description of the halfword-buffering scheme, the dropped-final-byte behaviour on odd sizes, and the pending-halfword back-reference hazard.