SearchA-ZG › Game Boy Color

Game Boy Color

1998 Open source · GPL-3.0 Online

This is the Nintendo Game Boy Color (CGB) running directly in your browser, in colour. It is powered by GameBoy-Online, Grant Galitz's pure-JavaScript Game Boy / Game Boy Color emulator: an interpreter of the Sharp SM83 (LR35902) CPU with a scanline picture processor that drives the Color hardware's per-tile palettes, second VRAM bank and double-speed mode. Here it is wired into the in-page debugger, where you can single-step the SM83, set breakpoints, and inspect the whole memory map.

Visit the official site ↗

Runs on: Web browser

Game Boy Color Online Emulator

Play Game Boy Color using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
µCityGame Boy ColorGame Boy ColoropenOpen ⛶

Machines emulated

Chips

Notes

Embedding

GameBoy-Online (Grant Galitz) is a pure-JavaScript Game Boy / Game Boy Color emulator whose whole machine is one big object, GameBoyCore. Vendor GameBoyCore.js and drive the machine yourself instead of loading GameBoyIO.js: that companion file owns a setInterval loop plus audio and save-state plumbing that cannot be paused or single-stepped, which the debugger needs.

Construct and boot. The core wants the cartridge as a binary string (one character per byte), not an ArrayBuffer. It also reaches for three page globals - settings (an options array), and no-op cout / pause functions, so define them first. Turn sound off (settings[0]=false) and skip the boot ROM (settings[1]=false) so it jumps straight into the game at 00 in Color mode:

var settings = [false, false, false, 1, true, false, 8, 10, 20, false, false, false, false, true, [true,true,true,true]];
function cout(){}
function pause(){}
var u8 = new Uint8Array(romBuffer), s = '';
for (var i=0; i<u8.length; i++) s += String.fromCharCode(u8[i]);
var gb = new GameBoyCore(canvas, s);
gb.start();                        // initMemory, ROMLoad, initLCD (draws itself), initSound
gb.programCounter = 0x0100;

The machine is plain fields. Everything the debugger needs is a live property; no wasm heap:

MemberKindWhat it does
executeIteration()methodThe interpreter loop: runs SM83 instructions until emulatorTicks >= CPUCyclesTotal. Set CPUCyclesTotal=1 and it runs exactly one instruction, the single-step primitive.
registerA/B/C/D/EfieldThe 8-bit registers. H and L are one combined field, registersHL.
FZero/FSubtract/FHalfCarry/FCarryfieldThe four flag bits, kept as booleans rather than packed into F.
programCounter / stackPointerfieldPC and SP, live 16-bit values.
memoryReader[a](gb,a) / memoryWrite(a,v)methodThe routed CPU bus, reflects the current ROM/VRAM/WRAM bank, so disassembly follows PC across bank switches.
memoryfieldThe raw 64 KB store (current VRAM bank, OAM, I/O, HRAM) as a Uint8Array - side-effect-free reads.
ROMfieldThe whole decoded cartridge as a byte array, a pure ROM view for the disassembler.
cGBCfieldColor mode flag, set from the cartridge header byte 43 at load.
JoyPadEvent(code, down)methodFeed input. Codes: 0 Right, 1 Left, 2 Up, 3 Down, 4 A, 5 B, 6 Select, 7 Start.

Because the CPU, PPU and memory are ordinary JavaScript, the debugger single-steps with one bounded executeIteration(), reads and writes registers straight off the core, and implements breakpoints and watchpoints as host-side checks around those calls, with no changes to the emulator core.

Debugger integration

Wiring GameBoy-Online into the shared in-browser debugger needed one piece of custom work, a boot shim that owns the loop, plus a set of techniques for reaching into the live machine.

A boot shim, because the loop lives in another file. Upstream, GameBoyIO.js drives the core from a setInterval that cannot be paused or single-stepped. We drop that file and drive the core's own executeIteration() from a loop we control. The trick is that the interpreter loop exits when emulatorTicks reaches CPUCyclesTotal, so we simply choose that budget - a whole video frame to run, or one instruction to step:

// run exactly one SM83 instruction
function stepInsn(){
  gb.stopEmulator = 0; gb.emulatorTicks = 0; gb.CPUCyclesTotal = 1;
  gb.executeIteration();            // fetch, execute, tick PPU/timers, then stop
}
// run one frame, halting on a PC breakpoint when any are set
function frame(){
  if (bps.size){
    var budget = 70224;
    while (budget > 0){
      if (bps.has(gb.programCounter)){ running = false; break; }
      stepInsn(); budget -= gb.CPUTicks;
    }
  } else { gb.stopEmulator=0; gb.emulatorTicks=0; gb.CPUCyclesTotal=70224; gb.executeIteration(); }
  gb.requestDraw();               // the core paints its own canvas
}

Techniques for deeper access. The plug-in reaches into the running machine through the core's own surfaces; no fork:

  • Composed registers. This core keeps F as four booleans and H:L as one 16-bit field, so the plug-in packs Z/N/H/C into the F byte and splits registersHL into H and L for the register grid, with setters that write straight back.
  • Side-effect-free reads. VRAM, OAM and I/O are read straight off the raw memory array; the cartridge view reads the decoded ROM array. Only the CPU-bus and banked-WRAM views use the routed memoryReader, so disassembly follows PC across ROM bank switches.
  • Single instruction step. Bounding executeIteration() to CPUCyclesTotal=1 runs one instruction with the PPU, timers and interrupts all clocked coherently.
  • Execution breakpoints. Because we own the loop, a breakpoint is a host-side Set of PC values checked before each step; when breakpoints are present the frame runs instruction-by-instruction and halts the moment PC matches.
  • Write watchpoints. The core dispatches writes through a compiled memoryWriter[] jump table, so the shim wraps the watched entries (re-applying them whenever the table is recompiled on a bank switch) to pause on a store.

Everything the debugger shows, registers read/write, memory hex/disassembly, follow-PC, single-step, breakpoints and watchpoints, is built from these, with no changes to the emulator itself.

Architecture

GameBoy-Online is Grant Galitz's interpreted Game Boy Color emulator. Unlike more object-split cores, almost the entire machine lives on one GameBoyCore instance:

  • CPU - a Sharp SM83 (LR35902) interpreter. executeIteration() fetches through a per-address memoryReader jump table, dispatches a flat OPCODE[] table, and advances the PPU, timers, serial and interrupts inline. It boots at 00 with the documented post-boot register state (the GBC boot ROM / logo is skipped).
  • PPU - a scanline renderer with the Game Boy Color's per-tile palettes and a second VRAM bank, compositing background, window and sprites into a 160×144 framebuffer that the core swizzles and blits to its own canvas.
  • APU - the four sound channels, resampled to Web Audio when enabled (disabled here for headless stepping).
  • Memory - compiled memoryReader[] / memoryWriter[] jump tables over the cartridge MBC, dual VRAM banks, seven switchable GBC work-RAM banks, OAM, I/O and HRAM, recompiled on bank switches.
  • Color - the cGBC flag is read from the cartridge header at 43; a CGB cartridge lights up the colour palettes, double-speed mode and the extra RAM banks.

Each frame runs the interpreter for one video frame's worth of clocks, then paints the finished framebuffer. Because the machine is one ordinary object, its whole state is inspectable, which is what the debugger reads each refresh.