40 KiB
BIOS replacement
The original BIOS replacement built by crates/gba-core/src/bios/ and the services it implements.
crates/gba-core/src/bios.rs builds a deterministic 16 KiB image from original ARM instructions and mathematically generated data.
No Nintendo firmware routines or dumped image are included. Documented protected-read words are explicit compatibility data.
A compile-time integer calculation generates the 512-byte sine table at 0x3e00..0x3fff.
The image builder checks that code and literal pools do not overlap this table.
Literal pools follow unconditional branches and stay within each load instruction's address range.
The CPU executes every instruction through the normal bus, timing, exception, HALT/STOP, and DMA paths.
There is no host-side interception of SWIs.
Use bios::boot(rom) to create a machine at the reset vector with this image mapped.
Then call Machine::step() to execute its minimal boot sequence.
bios::image() also exposes the bytes for Memory::with_bios; start with Cpu::at_reset() to initialize its stacks.
Starting directly at a cartridge with uninitialized banked stacks does not prepare these services.
Existing caller-supplied BIOS images and Memory::new behavior remain unchanged.
Minimal boot initializes:
- System stack:
0x03007f00. - IRQ stack:
0x03007fa0. - Supervisor stack:
0x03007fe0. - BIOS IRQ flags at
0x03007ff8and the callback pointer at0x03007ffc: zero. - IME: zero; POSTFLG: one.
- BG2/BG3 affine PA and PD: 256 (identity scale), through executed ARM halfword stores.
Fresh device state supplies zero PB, PC, and origins. Raw
Memory::newdefaults remain unchanged.
This prevents bitmap and affine ROMs from sampling one pixel across the screen when they rely on firmware initialization. Original regressions verify bitmap coordinates, both affine backgrounds, and scanline capture without ROM-side matrix writes. See the boot graphics investigation for the independent homebrew failure and evidence.
It then enters 0x08000000 in ARM System mode with IRQ/FIQ masks clear.
It does not reproduce Nintendo's logo, cartridge-header checks, RAM clearing, boot delays, or full hardware initialization.
The executable can load raw ROM files for terminal or window execution using this boot sequence.
This does not establish game compatibility or complete BIOS behavior.
Supported software interrupt services:
| Number | Service | Behavior |
|---|---|---|
0x00 |
SoftReset | Clear BIOS work RAM and reset CPU registers/stacks; restart in ROM or RAM without returning |
0x01 |
RegisterRamReset | r0 selects RAM and supported I/O resets, including disabled sound and disconnected serial state |
0x02 |
Halt | Wait for IE & IF; preserve IME and caller registers |
0x03 |
Stop | Freeze system clocks until an enabled live keypad condition wakes the system; preserve caller state |
0x04 |
IntrWait | r0: discard old selected flags when nonzero; r1: flags to wait for |
0x05 |
VBlankIntrWait | IntrWait with discard enabled and the VBlank flag selected |
0x06 |
Div | Signed r0 / r1; return quotient in r0, remainder in r1, and quotient magnitude in r3 |
0x07 |
DivArm | Div with the incoming numerator and denominator exchanged |
0x08 |
Sqrt | Unsigned integer square root of r0; return the rounded-down result in r0 |
0x09 |
ArcTan | Signed fixed-point tangent in r0; return a signed angle in r0 |
0x0a |
ArcTan2 | Signed fixed-point X/Y in r0/r1; return the unsigned direction angle in r0 |
0x0b |
CpuSet | r0: source; r1: destination; r2: count, fill, and width control |
0x0c |
CpuFastSet | Word copy/fill in eight-word blocks; round count upward to a multiple of eight |
0x0e |
BgAffineSet | Build background matrices and origins from r0 into r1; r2 is the record count |
0x0f |
ObjAffineSet | Build sprite matrices from r0 into r1; r2 is the count, r3 the coefficient stride |
0x10 |
BitUnPack | Expand packed units from r0 to r1 using the descriptor at r2; write complete words |
0x11 |
LZ77UnCompWram | Decompress from r0 to r1 with byte writes |
0x12 |
LZ77UnCompVram | Decompress from r0 to r1 with buffered halfword writes |
0x13 |
HuffUnComp | Decode 4-bit or 8-bit symbols from r0 to r1 with word writes |
0x14 |
RLUnCompWram | Expand run-length data from r0 to r1 with byte writes |
0x15 |
RLUnCompVram | Expand run-length data from r0 to r1 with buffered halfword writes |
0x16 |
Diff8bitUnFilterWrite8bit | Reconstruct 8-bit samples from r0 to r1 with byte writes |
0x17 |
Diff8bitUnFilterWrite16bit | Reconstruct 8-bit samples from r0 to r1 with buffered halfword writes |
0x18 |
Diff16bitUnFilter | Reconstruct 16-bit samples from r0 to r1 with halfword writes |
ARM code uses SWI service_number << 16; Thumb code uses the service number directly.
Only User/System callers are supported. Calls from exception modes return a diagnostic rather than risking nested stack corruption.
Returning services preserve CPSR and every caller register except the documented arithmetic outputs.
SoftReset instead initializes CPU registers and status as described below.
This is deterministic behavior, not a claim about undocumented firmware outputs.
Supervisor stack use is 28 bytes normally, including ArcTan and RegisterRamReset. Division and ArcTan2 use 40 bytes.
CpuFastSet, affine-matrix services, BitUnPack, all three decompression formats, and differential filters use 60 bytes.
They do not reproduce the original firmware's internal stack layout or cycle counts.
IRQ callback contract: install a word-aligned ARM callback address at 0x03007ffc after boot.
The dispatcher saves r0–r3, r12, and lr on the IRQ stack, then calls it with r0 equal to 0x04000000.
The callback must preserve r4–r11, acknowledge handled IF bits, and return with BX lr.
For interrupt waits, it must also OR the handled bits into the halfword at 0x03007ff8.
Acknowledging IF alone does not complete IntrWait. Nested IRQs and SWIs from callbacks are not supported.
The dispatcher restores registers and returns with SUBS pc, lr, #4.
IntrWait consumes only the selected BIOS RAM flags; other flags remain set.
With r0=0, a previously recorded selected flag permits immediate return.
Otherwise the service enters HALT and permits IRQ callbacks to record new flags.
It masks IME around the flag check and HALT write to avoid losing a wake-up between them.
IME returns enabled, while the caller's CPSR and instruction state are restored.
These routines follow the documented wait contract, not every known firmware implementation quirk.
CpuSet uses the low 21 bits of r2 as its halfword/word count, bit 24 for fill, and bit 26 for word width.
CpuFastSet uses the same count/fill fields but always transfers words using eight-register block loads and stores.
Fill reads its source value once. Zero count performs no source or destination accesses.
Addresses align down to the transfer width; callers should still supply aligned addresses.
Sources below 0x02000000, or ranges with a wrapping end address, return without copying.
Other invalid accesses use normal memory diagnostics. Earlier successful writes remain committed if a later instruction fails.
These copy services keep CPU IRQ delivery masked until return; device clocks and DMA continue throughout.
BIOS bus protection, exact firmware timing, undocumented side effects, and nested service calls remain incomplete.
CPU BIOS read protection
CPU data reads at 0x00000000..0x00003fff depend on the executing instruction's address, not its processor mode.
While executing inside BIOS, ARM and Thumb code can read the mapped image directly.
While executing outside BIOS, both instruction sets read byte lanes from retained BIOS prefetch data instead.
Normal word rotation, odd-halfword behavior, sign extension, and load timing still apply.
The current interpreter implements bounded ARM and Thumb BIOS prefetch snapshots:
- Before an ARM BIOS instruction executes, use its pipeline's new mapped PC+8 fetch without charging another data access.
- Before a Thumb BIOS instruction executes, sample the full mapped word at
(PC+4) & ~3. BIOS drives a 32-bit word even for a halfword fetch; both distinct lanes are retained. - After successful execution, retain that snapshot for later protected reads. Skipped conditional instructions count as successful execution.
- Instructions outside BIOS do not replace the retained value. Re-entering ARM or Thumb BIOS code refreshes it.
- Diagnostic failures preserve the previous value and clear the CPU access context.
- Unavailable lookahead bytes invalidate the known value after a successful BIOS instruction.
- A protected read without known history returns
MemoryError::Unmappedat the requested bus address, rather than exposing raw BIOS bytes.
The initial history is unknown. Direct cartridge startup with Cpu::new does not manufacture boot prefetch data.
Use BIOS execution, such as bios::boot, to establish supported history.
All retained words come from the supplied image. The bus never substitutes a firmware-specific constant.
Our generated image places documented compatibility data at PC+8 of its non-fallthrough exits:
| Boundary | Retained word |
|---|---|
| Boot and SoftReset | 0xe129f000 |
| SWI return | 0xe3a02004 |
| IRQ callback entry | 0xe25ef004 |
| IRQ return | 0xe55ec002 |
Boot, SoftReset, SWI return, and IRQ return skip a diagnostic trap at PC+4 and the data word at PC+8.
The callback branch instead has the real SUBS pc,lr,#4 return instruction at PC+8.
Builder fixups preserve this layout without adding executed instructions or host-side service hooks.
Other supplied BIOS images retain their own values; their data is not rewritten.
See the root-cause research for evidence and the firmware-versus-bus distinction.
Host/debug reads and ROM-suite memory assertions still inspect raw mapped bytes and do not initialize or change the history. Instruction fetches remain strict mapped reads outside the CPU data-access context. CPU and DMA writes to the mapped BIOS are ignored, as described below. Host writes remain read-only diagnostics. DMA BIOS sources never read image bytes. They reuse known channel data, or report an unsupported-source diagnostic when channel data is unknown. Missing BIOS images remain unmapped. The unused-memory ARM open-bus snapshot is separate from this retained BIOS value.
The Thumb rule follows documented BIOS bus lanes and a source comparison with NanoBoyAdvance. See Thumb BIOS prefetch research for evidence, emulator differences, and test scope. The BIOS latch remains separate from the CPU's ARM/Thumb instruction buffer. Both now consume a shared fetch sample; BIOS history no longer rereads lookahead memory at execution entry. It does not model exact refill, BIOS data-access, or DMA bus history. The boundary diagnostic policy and precise bus timing remain hardware-unverified. Original regressions cover synthetic images, boot, exception returns, access widths, alignment, modes, and diagnostics. The public BIOS ROM passes its unchanged protected-read assertions in debug and release builds. This result covers these firmware boundaries, not full BIOS service compatibility.
Writes to the mapped BIOS
CPU stores to 0x00000000..0x00003fff complete without changing the supplied BIOS image.
This applies to ARM and Thumb stores, all processor modes, block stores, and the write phase of swaps.
The rule depends on the destination, not the executing address or a particular BIOS service.
A swap must still complete its protected read; unknown read history remains diagnostic.
Ignored writes retain nominal data-access costs, instruction writeback, and normal machine clock advancement.
The write value does not replace BIOS read history. Instructions executed inside BIOS still update history through their ordinary fetches.
DMA writes follow the same discard rule, with source reads, channel data, address progression, completion IRQs, and nominal timing intact.
Host/debug Memory::write* calls remain strict and report ReadOnly for the mapped BIOS.
The bounded rule requires a supplied BIOS image and stops at 0x00003fff.
Missing BIOS images, unmapped destinations beyond that boundary, and cartridge writes retain their existing diagnostics.
Whole-instruction validation still rejects a block store with a later unsupported destination before committing CPU state.
DMA retains earlier completed units when a later unit fails.
This is not a general model of ignored writes across unused address ranges.
CpuSet and CpuFastSet need no service-specific change: their original ARM stores obey these bus rules.
Tests use original programs and images to check immutable firmware, widths, lanes, modes, swaps, clocks, DMA, and failed-step retry.
The local Emerald caller supplies destination zero itself; the copy routine does not corrupt that argument.
See the startup investigation.
Write-behavior references
- GBATEK memory map: the 16 KiB BIOS system-ROM region and bus widths.
- GBATEK memory-copy services: CpuSet/CpuFastSet arguments and executed copy operations.
- mGBA memory implementation at 3a5e34be: unsupported BIOS store cases log and continue without writing the image.
- ares BIOS implementation at 728105b2: the BIOS write handler discards writes separately from protected reads.
These are documentation and emulator comparisons, not new physical-hardware measurements. No reference implementation was copied.
Stop
Stop (SWI 0x03) executes an original ARM byte store of 0x80 to HALTCNT.
It uses the common 28-byte Supervisor frame and restores caller registers/status after wake-up.
It leaves IE, IME, KEYCNT, and DISPCNT unchanged.
Configure KEYCNT and IE bit 12 before calling; no matching wake source means the system stays stopped.
The caller can force blank separately before entering STOP.
Both ARM and Thumb User/System callers are supported.
A keypad wake does not set IF in the stopped-clock model. The BIOS resumes its return sequence without requiring an IRQ callback or BIOS IRQ-flag update. Ready DMA and previously pending IRQs follow the ordinary machine scheduling rules after wake-up. See STOP behavior and limits. Serial/Game Pak wake-up and exact oscillator restart timing are not implemented.
SoftReset
SoftReset (SWI 0x00) does not return to its caller.
It reads the byte at 0x03007ffa before clearing the containing RAM:
zero selects cartridge ROM at 0x08000000; any nonzero value selects work RAM at 0x02000000.
Surrounding bytes do not affect this choice. Both destinations start in ARM state, even for Thumb callers.
The service clears exactly 0x03007e00..0x03007fff, including BIOS IRQ flags, the callback pointer, and the restart flag.
It resets r0–r12 to zero and initializes these CPU banks:
| Mode | Stack pointer | Link register | Saved status |
|---|---|---|---|
| System/User | 0x03007f00 |
Selected restart address | Not present |
| Supervisor | 0x03007fe0 |
Zero | Zero |
| IRQ | 0x03007fa0 |
Zero | Zero |
Other banked registers and saved status registers remain unchanged.
The service enters System mode with CPSR 0x9f: IRQ masked, FIQ unmasked, condition flags clear.
This is the subset's deterministic status policy, not independently verified hardware behavior.
It jumps through the System link register and does not execute the cold-boot sequence.
SoftReset does not reset I/O registers or devices. IME, IE, pending IF bits, POSTFLG, display configuration, and timer configuration remain in place. Device clocks and DMA continue, so counters and pending requests can change during the service. IRQ delivery stays masked throughout the service and after restart. Restart code must install an IRQ callback and configure interrupts before unmasking IRQ.
The common SWI entry still requires an initialized, writable Supervisor stack and saves a 28-byte frame. SoftReset abandons that frame. It uses no further stack storage and never restores the old caller state. With default stacks, the frame lies inside the cleared RAM. If the Supervisor stack is outside this region, its entry writes remain there after reset. The clearing loop performs ordinary word stores; stopping execution partway leaves a partially cleared region. Active DMA can still modify memory. Software must stop conflicting transfers before requesting a restart.
Lower internal RAM, external RAM, palette RAM, video RAM, and sprite attribute memory are not cleared by SoftReset. Use RegisterRamReset or separate initialization code for those regions. Calls from exception modes retain the existing unsupported-service diagnostic. Exact firmware timing, bus-protection behavior, and undocumented side effects remain incomplete.
RegisterRamReset
RegisterRamReset (SWI 0x01) selects RAM and supported I/O resets using the low byte of r0.
Bits 8–31 are ignored. The service preserves all caller registers and status.
Supported flags can be combined:
| Bit | Action |
|---|---|
| 0 | Clear external RAM: 0x02000000..0x0203ffff |
| 1 | Clear lower internal RAM: 0x03000000..0x03007dff; exclude the top 512 bytes |
| 2 | Clear palette RAM: 0x05000000..0x050003ff |
| 3 | Clear all 96 KiB of video RAM: 0x06000000..0x06017fff |
| 4 | Clear sprite attribute memory (OAM): 0x07000000..0x070003ff |
| 5 | Reset supported idle serial state and select general-purpose inputs |
| 6 | Disable sound, reset FIFO queues and mixing control, set bias, and clear accessible wave bank 1 |
| 7 | Reset supported display, DMA, timer, and interrupt registers as described below |
All low-byte flag combinations, including r0=0xff, are accepted within the supported device subset.
This is not full audio or serial support. Two-bank wave playback and connected serial transfers remain unsupported.
Sound reset retains wave bank 0 and the separate in-flight playback word; exact firmware sound-reset equivalence is not established.
The firmware always clears the low halfword of SIODATA32, even when bit 5 is clear; its upper halfword is preserved.
Every supported request first writes DISPCNT=0x0080, including requests with no selected flags.
This forces a white screen and clears other display-control bits.
RAM clears then run in bit order, using ordinary word stores.
Zeroed OAM contains regular sprites at the origin, not disabled sprites; software must configure them before enabling objects.
After the RAM clears, bit 5 clears SIOCNT and SIODATA8, writes RCNT=0x8000, and clears supported Joybus reset registers. Disconnected general-purpose inputs read high, so RCNT reads back as 0x800f. Bit 6 disables SOUNDCNT_X, strobes both FIFO resets, clears SOUNDCNT_H, and sets SOUNDBIAS=0x0200. It then clears accessible wave bank 1. Wave bank 0 is retained and can be selected again after master enable. Disabled PSG registers remain zero. Running pulse and noise channels stop only when sound reset is selected. Reset does not produce audio, fabricate serial transfers, or bypass normal bus accesses.
Bit 7 runs after the selected RAM and sound/serial resets:
- Disable IME.
- Disable all four DMA channels, then zero their source, destination, count, and control registers.
- Stop all four timers, then zero their reload and control registers.
- Zero display registers at
0x04000004..0x04000057, subject to normal read-only register rules. - Set BG2/BG3 affine PA and PD to 256; PB, PC, and programmed origins remain zero.
- Clear IE and WAITCNT, then acknowledge IF with a halfword write of
0xffff.
Timer counter reads retain the stopped count until a later enable loads the cleared reload value. This is normal timer-register behavior, not a host-side replacement of device state. Read-only display status and VCOUNT continue to reflect the advancing display clock. GREENSWAP, KEYINPUT/button state, KEYCNT, POSTFLG, and BIOS IRQ communication words are not reset. Bit 7 covers only the listed registers. A later matching input sample can request keypad IRQ again after IF acknowledgement.
Without their corresponding reset flags, device configuration remains unchanged except for DISPCNT and the SIODATA32 low halfword. IRQ delivery stays CPU-masked during the call. The service restores the caller's mask on return. Timers and DMA continue until their reset writes, if selected; this service does not stop clocks or replace frame-capture history. Pending IRQs remain pending without bit 7 and can be delivered after return.
The service uses only the common 28-byte Supervisor frame. Default BIOS stacks lie in the excluded top 512 bytes, but the entry save still writes its usual frame there. Do not put the Supervisor stack or required return code/data in selected RAM. The service can erase a RAM caller's instructions and still return to that now-cleared address. Stop conflicting DMA transfers before a clear, even when bit 7 is selected: RAM clears occur before device-register resets. Stopping emulation midway leaves completed stores committed. Exact firmware write ordering and cycle counts are not reproduced.
Integer arithmetic
Div truncates the signed quotient toward zero. The remainder has the numerator's sign, or is zero.
DivArm takes the denominator in r0 and numerator in r1, then returns the same outputs as Div.
Both return the unsigned absolute quotient in r3 and preserve r2 and r4–r14.
INT_MIN / -1 returns 0x80000000 in r0 and r3, with a zero remainder.
Division uses a fixed 32-iteration integer algorithm. It does not use host arithmetic to calculate results.
Division by zero reaches bios::INVALID_ARGUMENT_TRAP instead of reproducing the firmware's possible endless loop.
Sqrt treats r0 as an unsigned 32-bit number and returns an unsigned result in 0..65535.
The result satisfies root² <= input < (root + 1)². Other registers are preserved.
Its integer algorithm uses 16 iterations without floating-point calculations.
Arithmetic services keep IRQ delivery masked until return, without changing IME.
Device clocks and DMA continue. Instruction costs do not reproduce the original BIOS algorithms' timing.
Fixed-point angles
ArcTan (SWI 0x09) and ArcTan2 (SWI 0x0a) accept signed 16-bit fixed-point inputs with 14 fractional bits.
An input value of 0x4000 represents 1.0. This format is also called signed Q2.14.
Inputs must be sign-extended into their 32-bit registers. For example, -1 uses 0xffffffff, not 0x0000ffff.
This subset rejects values outside -32768..32767 through bios::INVALID_ARGUMENT_TRAP, rather than interpreting undocumented wider inputs.
Angles use 65,536 units per full turn. A quarter turn is 0x4000; a half turn is 0x8000.
ArcTan reads the tangent from r0 and returns a sign-extended signed angle in r0.
For example, tangents +1.0 and -1.0 return +0x2000 and -0x2000, respectively.
The routine evaluates a fixed-point polynomial using low-32-bit products and arithmetic shifts.
Negative intermediate results round down. The implementation does not substitute a host floating-point calculation.
The polynomial has poor accuracy for tangent magnitudes above 1.0; this known limitation is retained.
ArcTan2 reads X from r0 and Y from r1. It returns an unsigned angle in 0..65535 in r0.
It divides the smaller coordinate magnitude by the larger, then applies the shared polynomial and quadrant correction.
The signed ratio truncates toward zero and stays within [-1.0, 1.0] before polynomial evaluation.
Axes return exact quarter-turn angles. The zero vector returns zero without division.
Use ArcTan2 for direction calculations that require all four quadrants.
Both services preserve r1–r14 and the caller's status. Undocumented BIOS scratch-register outputs are not reproduced. They execute original ARM instructions and keep CPU IRQ delivery masked until return, without changing IME. Device clocks and DMA continue. Exact firmware timing and full hardware compatibility remain unverified.
Affine matrices
Both services take a source pointer in r0, destination pointer in r1, and unsigned record count in r2. They generate inverse-mapping matrices for background and sprite rotation/scaling. Scale values are signed 8.8: 256 represents 1.0. The services do not calculate reciprocal scales. Angles use 65,536 units per turn, but only the upper byte is used.
BgAffineSet reads 20-byte, word-aligned source records:
| Offset | Field |
|---|---|
| 0, 4 | Signed 32-bit texture-center X/Y, with eight fractional bits |
| 8, 10 | Signed 16-bit display-center X/Y, in pixels |
| 12, 14 | Signed 16-bit X/Y scale |
| 16 | Unsigned 16-bit angle |
| 18 | Two padding bytes; not read |
Each word-aligned output record occupies 16 bytes. It contains four signed halfwords, PA/PB/PC/PD, followed by two 32-bit background origins. The destination can point directly to BG2 or BG3 affine registers.
ObjAffineSet reads eight-byte, halfword-aligned source records.
Offsets 0 and 2 contain signed X/Y scales; offset 4 contains the angle.
The final two padding bytes are not read.
The destination must be halfword-aligned. Register r3 specifies an even coefficient stride of at least two bytes.
Each matrix writes PA/PB/PC/PD at destination offsets 0*stride through 3*stride.
The next matrix starts at 4*stride. Stride two packs coefficients; stride eight preserves intervening sprite attributes in OAM.
Other valid even strides work, and the services leave gaps unchanged.
The shared kernel reads signed sine/cosine values with 14 fractional bits from the generated table. Products use arithmetic right shifts by 14, then narrow to signed 16-bit intermediates. PB negates the narrowed X-scale sine product after rounding, not before the shift. Background origins use those narrowed intermediates and wrap modulo 2³². All signed scales are accepted, including zero and -32768. Extreme overflow behavior follows the integer emulator reference; hardware equivalence remains unverified.
Zero count returns before buffer or stride validation, without source or output accesses.
For nonzero counts, invalid alignment, protected sources below 0x02000000, or wrapping ranges reach bios::INVALID_ARGUMENT_TRAP.
Counts cover complete record spans, including padding and the trailing object-stride gap.
Overflow checks run before output writes. They do not require every source byte to be mapped in advance.
Use separate source and output buffers; overlapping buffers are not supported.
The services read each record's fields before writing its output. A failed source read preserves earlier records without writing the current record. A failed destination write preserves earlier successful stores, including stores within the current record. Normal halfword/word bus rules apply to video memory and display registers. Both services preserve all caller registers and status. They keep CPU IRQ delivery masked without changing IME; device clocks and DMA continue. Exact firmware cycle counts and undocumented side effects are not reproduced.
LZ77 decompression
Both variants read a word-aligned header at r0. The upper 24 bits specify output length.
If the length is zero, both return without checking the low byte or accessing the destination or token stream.
This includes an all-zero header. Nonempty streams still require low byte 0x10.
Each flag byte describes eight tokens, most significant bit first.
A literal token produces one byte. A reference token copies 3–18 bytes from 1–4,096 bytes behind the output position.
References can overlap previously produced output. Reads observe each preceding write through the normal emulated bus.
Use separate compressed-source and output buffers; overlapping those buffers is not supported.
The WRAM variant writes bytes and accepts byte-aligned destinations. Use it only with memory that supports byte writes, such as work RAM. Video/palette RAM byte-write rules still apply. The VRAM variant also works in work RAM, but always combines two bytes into a halfword store. This subset requires an even destination address and an even output length for that variant. It rejects distance-one references, which depend on a previous byte that may still be buffered. A lone buffered byte is not committed if the next source read fails. The emulator does not model firmware quirks for odd output sizes or invalid references.
The decoder validates the type byte, source alignment, source protection, output address wrap, and reference bounds.
Sources below 0x02000000, references before produced output, and runs beyond the declared size reach bios::INVALID_ARGUMENT_TRAP.
Invalid VRAM alignment, odd output size, and distance-one references reach the same diagnostic trap.
Zero output length returns after source alignment/protection checks and the header read, before type or destination checks.
An odd or unmapped destination is therefore harmless for an empty stream; invalid source addresses still fail.
The empty-stream regression records the local reproduction and emulator evidence.
This is a bounded compatibility rule, not physical-hardware validation of malformed nonempty streams.
Truncated cartridge input and invalid destination accesses return normal memory diagnostics.
Earlier completed writes remain committed on failure; neither the whole service nor the whole output buffer is rolled back.
Each call runs as ordinary ARM instructions, so callers can enforce machine-step limits on large streams.
LZ77 preserves caller registers/status and keeps CPU IRQ delivery masked until return; device clocks and DMA continue.
Run-length decompression
Both variants read a word-aligned header at r0. Its low byte must be 0x30; the upper 24 bits specify output length.
Each block starts with a control byte:
- Bit 7 clear: copy the following
(control & 127) + 1literal bytes, from 1 to 128 bytes. - Bit 7 set: read one value and repeat it
(control & 127) + 3times, from 3 to 130 bytes.
Blocks can alternate freely. Repeated values are read once, rather than read back from the destination. Decoding stops at the declared output length without reading padding or another block. Use separate source and destination buffers; overlapping those buffers is not supported.
RLUnCompWram accepts byte-aligned destinations and uses normal byte stores. Use memory that supports byte writes.
RLUnCompVram buffers bytes across block boundaries and commits complete halfwords, including when the destination is work RAM.
This subset requires an even destination and an even output length for the halfword variant.
A lone buffered byte is not written after a source failure. Odd-size firmware behavior remains unmodeled.
Invalid type bytes, source alignment/protection, output address wrap, and halfword alignment/size reach bios::INVALID_ARGUMENT_TRAP.
Sources below 0x02000000 are rejected. Each block must fit the remaining declared output before its payload is read.
An oversized block returns a diagnostic, rather than reproducing firmware behavior on malformed data.
Zero length reads no blocks and writes no output after header/argument validation.
Truncated source data and invalid destination accesses use normal memory diagnostics. Earlier completed writes remain committed.
The CPU executes these routines as original ARM instructions, sharing the byte/halfword output routine with LZ77. They preserve caller registers/status and keep CPU IRQ delivery masked until return, without changing IME. Timers, display clocks, and DMA continue during execution. Callers can enforce machine-step limits for large streams. Exact Nintendo BIOS timing and undocumented side effects are not reproduced.
Huffman decompression
HuffUnComp (SWI 0x13) decodes binary-tree paths into 4-bit or 8-bit symbols.
The source at r0 and the destination at r1 must be word-aligned.
The header's low byte must be 0x24 or 0x28. Its upper 24 bits specify output length in bytes.
This subset requires a multiple of four output bytes and uses ordinary word stores.
The byte at source offset 4 holds the tree size. The root node follows at offset 5.
The size byte and tree table occupy 2 × (size + 1) bytes together, including any tree padding.
The compressed bitstream starts immediately after this section. This subset requires that address to be word-aligned; it does not round it.
The table can contain up to 511 bytes after its size byte.
Each internal node uses these fields:
- Bits 0–5 hold a forward child offset.
- The left child address is
(node address & ~1) + 2 × (offset + 1). - The right child follows one byte later.
- Bit 7 marks the left child as a leaf. Bit 6 marks the right child as a leaf.
Input words use little-endian byte order, but branch bits are consumed from bit 31 down to bit 0. Zero selects the left child; one selects the right child. A leaf contains the decoded symbol. The decoder returns to the root after each symbol. Internal-node state survives input-word refills. Output symbols fill each word from its lowest bits upward. For 4-bit output, the first symbol occupies the low nibble.
The decoder validates only visited edges and leaves. It does not scan unused branches or padding.
Traversed children must stay inside the declared table. A visited 4-bit leaf must not contain upper bits.
Invalid headers, widths, alignment, output size, protected sources, wrapping addresses, and invalid tree references reach bios::INVALID_ARGUMENT_TRAP.
Sources below 0x02000000 are rejected. These checks are development safeguards, not exact firmware behavior on malformed streams.
Zero output length reads no tree or bitstream after header/argument validation. Decoding stops at the declared output length without consuming remaining branch bits or trailing words. Truncated input and invalid stores use normal memory diagnostics. Earlier complete output words remain committed; pending output is discarded on failure. Use separate source and output buffers. Overlapping buffers and partial final output words are not supported.
The CPU executes original ARM instructions for all traversal and packing. No host-side service hook performs decoding. The routine preserves caller registers/status and keeps CPU IRQ delivery masked until return. IME is unchanged; device clocks and DMA continue. Exact Nintendo BIOS timing and undocumented side effects remain unmodeled.
Bit unpacking
BitUnPack (SWI 0x10) expands packed values, such as monochrome font pixels, into wider destination units.
r0 points to source bytes. r1 points to word-aligned output. r2 points to this eight-byte descriptor:
| Offset | Field |
|---|---|
+0 |
16-bit source length in bytes, from 0 to 65,535 |
+2 |
8-bit source unit width: 1, 2, 4, or 8 bits |
+3 |
8-bit destination unit width: 1, 2, 4, 8, 16, or 32 bits |
+4 |
32-bit offset: bits 0–30 hold the added value; bit 31 enables offsets for zero units |
Source bytes require no alignment. This subset requires a word-aligned descriptor and a destination width at least as large as the source width. Each source byte supplies its lowest unit first. Output units also fill each word from its lowest bits upward. The routine adds the offset to nonzero source units. It adds the offset to zero units only when bit 31 is set. The zero-data flag is not part of the numeric offset.
Output length is source length × destination width / source width bytes and must be a multiple of four.
The routine uses ordinary word stores, including for work RAM, video RAM, palette RAM, and OAM.
Use separate source, descriptor, and output buffers. Overlapping these buffers is not supported.
Invalid widths, narrowing, misalignment, incomplete output words, protected sources, and wrapping address ranges reach bios::INVALID_ARGUMENT_TRAP.
Sources below 0x02000000 are rejected. Each converted value must fit its destination width after adding the offset.
Overflow produces a diagnostic instead of masking the value or allowing bits to affect neighboring output units.
A failure discards the pending incomplete word; earlier completed stores remain committed.
These checks are development safeguards, not a model of Nintendo BIOS behavior for invalid parameters.
Zero length accesses neither source data nor output after descriptor and argument validation. Unmapped descriptor/source reads and invalid stores use normal memory diagnostics. The service preserves caller registers/status and uses original ARM instructions, without host-side SWI interception. CPU IRQ delivery stays masked until return. IME is unchanged; timers, display clocks, and DMA continue. Exact firmware timing and undocumented register outputs remain unmodeled.
Differential filters
These services reconstruct samples from stored differences. They do not change the payload size. The source at r0 starts with a word-aligned four-byte header:
- Low byte
0x81selects 8-bit samples;0x82selects 16-bit samples. - The upper 24 bits specify output length in bytes, including for the 16-bit service.
- The first payload unit is the first original sample. Each following unit is the difference from the previous sample.
The accumulator starts at zero. Each input unit adds to the accumulator, producing the next output sample. Eight-bit samples wrap modulo 256; sixteen-bit samples wrap modulo 65,536. This handles positive and negative differences. Sixteen-bit input and output units use little-endian byte order.
Diff8bitUnFilterWrite8bit accepts byte-aligned destinations and odd byte counts. It uses ordinary byte stores.
Diff8bitUnFilterWrite16bit buffers pairs of reconstructed bytes, then writes halfwords.
Diff16bitUnFilter reads and writes halfwords directly. Both halfword-output variants require even destination addresses and byte counts.
Use separate source and output buffers. Overlapping buffers and firmware quirks for odd halfword-output sizes are not supported.
Invalid header types/unit sizes, source alignment, protected sources, output alignment/size, and wrapping address ranges reach bios::INVALID_ARGUMENT_TRAP.
Sources below 0x02000000 are rejected. These strict checks are development diagnostics, not exact firmware behavior on invalid data.
After validation, zero output length reads no payload and writes no output. Decoding stops at the declared length without consuming trailing data.
Unmapped source reads and invalid output writes return normal memory diagnostics.
Earlier completed writes remain committed. The buffered byte variant does not write a lone pending byte after a source failure.
All output uses normal bus rules: byte writes to video memory can duplicate bytes, and byte writes to OAM are ignored.
These original ARM routines execute through the CPU, not a host-side service hook. They preserve caller registers/status and mask CPU IRQ delivery until return, without changing IME. Timers, display clocks, and DMA continue. Exact Nintendo BIOS timing and undocumented side effects remain unmodeled.
Firmware diagnostics
Unsupported SWIs, unsupported exception vectors, and null/misaligned IRQ callbacks reach an intentional undefined-instruction trap.
The CPU reports CpuError::UnsupportedInstruction with bios::UNSUPPORTED_TRAP, rather than silently treating a service as a no-op.
Invalid arithmetic/decompression arguments use the same CPU error type with the distinct bios::INVALID_ARGUMENT_TRAP instruction.
Prior boot/service steps remain committed on failure.
Two-bank wave playback, connected serial links, HardReset, and serial/Game Pak STOP wake-up remain unimplemented.
Darwin ROM windows can enable host audio with --audio; this does not extend BIOS coverage.
Disconnected normal serial transfers use the bounded serial model; cancel active transfers before BIOS serial reset.
This subset is not sufficient for Pokémon Emerald compatibility.