A GameBoy emulator written in C++, using the SDL2 library for graphics.

Screenshots

pokemon_red super_mario_land kirbys_dream_land
tetris donkey_kong_land legend_of_zelda_links_awakening

Usage

Requirements

  1. C++ Compiler: Support for C++23 or later.
  2. SDL2 Library: For graphics rendering.
  3. CMake and Make: For building the emulator.

Building the Emulator

Make sure the SDL2 package is available somewhere on your system and run:

mkdir build
cd build
cmake .. -DCMAKE_PREFIX_PATH=<path_to_sdl2_package>
make

Place SDL2.dll from the package in the build folder before running.

Running the Emulator

./gbemu <ROM file>

Controller Mapping

Gameboy Key
Start Enter
Select Backspace
A X
B Z
Up ↑
Down ↓
Left ←
Right →
Speed-up Space (hold)
Pause/Resume P

Implementation Details

Opcodes

There are just over 500 opcodes, and typing them all out is not only boring and error-prone, but it is also highly inefficient as many of them can be grouped together. I have included my Python scripts that I used to automatically generate the C++ code for all of them here.

Timing

All of the components in the GameBoy need to work in synchronization - as the internal timer ticks, each component needs to carry out the correct amount of work. There are 2 main ways to emulate this:

  • CPU driven: The CPU fetches an opcode, carries out the relevant operations and then notifies all other components how many cycles it took so that they can catch up.
  • Timer driven: Each timer tick, every component advances one cycle.

The first method is far easier to implement, and would also result in a faster emulator. However it is not very accurate, and passing many of the tests mentioned below would be impossible. I have therefore chosen to take the timer driven approach, and this is why all the opcodes are implemented in steps.

Absent Features

  • The APU does not function other than to hold the relevant registers. This does not impact any other functionality.
  • Some bugs I have chosen not to implement, like the OAM corruption bug and STAT blocking.
  • OAM and VRAM have inaccessibility windows, but the CPU is currently free to access them at all times.
  • Cartridge types beyond MBC3.
  • Save functionality.

Tests

  • Blargg - Passes 19/27 - all except the OAM bug tests (as not implemented).
  • Mooneye - Passes 78/86 of the tests that DMG-ABC and MBCs 1-3 should pass - all except for:
    • acceptance/interrupts/ie_push
    • acceptance/ppu/hblank_ly_scx_timing-GS
    • acceptance/ppu/intr_2_mode0_timing_sprites
    • acceptance/ppu/intr_2_oam_ok_timing
    • acceptance/ppu/lcdon_timing-GS
    • acceptance/ppu/lcdon_write_timing-GS
    • acceptance/ppu/stat_irq_blocking
    • acceptance/ppu/stat_lyc_onoff
  • rtc3test - Used to get the Real Time Clock working for MBC3.

References

  • PanDocs - Implementations for everything except the CPU mostly utilised the information found here. Note that for the PPU, lots of specific details are missing or glossed over.
  • CPU opcodes - A useful overview of all the CPU opcodes.
  • CPU instruction timings - Information on the general grouping of the CPU opcodes and importantly their timings.