Gemboy is a Game Boy emulator written in Ruby, covering both the DMG-01 (original model) and
the Game Boy Color. It runs .gb and .gbc ROMs: SM83 CPU, memory banking, scanline-accurate
graphics in monochrome or color, 4-channel stereo sound and battery saves.
Because C was too reasonable.
test_roms/homemade/gemboy_logo.asm — a Game Boy Color ROM written for this project
- Models: DMG-01 and Game Boy Color, picked from the cartridge header (
--cgbforces color on a dual-compatible ROM) - CPU: full SM83 instruction set, interrupts,
HALT/STOP, per-instruction cycle counts, CGB double speed (KEY1) - Cartridges: ROM only, MBC1, MBC3 and MBC5, with
.savpersistence for battery-backed games - Real time clock: MBC3 clock driven by emulated cycles, catching up the powered-off delay from the
.savtimestamp - Graphics: background, window and sprites, dot-level rendering with the real PPU mode cycle; in CGB, the two VRAM banks, per-tile attributes, the 8+8 RGB555 palettes and GDMA/HDMA transfers
- Sound: the four channels, stereo panning and master volume
- Input: the eight buttons, mapped to the keyboard (no gamepad support yet)
- Save states: nine slots per ROM, saved and restored from the keyboard, a few KB each
- Debug Web UI: optional web UI showing the PPU and APU internals, activated with
--debug-server - Accuracy: passes Blargg's
cpu_instrssuite anddmg-acid2(pixel-level check)
- Ruby 3.3+
- SDL2 and SDL2_ttf:
- macOS:
brew install sdl2 sdl2_ttf - Linux:
apt install libsdl2-dev libsdl2-ttf-dev
- macOS:
If the libraries live somewhere unusual, point GEMBOY_SDL_DIR at the directory holding them.
git clone https://github.com/kaderate/gemboy.git
cd gemboy
bundle install
bundle exec rspec # optional, verifies the setupbin/gemboy path/to/rom.gb
bin/gemboy --cgb path/to/rom.gbc # force color on a ROM that also supports DMG
bin/gemboy --no-overlay rom.gb # hide the on-screen stats and save state messagesOn macOS, omitting the path opens a file picker.
The model comes from the cartridge's CGB flag: a CGB-only ROM always runs in color, a
DMG-only ROM always in monochrome, and a dual-compatible one runs in DMG mode unless --cgb
is passed.
Battery-backed games write their save next to the ROM as a .sav file, on exit and whenever the
game disables cartridge RAM, which is what it does right after saving.
| Game Boy | Keyboard |
|---|---|
| D-Pad | Arrow keys |
| A | Z |
| B | X |
| Start | Enter |
| Select | Space |
| Emulator | Keyboard |
|---|---|
| Save state slot | 1 to 9 |
| Save into the slot | F5 |
| Restore the slot | F8 |
The bottom-right corner of the window keeps a reminder of those keys. Pressing one replaces it for a few seconds with the selected slot and the date of its last save.
A slot is a file next to the ROM (tetris.s3): the whole machine, gzipped, without the ROM
itself, which keeps it in the low kilobytes even for an 8 MB cartridge. A state carries a digest
of the ROM it came from and refuses to load onto another game.
The cartridge RAM is part of the machine, so restoring an older state rolls back the game's own
save too. To make that recoverable, loading a slot first flushes the live RAM and copies the
.sav to .sav.bak.
bin/gemboy --debug-server path/to/rom.gb # then open http://127.0.0.1:4000
bin/gemboy --debug-server=8080 path/to/rom.gb # on another portThe emulator serves a small page over server-sent events, sampled at frame boundaries:
- PPU — decoded tile data, both tilemaps, and a sprite layer rendering what OAM actually holds
- APU — channel-level, with envelope, length timer and period divider, the wave RAM, and a scope buffer of the mixed and per-channel samples
It costs nothing when the flag is absent (no probe instantiated).
bundle exec rspec # test suite
bundle exec rspec --tag accuracy # reference ROMs, kept out of the default run (~40s)
bundle exec rubocop # linter
ruby test_roms/run_test.rb <rom.gb> <out.png> # one test ROM, headless + screenshot
ruby test_roms/run_all.rb # HTML report over every suitetest_roms/ holds the reference suites (Blargg, dmg-acid2, cgb-acid2, rtc3test). They run headless
and report either through the serial port, by exporting the final framebuffer as a PNG, or by
comparing that framebuffer to a reference image.
debug/ holds throwaway-style scripts built on HeadlessEmulator, which runs a ROM without SDL and
replays a scripted sequence of button presses:
ruby debug/rom_info.rb roms # cartridge header of every ROM in a dir
ruby debug/debug_rtc3test.rb sub_second 90 limiter # one rtc3test suite, screenshotted
ruby debug/debug_zelda_pause_crash.rb # a reproduction script kept as an exampleSee ARCHITECTURE.md for how the emulator is built, the design decisions behind it, and performance notes.
- Pan Docs — Game Boy technical reference
- CPU opcode list
- Blargg's test ROMs
- dmg-acid2 — PPU rendering test
- cgb-acid2 — CGB PPU rendering test
- rtc3test — MBC3 real time clock test
- GBEmulatorShootout — accuracy comparison across emulators
MIT, see LICENSE. The bundled Inter font is under the SIL Open Font License, see assets/fonts/LICENSE-Inter.txt.
Contributions are welcome! Feel free to:
- Open issues for bugs or feature requests
- Submit pull requests with improvements
- Share ideas for optimization or compatibility enhancements
Please ensure tests pass before submitting PRs:
bundle exec rspec