A Game Boy emulator written entirely in the Nix expression language. It
interprets the LR35902 CPU, emulates memory and cartridge banking, renders the
160×144 display, and prints frames from the evaluator with builtins.trace.
This is deeply cursed. It also works.
ROMs are not included. Bring your own legally obtained Game Boy ROM.
The ROM is decoded into a Nix list, then nix/gb.nix advances the machine one
input frame at a time. The CPU interpreter handles instructions and interrupts,
the memory bus handles RAM, the joypad, DMA, and MBC1/MBC3/MBC5 bank switching,
and the PPU renders backgrounds, windows, and sprites.
Rendered frames go to stderr as Unicode terminal graphics. By default they include a small CPU display and upcoming-input timeline. Timing is approximate and there is no audio.
Convert a ROM to a continuous ASCII hex dump. This is necessary because Nix strings cannot contain the ROM's NUL bytes.
xxd -p game.gb | tr -d '\n' > game.hexlet
state = import ./nix/gb.nix {
romHex = ./game.hex;
traceEvery = 1;
panel = true;
input = [
{ frames = 600; keys = {}; }
{ frames = 4; keys = { start = true; }; }
{ frames = 60; keys = {}; }
{ frames = 8; keys = { left = true; }; }
];
};
in
state.cyclesThen run it:
nix-instantiate --eval ./game.nixEach input segment holds its keys for the given number of frames. Available
buttons are a, b, start, select, up, down, left, and right;
omitted buttons are released.
Set panel = false for the Game Boy screen alone, or traceEvery = 0 to
disable frame output. To page through traced frames:
nix-instantiate --eval ./run.nix 2>&1 >/dev/null | lessnix/gb.nix— top-level emulator and input/frame loopnix/cpu.nix— LR35902 interpreternix/mem.nix— memory bus, joypad, DMA, and cartridge bankingnix/ppu.nix— display renderer and terminal outputnix/rom.nix— ASCII hex ROM loadernix/state.nix— JSON checkpoint helpersnix/bits.nix— byte, word, and bit operations