1. Executive mental model
Think of SameBoy as a single monolithic state machine (GB_gameboy_t) advanced by bus-cycle-accurate CPU emulation, with peripheral hardware (PPU, APU, timers, DMA, MBC) synchronized through a central GB_advance_cycles() hub. Frontends are thin hosts that call GB_run() in a loop and receive pixels, audio samples, and events through C callbacks.
Key insight: GB_run() does not emulate a full frame. It runs one CPU quantum (one instruction, interrupt service, halt cycle, or STOP-mode tick). Frontends loop until vblank_just_occured is set, or libretro uses GB_run_frame() which loops internally. Video presentation happens in the vblank_callback, not inside GB_run().
External references: Pan Docs (hardware spec), DeepWiki SameBoy index, BESS save-state format.
2. Repository map
What kind of project this is
SameBoy is a native desktop/mobile emulator — not a web app, not a server. It ships multiple GUI frontends around one shared emulation core, plus a headless tester, libretro core, and open-source boot ROMs.
C Objective-C Makefile rgbds SDL2 Metal/OpenGL
Top-level directories
| Path | Role | Central vs peripheral |
|---|---|---|
Core/ | Emulation engine: CPU, memory, PPU (display.c), APU, MBC, timing, debugger, save states, cheats, rewind, SGB HLE | Central |
SDL/ | Cross-platform frontend (Linux, Windows, BSD). Entry: main.c | Peripheral (host) |
Cocoa/ | macOS native app with debugger UI, link cable, camera. Entry: main.m → GBApp.m | Peripheral |
iOS/ | Touch UI, Metal rendering, ROM library. Entry: main.m | Peripheral |
libretro/ | RetroArch core. Entry: retro_run() in libretro.c | Peripheral |
AppleCommon/ | Shared Metal/GL view, audio client for Cocoa+iOS | Glue |
BootROMs/ | SameBoot — rgbds assembly sources for DMG/CGB/SGB boot ROMs | Supporting assets |
Tester/ | Headless ROM runner for CI regression (screenshot hashing) | Test infra |
Shaders/ | GLSL scaling filters for SDL | Peripheral |
JoyKit/ | Game controller abstraction (macOS) | Peripheral |
Misc/, HexFiend/, QuickLook/, Windows/, FreeDesktop/ | Utilities, hex editor plugin, Quick Look, Windows helpers, .desktop files | Peripheral |
.github/workflows/ | CI: build all targets + sanity ROM tests | Infra |
Entry points and configuration
| File | Type |
|---|---|
SDL/main.c:main() | SDL CLI/GUI entry |
Cocoa/main.m | macOS NSApplicationMain |
iOS/main.m | iOS UIApplicationMain |
libretro/libretro.c:retro_init/load_game/run | Libretro API |
Tester/main.c:main() | Headless test runner |
Makefile | Primary build system |
version.mk | VERSION := 1.0.3 |
sameboy.pc.in | pkg-config template for libsameboy |
3. Main runtime architecture
Core abstractions
GB_gameboy_t— opaque (public) or fully expanded (internal) struct holding all hardware state, split intoGB_SECTIONblocks for save states.GB_model_t— DMG, MGB, SGB, SGB2, CGB, CGB0, AGB, etc. (Core/model.h).- Callback function pointers in the
unsavedsection — bridge to frontends without platform deps in Core. GB_UNITstate machines intiming.h— batchable per-component timing (display, div, APU channels).
Startup lifecycle
GB_init(gb, model)— zero state, allocate WRAM/VRAM by model, set defaults, callGB_reset(), load default border tiles.- Frontend registers callbacks (
GB_set_vblank_callback,GB_apu_set_sample_callback, etc.). GB_load_rom()— mmap/copy ROM, parse header viaGB_configure_cart(), set MBC type.GB_load_battery()— restore MBC RAM + RTC from.sav.- Boot ROM load via
boot_rom_load_callback(frontend provides file or embedded ROM). - Main loop:
GB_run()until exit.
4. Key execution flows
Five end-to-end paths with dedicated walkthrough pages:
while(true) GB_run()
Flow B — CPU/memory cycle
GB_cpu_run → cycle_read → GB_read_memory → GB_advance_cycles
Flow C — PPU frame
GB_display_run state machine → line render → GB_display_vblank → callback
Flow D — Save state
Section serialization → external RAM blobs → optional BESS footer
Flow E — Libretro frame
retro_run → input poll → GB_run_frame → video_cb / audio_batch_cb
5. Core modules
Core/ — emulation engine
| Module | Responsibility | Key API | Depends on |
|---|---|---|---|
gb.c | Orchestration, ROM/battery I/O, borders, GB_run | GB_init, GB_run, GB_load_rom | All subsystems |
sm83_cpu.c | SM83 CPU, opcode table, bus conflicts | GB_cpu_run (internal) | memory, timing |
memory.c | Address decode, OAM bug, DMA | GB_read_memory, GB_write_memory | mbc, display, apu |
mbc.c | Cartridge banking MBC1-7, HuC, TPP1, camera | GB_configure_cart, GB_reset_mbc | — |
display.c | PPU — LCD timing, FIFO rendering, palettes | GB_display_run, GB_display_vblank | timing, memory |
apu.c | 4-channel audio, band-limited synthesis, 96kHz output | GB_apu_run, GB_apu_set_sample_callback | timing |
timing.c | GB_advance_cycles hub, DIV/TIMA, serial, turbo sync | GB_advance_cycles | joypad, apu, display, dma |
joypad.c | Key matrix, debouncing, faux analog | GB_set_key_state, GB_joypad_run | timing |
save_state.c | Native + BESS save/load | GB_save_state, GB_load_state | gb.h layout |
debugger.c | CLI debugger, breakpoints, symbols | GB_debugger_run, GB_debugger_break | sm83_disassembler |
sgb.c | Super Game Boy HLE (border, palettes, intro) | Internal | display, apu |
rewind.c | Compressed rewind ring buffer | GB_rewind_push/pop | save_state (no BESS) |
Frontends — glue, not business logic
SDL (SDL/main.c, gui.c, audio.c), Cocoa (Cocoa/Document.m, GBView.m), iOS (GBViewController.m), libretro (libretro.c). Each translates OS events → GB_set_key_state and callbacks → OS audio/video APIs. No emulation accuracy logic lives here.
6. Data model and persistence
In-memory state (GB_gameboy_t sections)
| Section | Contents | Saved? |
|---|---|---|
header | Magic, version (15) | Yes |
core_state | Registers AF–PC, IME, flags, model | Yes |
dma | OAM DMA, HDMA state | Yes |
mbc | Bank registers, rumble, camera regs | Yes |
hram | HRAM + I/O registers FF80–FFFF | Yes |
timing | DIV, TIMA, serial, joypad bounce | Yes |
apu | Channel state (not output buffer) | Yes |
rtc | MBC3/TPP1 real-time clock | Yes |
video | VRAM pointer is external; OAM, palettes, PPU state saved | Partial + blob |
unsaved | ROM/RAM pointers, callbacks, debugger, rewind | No |
External heap blobs: rom, ram, vram, mbc_ram — serialized as trailing blocks in save files.
File formats
- ROM — raw
.gb/.gbc; header parsed at0x0100–0x014F - Battery —
.savbeside ROM (MBC RAM + RTC); multiple legacy formats supported ingb.c - Save state — section-ordered binary + optional BESS footer for cross-emulator compatibility
- Cheats —
.chttext format - SDL prefs — binary
prefs.bin(SDL/configuration.c) - Cocoa/iOS —
NSUserDefaultskeys likeGBEmulatedModel,GBRight - Libretro — core options via
RETRO_ENVIRONMENT_GET_VARIABLE
7. External interfaces
What the system talks to
| Interface | Used by | Purpose |
|---|---|---|
| Filesystem | All | ROM, .sav, save states, boot ROMs, cheats, symbols |
| SDL2 / Metal / OpenGL | SDL, Apple frontends | Window, input, GPU rendering, shaders |
| Core Audio / SDL Audio / AVAudioEngine | Cocoa, iOS, SDL | 96 kHz PCM output |
| Libretro API | libretro.c | RetroArch integration |
Host clock (time()) | RTC, WorkBoy | Real-time clock advancement |
| Serial callbacks | Cocoa link cable, printer, WorkBoy | Bit-banged serial between two GB instances or accessories |
| Camera callbacks | Cocoa, iOS | Game Boy Camera sensor pixels |
No network, database, or cloud services in the core product.
8. Error handling and edge cases
Patterns
- Return codes:
GB_load_rom,GB_save_state,GB_load_batteryreturn0on success, negative on failure. - Assertions:
GB_ASSERT_NOT_RUNNINGprevents re-entrantGB_run; debug-only thread checks on callback registration. - Logging:
GB_log(gb, fmt, ...)→ optionallog_callback; SDL can capture to message boxes. - Tester crash detection: stack overflow, halt-with-IE=0 deadlock, infinite
0xFFat0x38. - Graceful degradation: unreadable
.savpath logs warning; RTC reset if timestamp implausible.
Failure modes: corrupt ROM header → GB_configure_cart may default to ROM-only; save state version mismatch → load rejected; audio backpressure in SDL drops samples when queue > freq/8.
9. Concurrency and lifecycle
Threading model
- Core is single-threaded.
running_thread_idtracks the emulation thread; callbacks must not callGB_runre-entrantly. - Cocoa: emulation on
NSThread(Document.m -run); UI on main thread; atomic blocks for debugger. - iOS: serial GCD queue
_runQueueforGB_run. - SDL: single-threaded main loop; input polled via
update_input_hint_callbackfrom timing code. - Libretro: host-thread driven; no internal threads.
- Tester: optional
--jobs Nforks processes (Unix only).
Turbo mode skips PPU rendering and may skip vblank callbacks unless enable_skipped_frame_vblank_callbacks is set. Rewind pushes state on each vblank (compressed).
10. Tests
Framework and coverage
Framework: Custom headless runner (Tester/main.c), not unit tests. CI compares SHA1 of output BMP screenshots.
CI ROMs (.github/actions/sanity_tests.sh): cgb_sound.gb, cgb-acid2.gbc, dmg-acid2.gb, dmg_sound-2.gb, oam_bug-2.gb.
Run locally:
make tester CONF=release
./build/bin/tester/sameboy_tester --cgb --length 10 path/to/rom.gb
Covered: PPU accuracy (Acid2), APU (blargg sound ROMs), OAM bug, basic crash detection.
Uncovered: Individual MBC edge cases, debugger commands, frontend UI, link cable timing, most cheat paths — no automated tests found.
11. Build, run, and deploy
Commands
# Dependencies: clang, make, rgbds, cppp, libsdl2 (SDL port)
git clone https://github.com/LIJI32/SameBoy.git
cd SameBoy
make # sdl on Linux, cocoa on macOS
make all CONF=release # sdl + tester + libretro + lib (+ cocoa on macOS)
make -C libretro # libretro core only
./build/bin/SDL/sameboy game.gb
# CI equivalent
make -j all CONF=release
.github/actions/sanity_tests.sh
Deploy: GitHub Actions builds artifacts on push to master; libretro branch auto-synced via update_libretro.sh; macOS releases as .app, iOS as IPA/deb; FreeDesktop make install.
Config: CONF=debug|release|native_release|fat_release, BOOTROMS_DIR=, DATA_DIR=, DISABLE_DEBUGGER=1 (lib only).
12. Most important files to understand first
Ranked list
Core/gb.h— entire public API and state layoutCore/gb.c—GB_init,GB_run, ROM/battery lifecycleCore/timing.c—GB_advance_cycles, the synchronization hubCore/sm83_cpu.c— CPU execution and bus-cycle timingCore/memory.c— memory map, side effects, OAM bugCore/display.c— PPU (largest accuracy-critical file)Core/apu.c— audio generationCore/mbc.c— cartridge hardwareCore/save_state.c— persistence formatSDL/main.c— reference frontend integrationlibretro/libretro.c— alternative frame-driven host modelMakefile— how everything is wired together
13. Things that are confusing or risky
Non-obvious insights
- 8 MHz cycle units: Internal cycle counters use 8 MHz units; DMG divides by 2 in
GB_advance_cycleswhen not in double-speed CGB mode. GB_run≠ one frame: Easy to misread; useGB_run_frameor loop onvblank_just_occured.- Bus conflicts:
sm83_cpu.chas per-register conflict maps for DMG/CGB/SGB — changing I/O timing can break Acid2-style tests. - PPU state machine: 20+ states in
display.c; STAT mode bits intentionally delayed 4 T-cycles (see TODO in source). - Opaque struct: Public builds hide
GB_gameboy_tlayout; defineGB_INTERNALwhen hacking Core. - Libretro strips features: Built with
GB_DISABLE_REWIND,GB_DISABLE_DEBUGGER, etc. - Band-limited init:
apu.cuses__attribute__((constructor))— runs at library load. - SGB intro hack: Special path in
GB_runadvances PPU without CPU during intro animation. - Save state version:
STRUCT_VERSION 15— bumping requires migration logic insave_state.c.
14. Suggested learning path
Ordered reading list
- Read
README.mdand skim DeepWiki overview - Study
Core/gb.h—GB_SECTIONlayout and public functions - Trace
GB_init→GB_resetingb.c - Read
GB_runand follow intoGB_cpu_run(sm83_cpu.c) - Understand
GB_advance_cyclesintiming.c - Pick one bus operation:
GB_read_memoryfor I/O register side effects - Skim
display.cGB_display_runstate machine header - Read SDL
run()inSDL/main.cfor callback wiring - Run
make testerondmg-acid2.gband inspect output BMP - Explore
debugger.cif modifying CPU or memory behavior
15. Open questions
Cannot be determined from code alone
- Exact hardware behavior for several TODO-marked timing cases (ISR IF read timing, STOP mode DMA, MGB OAM bug variants).
- Whether Windows
testertarget is intentionally broken (Makefile links incomplete object set). - Production release cadence and signing process for macOS/iOS builds.
- Which real-hardware tests were used to validate each
bitwise_glitchOAM corruption variant. - Performance characteristics on low-end ARM without profiling data in repo.