aboutsummaryrefslogtreecommitdiffstats
ModeNameSize
-rw-r--r--.gitignore57logstatsplainblame
-rw-r--r--Makefile513logstatsplainblame
-rw-r--r--README.md5829logstatsplainblame
d---------src475logstatsplain

gbc — a Game Boy Color emulator in C, rendered to the terminal

A from-scratch Game Boy / Game Boy Color emulator written in plain C11. It renders graphics directly to the terminal using 24-bit (truecolor) ANSI escape codes and the Unicode upper-half-block trick (): each character cell packs two vertical pixels (foreground = top pixel, background = bottom pixel), giving a 160×144 GB screen in 160×72 terminal cells.

To keep terminal load low, output is minimized two ways: SGR color escapes are only emitted when the fg/bg changes, and inter-frame diffing redraws only the cells that changed since the previous frame (jumping over unchanged runs with ESC[row;colH). A fully static screen writes nothing after the first frame; on the acid2 screens this cut output from ~7.1 MB to ~65 KB over 2 s (~108×). Terminal resize (SIGWINCH) triggers a full repaint.

Build & run

make
./build/gbc path/to/rom.gb        # interactive
./build/gbc rom.gb --test 30      # headless: run 30s, log serial (test roms)
./build/gbc rom.gb --shot 60 out.ppm   # run 60 frames, dump a PPM screenshot

Requires a truecolor-capable terminal (COLORTERM=truecolor) at least 160 columns wide.

Speed & frame skipping

The emulator always advances every Game Boy frame at the correct rate, so games run at their intended speed. Because writing a frame to the terminal is by far the most expensive step, drawing can be decoupled from emulation via frame skipping — the machine keeps running in real time while the terminal is refreshed less often:

./build/gbc rom.gb --frameskip 2   # emulate 60 fps, draw every 3rd frame (20 fps)
./build/gbc rom.gb --fps 120        # run the game at 2x speed
./build/gbc rom.gb --uncapped       # run as fast as possible (turbo)
Flag Meaning
--frameskip N draw 1 of every N+1 emulated frames (default 0)
--fps N emulation speed cap in fps (default 59.73 native)
--uncapped/--turbo run emulation as fast as possible

Frame skip and turbo are also adjustable live with [ / ] and f; in turbo mode terminal redraws are additionally clamped to ~60 Hz. On exit the emulator prints how many frames it emulated vs. actually drew.

Controls

Key Button
arrows / WASD D-pad
z / j A
x / k B
enter Start
space Select
f toggle fast-forward (turbo)
[ / ] decrease / increase frame skip
q / Ctrl-C Quit

Since terminals don't report key-release, buttons are held for a few frames after each keypress (relying on the terminal's key-repeat for sustained holds).

External control channel (FIFO)

Start with --fifo [path] (default /tmp/gbc.fifo) to open a named pipe that accepts button commands from any process. Unlike the terminal keyboard, this can send true key-release events, so it gives deterministic input for scripting and automated testing.

./build/gbc rom.gb --fifo /tmp/gbc.fifo &
echo 'a' > /tmp/gbc.fifo          # momentary tap of A
echo 'start' > /tmp/gbc.fifo      # tap Start
echo '+left' > /tmp/gbc.fifo      # press and HOLD Left
echo 'a b' > /tmp/gbc.fifo        # tap A and B together (Left still held)
echo '-left' > /tmp/gbc.fifo      # release Left
echo 'release' > /tmp/gbc.fifo    # release everything held
echo 'a:30' > /tmp/gbc.fifo       # tap A, held for 30 frames

Commands (one line, space/comma separated tokens, case-insensitive):

Token Effect
a b start select up down left right momentary tap
name:N tap held for N frames
+name press and hold until released
-name release a held button
release release all held buttons

Set GBC_INPUT_DEBUG=1 to trace resulting joypad state to stderr.

Architecture

File Responsibility
types.h fixed-width integer typedefs
cart.c ROM loading, header parsing, MBC1/2/3/5, battery saves
gb.c system bus / memory map, joypad, OAM-DMA, HDMA/GDMA, reset
cpu.c Sharp SM83 core (full opcode set, cycle-accurate accesses)
timer.c DIV/TIMA/TMA/TAC with falling-edge accuracy
ppu.c LCD controller, scanline renderer (BG/window/sprites), CGB
render.c terminal truecolor output + raw-mode keyboard input
main.c argument parsing, frame loop, timing, screenshot/test modes

Emulation status

Passing:

  • blargg cpu_instrs — all 11 sub-tests
  • blargg instr_timing
  • blargg mem_timing / mem_timing-2 — all 3
  • dmg-acid2 — PPU test renders correctly
  • cgb-acid2 — CGB PPU test renders correctly

Implemented:

  • Full SM83 CPU incl. HALT bug, EI delay, interrupt dispatch
  • Accurate timer edge behavior
  • PPU modes 0–3, STAT/LYC/VBlank interrupts, window line counter
  • CGB: double VRAM banks, BG/OBJ color palettes, tile attributes, master priority, WRAM banking, double-speed (KEY1), HDMA/GDMA
  • MBC1/2/3/5 with RAM banking and battery .sav files

Not yet implemented:

  • APU (audio) — registers are stubbed
  • MBC3 RTC advancement
  • Sub-instruction PPU FIFO timing edge cases

License

For educational use. Test ROMs are downloaded separately and not included.