Stores to 0x08-0x0c returned ReadOnly and stopped the machine. Hardware ignores them, and a second game's boot code stores to a ROM mirror. Emulated writes now pay their bus cost and change nothing. The 0x0d window stays a diagnostic until EEPROM is modeled there, and host writes stay strict. Tests that used a ROM store as their failing write now cross an ignored ROM word into the 0x0d window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhfHfHEuzv2AUTd6erW1gU
11 KiB
Cartridge files and execution
The desktop executable can load raw ROM bytes for terminal diagnostics or window execution. This supports original test programs. It does not establish commercial-game compatibility. For explicit pass/fail assertions instead of diagnostic limits, use the headless ROM suite runner.
direnv exec . cargo run --locked --release -- --rom path/to/original.gba --steps 100000
direnv exec . cargo run --locked --release -- --rom path/to/original.gba --window
Supply --rom PATH and exactly one execution mode: --steps COUNT or --window.
Option order does not matter. Terminal step counts accept decimal digits from 1 through 100,000,000.
Window mode optionally accepts --frames COUNT, from 1 through 100,000, for bounded presentation tests.
Reject zero, signed numbers, duplicate options, and combinations of terminal and window limits.
Do not combine ROM mode with demo or help options. Bare file paths are not accepted.
Quote paths that contain spaces. File extensions do not affect loading.
Loading and boot
crates/desktop/src/cartridge.rs owns file access, argument checks, execution limits, and reports.
The core remains independent of the filesystem and window library.
The loader opens a regular file read-only and accepts 4 bytes through 32 MiB inclusive.
It checks metadata before and after opening, then independently bounds the read to the limit plus one byte.
This bounds file content reads even if the file grows. It is not a filesystem race protection mechanism.
Directories and other non-regular files are rejected. Symlinks to regular files are accepted.
Paths retain their operating-system representation and appear quoted in diagnostics.
The host filesystem can reject filenames, including invalid UTF-8 on Darwin.
Bytes are not padded, patched, or written back.
The loader does not validate a cartridge header, logo, or checksum.
It does not decode archives or ELF executables. Supply a raw binary with an ARM instruction at offset zero.
Headerless original programs are accepted.
Reads past supplied cartridge bytes remain explicit memory errors, not open-bus values.
All three Game Pak ROM windows use the same supplied bytes and existing wait-state rules.
Ordinary cartridge bytes remain read-only. CPU and DMA writes to the 0x08–0x0c ROM windows pay their bus cost and are ignored, as on hardware.
Writes to the 0x0d window remain ReadOnly diagnostics until EEPROM is modeled there. Host Memory::write* calls stay strict everywhere.
Optional --save-type flash64 or --save-type flash128 enables nominal Flash programming, erase, and reads.
Without --save-file PATH, changes remain volatile. With it, the frontend loads an exact-size image and persists changes on clean exit or when a ROM window ends on a diagnostic.
Missing images start erased. Existing images receive unique backups before replacement; incomplete Flash operations refuse persistence.
Read the storage safeguards and limits before using a valuable save.
Optional --rtc selection enables the bounded Game Pak GPIO and RTC calendar interface.
It supports date/time reads, complete writes, reset, and explicit elapsed-time updates. RTC interrupts and persistence remain unsupported.
The desktop seeds UTC once, then supplies monotonic host elapsed seconds independently of GBA cycles and STOP.
Both modes call bios::boot with the loaded bytes.
With --rtc, the host then selects the cartridge peripheral before the first machine step.
Without that option, no RTC is attached. Duplicate --rtc options are rejected.
Save-device selection is independent of RTC selection. Omitting --save-type leaves save memory unmapped.
Unknown save types and duplicate options fail before file access. No ROM-based hardware detection is used.
The original BIOS replacement executes its minimal boot before entering 0x08000000 in ARM System mode.
There is no command-line option for external BIOS files.
Unsupported services remain diagnostics. RegisterRamReset now accepts sound/serial reset flags, including r0=0xff, within the initialization subset.
Supported sound and serial behavior extends beyond reset, but remaining unsupported operations still produce diagnostics.
Accepting reset does not establish game compatibility.
Terminal execution limits and results
The budget counts successful machine steps, including BIOS boot instructions. Each CPU instruction, IRQ entry, DMA unit, or HALT idle batch consumes one step. The budget is not an instruction-only, cycle, frame, or wall-clock limit. HALT continues advancing devices within the budget. STOP returns immediately because terminal mode has no input source to wake the machine. STOP on the final allowed step is reported as STOP, not as budget exhaustion. A failed CPU or DMA step consumes no budget and retains the machine state from earlier successful steps.
The report includes:
- Quoted path, byte count, and requested step limit.
- Stop reason and successful-step counters by kind.
- Nominal emulated cycles, PC, CPSR, CPU mode, and ARM/Thumb state.
- HALT/STOP flags and all active registers r0–r15.
Exit status zero means the step limit was reached or STOP was entered. It does not mean the program completed or passed a hardware test. Invalid arguments, loading failures, output failures, save-persistence failures, and emulation diagnostics produce a nonzero exit status. For emulation diagnostics, stdout contains the final machine state and stderr contains the core error.
Terminal mode has no window, keyboard input, or real-time pacing. It does not enable frame capture or validate rendered output.
Window execution
--window opens a native 240×160 window at 4× scale on the main thread.
The window presents scanline-captured frames at VBlank entry.
Capture starts before BIOS boot. It has no demo-specific readiness checks and does not suppress startup images.
A black buffer appears before the first captured frame.
The host does not set display registers, initialize game RAM, or draw replacement game graphics.
Controls use the same focus correction and key mapping as the existing demos:
| Host key | GBA button |
|---|---|
| Arrows | D-pad |
| Z / X | A / B |
| Q / W | L / R |
| Enter | Start |
| Backspace | Select |
Escape or the window close control ends execution. Enter does not reset the emulator.
F5 writes and F9 loads a save state in the file chosen by --state-file PATH (default: the ROM path with a .state extension).
Focus loss releases all buttons but does not pause execution.
The host samples buttons before each execution slice, including while stopped.
Each slice executes at most 4,096 machine steps or stops at the next VBlank event. The host processes window events between slices, not only when a frame completes. A separate 400,000-step limit guards progress between VBlank events and persists across slices and STOP waits. This limit permits a full frame of one-cycle instructions. It is not a wall-clock deadline. HALT continues device clocks and frame publication. Normal presentation targets approximately 59.73 frames per second, without frame skipping or accumulated catch-up work. Slow hosts can run slower than real time. Host sleeping never advances emulated clocks.
Playback speed
Use --speed 2 with --window to target twice normal speed.
The option accepts integer multipliers from 1 through 16; the default is 1.
Missing values, duplicate options, fractions, and values outside this range are rejected before ROM loading.
The option is not available for terminal runs, test suites, or demos.
direnv exec . cargo run --locked --release -- --rom roms/pokemon-emerald.gba --rtc --save-type flash128 --save-file roms/pokemon-emerald.sav --window --speed 2
Speed changes only the host frame period. CPU/device cycle ratios, captured frames, execution budgets, and save operations remain unchanged. Every emulated frame is still presented. Actual speed depends on CPU, rendering, window-system, and display costs. Input remains sampled between bounded execution slices. Interactive STOP retains normal-speed host polling instead of busy waiting. RTC still follows host elapsed time; the speed multiplier does not accelerate the cartridge clock.
Above 1×, audio output and capture are disabled, even when --audio is present.
No host audio device is opened. Sound hardware still executes normally, including diagnostics for unsupported behavior.
At 1×, --audio retains its existing meaning. The window title and terminal report show the requested speed.
Finish the in-game save before exiting normally; speed does not change clean-exit persistence rules.
In interactive mode, STOP retains the last image and changes the window title. The host continues polling input and window-close events while GBA clocks remain frozen. Only keypad conditions configured by the ROM through KEYCNT and IE can wake STOP. A key press cannot wake a program that has not enabled a matching wake source.
--frames COUNT exits successfully after that many captured frames are submitted to the window.
It is a presentation limit, not proof that the ROM completed or passed a test.
STOP before that limit is an error instead of an indefinite wait for physical input.
Early window closure is also an error in this bounded mode; closure is normal in interactive mode.
CPU, DMA, video, and window errors end the session with a nonzero exit status. The terminal report includes captured-frame count and the final machine state; stderr contains the error. Rendering errors do not overwrite the last complete framebuffer. Reports count captured frames, not idle redraws or window-event updates.
Try an original ROM
Generate an original headerless input-test file, then open that file through the ROM loader:
direnv exec . cargo run --locked --example write_rom_demo -- /tmp/gba-input-demo.gba
direnv exec . cargo run --locked --release -- --rom /tmp/gba-input-demo.gba --window
The generator refuses to overwrite an existing file. Choose a new path if needed. The CPU writes the palette: blue at rest, red while Z is held, and green while Right is held. Right takes priority over Z. Other buttons have no visible effect in this small program. No game or firmware content is included.
ROM windows can enable Darwin output with --audio; they are muted by default.
At 1×, the option requires a usable default audio device. Device/configuration errors fail explicitly; omit the flag to run muted.
Above 1×, --speed overrides --audio and keeps output muted.
Focus loss and STOP clear queued audio. See audio output and its limits.
Terminal mode has no audio. Both modes support explicit save files but lack per-step tracing.
Commercial-game compatibility remains unverified and unsupported.
Only load files that you may lawfully use. Do not commit game ROMs or firmware.