gameboy-rust/src/bios/run_length.rs
0xrsydn 5984551784 feat(bios): decode run-length streams through ARM instructions
Add RLUnCompWram and RLUnCompVram with block bounds, word-independent buffering, and device-progress tests.
2026-10-05 12:58:39 +07:00

64 lines
2.7 KiB
Rust

//! Original run-length decoder emitted as ARM instructions.
//! Runs must fit the declared output size. Halfword output requires an even
//! destination and size; malformed-input diagnostics do not emulate BIOS quirks.
use super::ArmImage;
pub(super) fn emit(a: &mut ArmImage) {
a.label("rl_wram");
a.emit(0xe3a0_c000); // MOV r12,#0 (byte stores)
a.branch(14, "rl_start");
a.label("rl_vram");
a.emit(0xe3a0_c001); // MOV r12,#1 (buffered halfword stores)
a.label("rl_start");
a.emit(0xe92d_0ff0); // STMDB sp!,{r4-r11} (60 bytes total SVC stack use)
a.emit(0xe310_0003); // TST r0,#3 (word-aligned header)
a.branch(1, "invalid_argument");
a.emit(0xe350_0402); // CMP r0,#0x02000000 (reject protected source region)
a.branch(3, "invalid_argument");
a.emit(0xe490_2004); // LDR r2,[r0],#4
a.emit(0xe202_b0ff); // AND r11,r2,#0xff
a.emit(0xe35b_0030); // CMP r11,#0x30 (run-length type, reserved bits zero)
a.branch(1, "invalid_argument");
a.emit(0xe1a0_2422); // MOV r2,r2,LSR #8 (24-bit output byte count)
a.emit(0xe35c_0000); // CMP r12,#0
a.branch(0, "rl_validate_end");
a.emit(0xe181_b002); // ORR r11,r1,r2
a.emit(0xe31b_0001); // TST r11,#1 (even destination and length)
a.branch(1, "invalid_argument");
a.label("rl_validate_end");
a.emit(0xe091_b002); // ADDS r11,r1,r2
a.branch(2, "invalid_argument"); // Reject destination address wrap.
a.emit(0xe352_0000); // CMP r2,#0
a.branch(0, "rl_done");
a.emit(0xe3a0_9000); // MOV r9,#0 (pending halfword)
a.emit(0xe3a0_a000); // MOV r10,#0 (bit position 0 or 8)
a.label("rl_block");
a.emit(0xe4d0_3001); // LDRB r3,[r0],#1 (block control)
a.emit(0xe203_707f); // AND r7,r3,#0x7f
a.emit(0xe313_0080); // TST r3,#0x80 (repeat flag)
a.emit(0x0287_7001); // ADDEQ r7,r7,#1 (1..128 literal bytes)
a.emit(0x1287_7003); // ADDNE r7,r7,#3 (3..130 repeated bytes)
a.emit(0xe157_0002); // CMP r7,r2
a.branch(8, "invalid_argument"); // Reject a block exceeding remaining output.
a.emit(0xe313_0080); // TST r3,#0x80
a.branch(0, "rl_literal");
a.emit(0xe4d0_5001); // LDRB r5,[r0],#1 (read repeated value only once)
a.label("rl_repeat");
a.call("decompress_store_byte");
a.emit(0xe257_7001); // SUBS r7,r7,#1
a.branch(1, "rl_repeat");
a.branch(14, "rl_next");
a.label("rl_literal");
a.emit(0xe4d0_5001); // LDRB r5,[r0],#1
a.call("decompress_store_byte");
a.emit(0xe257_7001); // SUBS r7,r7,#1
a.branch(1, "rl_literal");
a.label("rl_next");
a.emit(0xe352_0000); // CMP r2,#0
a.branch(1, "rl_block");
a.label("rl_done");
a.emit(0xe8bd_0ff0); // LDMIA sp!,{r4-r11}
a.branch(14, "return");
}