Skip to content

Repository files navigation

Gemboy, a Game Boy emulator written in Ruby

coverage report

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.

Gemboy boot animation
test_roms/homemade/gemboy_logo.asm — a Game Boy Color ROM written for this project

Features

  • Models: DMG-01 and Game Boy Color, picked from the cartridge header (--cgb forces 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 .sav persistence for battery-backed games
  • Real time clock: MBC3 clock driven by emulated cycles, catching up the powered-off delay from the .sav timestamp
  • 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_instrs suite and dmg-acid2 (pixel-level check)

Prerequisites

  • Ruby 3.3+
  • SDL2 and SDL2_ttf:
    • macOS: brew install sdl2 sdl2_ttf
    • Linux: apt install libsdl2-dev libsdl2-ttf-dev

If the libraries live somewhere unusual, point GEMBOY_SDL_DIR at the directory holding them.

Installation

git clone https://github.com/kaderate/gemboy.git
cd gemboy
bundle install
bundle exec rspec   # optional, verifies the setup

Running

bin/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 messages

On 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.

Input mapping

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

Save states

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.

Debug UI

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 port

The 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).

Development

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 suite

test_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 example

See ARCHITECTURE.md for how the emulator is built, the design decisions behind it, and performance notes.

References

License

MIT, see LICENSE. The bundled Inter font is under the SIL Open Font License, see assets/fonts/LICENSE-Inter.txt.

Contributing

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

About

A Ruby GameBoy emulator

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages