A C++ Game Boy emulator project.
The emulator loads a .gb ROM, runs the CPU on a worker thread, renders the LCD output through SDL2, and opens a second debug window that shows tile data from VRAM.
This project is a work in progress, but the core pieces are already in place:
- CPU instruction decoding and execution.
- MMU/bus routing for cartridge ROM/RAM, VRAM, WRAM, OAM, IO, HRAM, and interrupt enable.
- PPU/LCD timing model with OAM, transfer, HBlank, and VBlank modes.
- Timer, interrupts, DMA, gamepad input, and serial IO registers.
- Cartridge support for ROM-only, MBC1, MBC3, and MBC5 cartridges.
- Battery-backed RAM loading/saving for supported cartridge types.
- SDL2 UI with main display and VRAM tile debug display.
- GoogleTest target for library tests.
The checked-in roms/ directory contains public test ROMs that are useful for emulator validation. The games/ directory is local/private content and should be treated separately from distributable project assets.
The executable starts by loading cartridge metadata from the ROM header and selecting a cartridge implementation. It then creates the CPU with the initial Game Boy register state, starts CPU execution on a std::jthread, and keeps the SDL UI loop on the main thread.
At runtime:
- The CPU fetches, decodes, and executes instructions.
- Memory reads and writes go through the MMU.
- The MMU forwards accesses to cartridge banking, RAM, PPU memory, LCD/device registers, DMA, and interrupt registers.
- The timer and PPU advance with CPU cycles.
- The PPU fills a 160x144 video buffer.
- SDL scales that buffer into the main window and renders a separate VRAM tile viewer.
| Game Boy input | Keyboard |
|---|---|
| D-pad | Arrow keys |
| A | X |
| B | Z |
| Start | K |
| Select | Enter |
Close either SDL window to stop the emulator.
- CMake 3.26 or newer.
- Ninja.
- A compiler with C++23 support.
- Conan 2.
Conan resolves the C++ dependencies declared in conanfile.py:
- Boost.
- SDL2.
- SDL2_ttf.
- GoogleTest.
- portable-file-dialogs.
When using a custom GCC installation, such as GCC 16 under /opt/gcc, the build embeds that compiler's libstdc++ runtime path into the emulator binary. This avoids runtime errors where Linux finds an older system libstdc++.so.6 first.
Install dependencies, configure, and build the release preset:
conan install . --output-folder=build-release/conan --settings=build_type=Release --build=missing
cmake --fresh --preset Release
cmake --build build-releaseThe emulator binary is produced at:
build-release/app/emulator
Run the emulator and load a ROM from the filesystem using L, Enter, or Ctrl+O:
./build-release/app/emulatorYou can still start directly with a Game Boy ROM:
./build-release/app/emulator roms/cpu_instrs.gbOr use a local game ROM:
./build-release/app/emulator games/dr_mario.gbThe app opens two SDL windows:
gbemu_cpp: scaled Game Boy screen.- Debug tile window: VRAM tile viewer.
ROM loading shortcuts:
| Action | Keyboard |
|---|---|
| Open ROM file dialog | L, Ctrl+O, or Enter when no ROM is loaded |
Build first, then run CTest from the release build directory:
ctest --test-dir build-release/lib/testsYou can also run the GoogleTest binary directly:
./build-release/lib/tests/test_gbemu_libThe historical manual CPU-log comparison note was:
vimdiff <(head -n 200000 "log2.txt") <(head -n 200000 "/path/to/reference/log2.txt")That workflow is not automated in the repository yet.
Install debug dependencies and configure the debug build directory:
conan install . --output-folder=build-debug/conan --settings=build_type=Debug --build=missing
cmake --fresh --preset Coverage
cmake --build build-debugapp/
emulator.cpp SDL app entry point and emulation loop
gameboy_ui.h SDL rendering, debug tile window, and keyboard input
lib/include/
cartridge/ ROM header parsing, cartridge interface, MBC1/MBC3/MBC5, battery save support
cpu/ CPU context, instructions, interrupt handling, timer
io/ LCD, gamepad, serial/device registers
mmu/ Memory map, DMA, RAM
ppu/ PPU state machine, tile fetch pipeline, OAM
utils/ Logging and ROM loading helpers
lib/src/ Main library implementation
lib/tests/ GoogleTest target
roms/ Test ROMs
docs/images/ README diagrams
- Test coverage is minimal and does not yet encode the manual validation workflow.
- Cartridge support is focused on ROM-only, MBC1, MBC3, and MBC5.
- Audio/APU emulation is not implemented.
- Some IO and memory edge cases are still logged as unsupported.
- The README diagrams describe the current implementation; they are not emulator screenshots.
The original project structure is a good foundation: the emulator is already split around the main Game Boy hardware concepts, with separate areas for CPU, MMU, PPU, IO, cartridge handling, and the SDL app. That layout made it practical to add ROM loading, resizable rendering, input overlays, and MBC5 support without rewriting the whole project.
The next improvements should focus on reducing coupling between those pieces while keeping the existing design recognizable.
- Introduce a
GameBoyorEmulatorfacade that owns the core components and exposes a small API for loading ROMs, ticking frames, reading the framebuffer, handling input, and saving state. - Keep SDL, menus, file dialogs, overlays, and debug windows in the app layer so the emulation core can also run headless.
- Move timing-sensitive behavior, such as DMA progression, into explicit implementation files and give it focused tests.
- Share common cartridge mapper behavior for RAM banking, battery-backed saves, and bounds-safe ROM/RAM access.
- Add automated test ROM execution with pass/fail detection.
- Capture real screenshots for
dmg-acid2.gb,cpu_instrs.gb, and a small homebrew ROM. - Document compatibility by ROM and cartridge type.
- Add CI for configure, build, and tests.
Additional design notes:
