Transform Game Boy Advance ROMs into standalone Python files.
NOT an emulator. GBAtoPy transpiles GBA ROMs into human-readable Python code that, when executed, reproduces the game's behavior. The goal is a
.pyfile you can open, read, and modify.
GBAtoPy converts ARM/Thumb assembly → Python code using a Rust pipeline:
ROM bytes → Disassembly → Python Code Gen → Executable Python
- Disassembler (
crates/gbatopy-disasm/) - Decodes 56,000+ ARM/Thumb instructions - Code Generator (
crates/gbatopy-cli/src/cmds/pipeline_cmd.rs) - ARM/Thumb → Python translation - Memory Model - GBA memory map (0x08000000 ROM, 0x06000000 VRAM, 0x04000000 MMIO)
- Game Loop - pygame-based display and input
- PyBoyAdvance Runtime - Core emulation modules embedded in generated Python (see
assets/gba_runtime/). MIT-licensed, attribution required.
# ROM data embedded
ROM_DATA = bytearray([...])
def func_08000000():
global r0, r1, ..., r15
r0 = r1 + r2 # Example: ADD instruction
memory.write_32(0x08000100, value) # Example: STR instruction
def main_entry():
# ROM execution loop with pygame display
while True:
call_func(r15)- Rust toolchain (1.70+):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Python 3.10+
- Pygame:
pip install pygame - SDL2 (optional, for display):
sudo apt install libsdl2-dev
cargo build --releasecargo run --release -p gbatopy-cli -- pipeline --rom test_roms/roms/arm.gba --output /tmp/test.pyHeadless mode (for testing):
python3 /tmp/test.py --headless --frame=60 --screenshot /tmp/test.pngInteractive mode (with display):
python3 /tmp/test.py --scale=2--headless: Run without display (for testing/screenshot)--frame=N: Run exactly N frames then exit--screenshot=FILE: Save screenshot at end--scale=N: Scale display by N (e.g., 3 = 720×480 pixels)
Test ROMs are downloaded automatically via scripts/setup/download_test_roms.sh (39 ROMs):
# First time setup
bash scripts/setup/download_test_roms.sh- Rust pipeline: Compiles without warnings
- Disassembler: Decodes 56,000+ ARM instructions from test ROMs
- Code generation: Produces valid Python (75,000+ lines output)
- Syntax validation: Generated Python passes
python3 -m py_compile - Memory model:
memory.read_32()/memory.write_32()operations work - Asset extraction: Loads VRAM, palette, and sprite data at runtime
- No instruction codegen: Pipeline does NOT translate instructions. Output is template + ROM data.
- 603+ stubs: Generated Python has
pass # TODO: {opcode} not implementedfor all opcodes. - No execution: Codegen match uses exact strings; disassembler emits suffixed forms (e.g.,
ORRVS,ADDGT,STRBEQ). 603+ stubs from mismatch. - No PPU rendering: Screenshots are blank (PPU not implemented)
- No Thumb codegen: Thumb coverage ~0%
- No multiply instructions: MUL, MLA not handled
- No interrupts: VBlank/HBlank IRQ handling incomplete
- No audio: APU stubs return 0
The pipeline was simplified — run_pipeline() does not call disassembly or codegen. The output is a template with ROM data embedded. The actual codegen path (strip condition codes, full opcode map, branch handling) exists as unexecuted plan tasks T8-T12 in .sisyphus/plans/roadmap-rewrite.md.
- Blank Screenshots: All generated Python produces black/blank screens - PPU rendering not implemented
- 603+ Stubs: Most instructions generate
passstatements instead of code - No Execution: Generated Python cannot execute real game logic
- No Audio: APU emulation not integrated
- No Interrupts: VBlank wait loops may hang
# Debug
cargo build
# Release (faster)
cargo build --release
# All crates
cargo build --workspace# Rust unit tests
cargo test --workspace
# Transpile smoke test
bash scripts/setup/download_test_roms.sh # First time only
for rom in test_roms/roms/*.gba; do
cargo run --release -p gbatopy-cli -- pipeline --rom "$rom" --output /tmp/test.py && \
python3 -m py_compile /tmp/test.py && \
echo "✓ $(basename "$rom")"
done# Generate ROM
cargo run --release -p gbatopy-cli -- pipeline --rom test_roms/roms/arm.gba --output /tmp/test.py
# Check syntax
python3 -m py_compile /tmp/test.py
# Count stubs (expect 603+)
grep -c "pass.*not implemented" /tmp/test.py
# Verify not black
python3 -c "from PIL import Image; img=Image.open('/tmp/test.png'); px=list(img.getdata()); nb=sum(1 for p in px if sum(p)>30); print(f'Non-black: {nb}/38400'); assert nb >= 100"For golden screenshots and debugging, GBAtoPy integrates with mGBA emulator.
mGBA source is NOT included in this repository (see .gitignore). To use:
# Clone mGBA with Lua scripting extension
git clone --branch extend-lua https://github.com/Mte90/mgba.git
# Build mGBA
cd mgba
cmake -B build . && cmake --build build -j$(nproc)
# Run with Lua scripting
./build/sdl/mgba --lua-script your_script.lua game.gbaThe extend-lua branch adds APIs for memory/instruction tracing:
emu:traceMemory(enabled, callback)- Memory access monitoringemu:traceInstructions(enabled, callback)- Instruction execution tracingemu:watchRegister("r0", callback)- Register change detection
See PR #3752 for details.