Game Boy Advance emulator written in Rust. The way the subsystems are split up follows gdkGBA.
Most games boot, play and save. No BIOS file is required; the SWI calls
are implemented in the emulator and the interrupt dispatcher is assembled into
a small synthetic BIOS image. Pass a real BIOS with --bios and that is used
instead.
cargo run --release -- "path/to/game.gba"
Flags:
--bios bios.bin use a real BIOS image
--scale 1|2|4 window scale, default 2
--mute don't open an audio device
--save file.sav backup memory file, defaults to the ROM path with .sav
Backup memory is written out a second after the game stops touching it, and again when the window closes, so in-game saves survive quitting.
Keys:
arrows d-pad
X / Z A / B
A / S L / R
enter / backspace start / select
tab (hold) fast forward
P pause
F1 / F2 save / load state
F12 screenshot next to the ROM
escape quit
The save state goes to the same path as the backup file with a .state
suffix. It carries no ROM, so it refuses to load into a different game.
The same binary runs without a window. This is how the emulator is tested.
cargo run --release -- game.gba --headless 4300 \
--screenshot shot.png --every 500 --from 3000 \
--input "4300:START:20,4500:A:20"
--input takes a comma separated list of frame:KEY[+KEY][:frames_held].
--every writes a numbered PNG every N frames, --from delays the first one,
and --registers prints the processor state at the end.
CPU: full ARM and Thumb instruction sets, all six register banks, the barrel shifter edge cases, exceptions, and the three stage prefetch pipeline, so code that overwrites the instructions behind itself runs the old ones like hardware does.
Memory: the whole map with region mirroring, VRAM's odd upper block, byte writes duplicating into palette and background VRAM, and BIOS reads from outside the BIOS returning the open bus latch. Access timing tracks whether an access follows on from the last one, since cartridge reads are much cheaper when they do, and the ROM prefetch unit is modelled well enough that straight line code runs at roughly the right speed.
Video: scanline renderer for every mode. Four text layers, affine layers, the three bitmap modes, 128 sprites (regular and affine, 4bpp and 8bpp, both mapping layouts), both windows and the object window, mosaic, and the alpha, brighten and darken effects.
DMA: four channels, immediate/vblank/hblank/sound-FIFO timing, DMA3's video capture mode, address control including increment-and-reload, and repeat.
Timers: four channels with prescalers, cascade and interrupts.
Audio: both DirectSound FIFOs and all four PSG channels, mixed to 32 kHz stereo and played through cpal.
Cartridge: save type detection, SRAM, 64K and 128K flash with bank switching and sector erase, EEPROM, and the GPIO real time clock.
Save states capture the whole machine, so loading one puts the game back exactly where it was.
There is no link cable. Writes that would start a transfer complete straight away instead, so a game that looks for a partner reports none rather than hanging on the busy bit.
cargo test
Two tests are opt-in and skip themselves unless pointed at real files:
FEAR_GBA_TEST_SUITE=path/to/gba-tests \
FEAR_GBA_TEST_ROM="path/to/game.gba" cargo test --release
FEAR_GBA_TEST_SUITE wants a checkout of
jsmolka/gba-tests. The arm, thumb,
memory, bios, nes, unsafe and all four save ROMs pass.
cargo run --release --example trace -- game.gba # stops where execution derails
cargo run --release --example layers -- game.gba 600 out/ # renders each layer on its own
cargo run --release --example audiostat -- game.gba # reports what the APU produced
FEAR_GBA_TRACE_MODES=1 makes trace log processor mode changes.
FEAR_GBA_TRACE_FLASH=1 logs the backup memory command stream.