| Mode | Name | Size | |
|---|---|---|---|
| -rw-r--r-- | .gitignore | 57 | logstatsplainblame |
| -rw-r--r-- | Makefile | 513 | logstatsplainblame |
| -rw-r--r-- | README.md | 5829 | logstatsplainblame |
| d--------- | src | 475 | logstatsplain |
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
.savfiles
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.
