A hand-written Game Boy (DMG) emulator in C++20. CPU, memory bus, PPU, sound, and cartridge mappers are all implemented from scratch; SDL2 provides the window, input, and audio.
Requires CMake, a C++20 compiler, and SDL2 headers.
sudo dnf install cmake gcc-c++ SDL2-devel # Fedora
sudo apt install cmake g++ libsdl2-dev # Debian/Ubuntu
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -jThis produces four binaries in build/:
| Binary | Purpose |
|---|---|
gbemu |
the emulator |
gbtests |
unit tests |
gbrom |
headless test-ROM runner (serial output) |
gbshot |
dumps a frame to a PPM image |
./build/gbemu path/to/rom.gb [scale]scale is the window magnification (default 4).
| Button | Key |
|---|---|
| D-pad | Arrow keys |
| A | X |
| B | Z |
| Start | Enter |
| Select | Backspace |
| Quit | Esc |
Battery-backed games save to a .sav next to the ROM: use the in-game save
menu, then quit with Esc.
Implemented
- Full LR35902 instruction set including CB opcodes, cycle timing, the EI delay, and the HALT bug.
- All five interrupts (VBlank, STAT, Timer, Serial, Joypad).
- Background, window, and sprites: priorities, 8x16 sprites, flipping, both object palettes.
- Timer built on the internal DIV counter.
- Four sound channels driven by the frame sequencer.
- MBC0, MBC1, and MBC3 cartridges with battery saves.
Not done, and why
- OAM DMA copies instantly instead of one byte per cycle. Only matters if a game reads OAM mid-transfer.
- Mode 3 length is fixed, not varied by sprite count and scroll, so mid-scanline timing tricks are not cycle-perfect.
- MBC3's real-time clock is present but frozen.
STOPis a two-byte NOP; no speed switch.
Each is marked in the source at the relevant spot.
./build/gbtests # unit tests: CPU, MMU, cartridge
for f in roms/cpu_instrs/*.gb; do ./build/gbrom "$f"; done # Blargg cpu_instrs- Passes all 11 of Blargg's
cpu_instrstests. - Passes dmg-acid2 (PPU accuracy).
- Pokemon Red (MBC3) boots and plays.
