ninety-four

ninety-four is a native PC port of NHL '94 for Sega Genesis Mega Drive that runs at 60 frames per second and includes save states and rewind functionality.

README

ninety-four ๐Ÿ’

NHL '94, rebuilt in C.

The greatest hockey game ever made, running natively on your machine, with no emulator in sight. All of the roughly 1,500 routines of EA's 1993 Sega Genesis classic have been rewritten by hand in C and checked against the original, frame by frame, until the two can't be told apart. The same one-timers, the same diving saves, the same organ, the same Gretzky. Smooth 60 frames a second, without the cartridge's slowdown.

And things the cartridge never had: save in the middle of a game (F7), load it back (F8), and hold F4 to rewind the goal you just gave up. Hold F6 to fast-forward, slow the replay of that one-timer down, step through it a frame at a time, and press F12 to keep it.

Bring your own cartridge dump and you're on the ice in a minute (what to install first is under Build):

git clone --recursive https://github.com/1nputs/ninety-four.git && cd ninety-four
make nhl94
./build/nhl94          # the first time, it asks for your ROM: the 1 MB retail cartridge (US/Europe)

The first time, pick your ROM in the game's file browser (or give it with --rom FILE); it is remembered after that.

Player 1Player 2
D-padarrowsI J K L
A / B / CZ / X / CQ / W / E
StartEnterTab

Game controllers work too (the first two are players 1 and 2).

Key
F1mute
F2 / F3volume down / up
F4 (held)rewind
F5slow motion on / off
F6 (held)fast-forward
F7 / F8save / load a state
F9a frame at a time, while paused
F10pause
F11 or Alt+Enterfull screen
F12screenshot (a PNG in screenshots/)
Escthe menu

The menu (Esc, or a controller's Back button; arrows and Enter, or the d-pad, A and B) has the ROM, a reset, states, the video (sharp, integer or fit scaling; square pixels or 4:3; full screen), the sound (volume, and the FM sound core and chip: hear the difference as you play), options, and every key of both pads and the controls: pick one and press the new key.

All of it is kept in settings.ini, in the folder you run the game from, beside its saves (you can also edit it while the game is closed). The game pauses when its window loses focus (an option in the menu). On a Mac, hold Fn for the function keys.

Build

The hardware side is pulseman, our Mega Drive runtime, included as a git submodule in ext/pulseman: clone with --recursive, or after a plain clone run git submodule update --init. make nhl94 builds it too. Besides that you need GCC (C and C++), make, pkg-config and SDL2. The game itself must be built with GCC for now: its RAM layout uses a GCC-only attribute until the planned WebAssembly work replaces it. Your saves and states go to out/ in this repository.

Linux (built and tested here, Debian 13, GCC 14):

sudo apt install build-essential pkg-config libsdl2-dev          # Debian, Ubuntu
sudo dnf install gcc gcc-c++ make pkgconf-pkg-config SDL2-devel  # Fedora
sudo pacman -S --needed base-devel sdl2                          # Arch
make nhl94
./build/nhl94 --rom "/path/to/NHL 94.gen"

macOS (not tried yet). Apple's gcc is really Clang, so install GCC from Homebrew and name it:

brew install gcc sdl2 pkg-config
make CC=gcc-15 CXX=g++-15 nhl94          # the version brew installed: ls $(brew --prefix)/bin/gcc-*
./build/nhl94 --rom "/path/to/NHL 94.gen"

Windows (cross-built here with MinGW-w64 GCC 14 without a warning, not yet run on Windows), with MSYS2, in its UCRT64 shell:

pacman -S --needed git make mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-pkgconf mingw-w64-ucrt-x86_64-SDL2
make CC=gcc CXX=g++ nhl94
./build/nhl94.exe --rom "C:/path/to/NHL 94.gen"

The C++ runtime is linked in, so outside the MSYS2 shell build/nhl94.exe needs only SDL2.dll beside it (cp /ucrt64/bin/SDL2.dll build/).

If a compiler newer than these warns about something, make WERROR= nhl94 builds anyway; please tell us what it said.

For agents: where this stands (16 October 2026)

  • What it is. A hand-written C reimplementation of NHL '94 (Genesis, US/Europe retail, SHA-1 7b1489ab501258133bf03e5f0892b77d04312580). src/ has one C file per asm module of the community's byte-exact rebuild (34 modules, about 1,500 routines, the same names). The Z80's 661-byte sound relay is ported too (src/z80.c). It runs on pulseman (1nputs/pulseman, MIT, the git submodule ext/pulseman; its C API keeps the mdrt_ prefix): the VDP, PSG, YM2612, pads and save RAM as C modules, with an SDL2 platform. There is no 68k or Z80 emulation. A change to pulseman is committed and pushed there, then the submodule's new commit is committed here.
  • How it is verified. Genesis Plus GX with a hook (the oracle, a development tool only) records real play. make test replays 10,197 recorded routine calls. make lockstep runs 17 whole sessions against the oracle, checking work RAM and VDP at every vblank. make golden-check compares the native build's own output with a recorded build, bit for bit (88,000 frames), plus save-state round trips. pulseman is checked on the oracle's vectors (make runtime-check) and on this game's own recordings (make runtime-selfcheck).
  • Done. The harness, the game-play core, video and sound, the frame drivers, native running, lockstep, the whole front end (it boots from power on), a cleanup, golden runs, native save states and rewind. In pulseman: the console's state as a value, a client-owned clock with line-by-line drawing and an H-interrupt hook, a bus map, FM cores and models chosen at run time, and a trace recorder.
  • Deferred by the owner. WebAssembly (it first needs src/ram.h's GCC-only big-endian struct replaced: the choice is part of that job) and the cartridge's slowdown (it needs a 68k cost model).
  • Read first. ext/pulseman/README.md and docs/API.md there; the comments at the top of tools/oracle/vectors.py, tools/golden.py and tools/hybrid/oracle_shim.c. The working notes (the handoff guide, the plan, the playbook for another game) are kept outside the repository.
  • Rules. The ROM and everything made from it (states, traces, captures, screenshots, GIFs such as tools/goal_gif.py makes) stay out of git. Commit after each finished job. Before and after a change: make test, make lockstep, make golden-check; for a runtime change, also make runtime-check and make runtime-selfcheck.
./tools/fetch_refs.sh && tools/asm/build_vasm.sh     # references, the oracle core, the byte-exact rebuild
cp /path/to/rom "NHL 94 (UE) [!].gen"                # your ROM at the repo root
make oracle && make vectors                          # the oracle; every state and vector (tools/oracle/vectors.py)
tools/hybrid/capture_all.sh                          # the 29 capture sessions (~15 min, 3.6 GB)
make test && make lockstep && make golden-check      # the checks

Features

From the least ambitious to the most.

  • The whole game from power on: exhibition, playoffs, shootouts, user records, every menu and screen
  • FM and PSG music and effects, the Z80 sound relay ported to C
  • Keyboard and two game controllers
  • Window scaling, full screen, 4:3 aspect
  • Battery save (save RAM) kept in a file
  • Smooth 60 frames a second (no cartridge slowdown)
  • Save states (F7 / F8) and rewind (F4)
  • FM core and chip model choice (--fm-core nuked|ymfm, --fm-model ym2612|ym3438)
  • Headless runs: screenshots, WAV, scripted input, RAM watch (--watch), trace recording (--record)
  • Mute, volume, audio device and latency options
  • A settings file (window, scaling, volume, keys), every key rebindable
  • A menu: the ROM in a file browser, reset, video, sound and the FM chip live, key rebinding
  • Integer and sharp-bilinear scaling, hi-DPI
  • Screenshot key (PNG)
  • Fast-forward, slow motion, frame step
  • Pause when the window loses focus
  • Controller remapping: profiles (B/C swap, pads labelled A B C), player slots, turbo, dead zones
  • Video and audio capture
  • Save slots with thumbnails; quick resume on quit and start
  • Skip the EA screen, boot straight into a game
  • Colour options; scanlines, CRT mask, composite dither blending
  • IPS and BPS patches, soft-patching, the ROM check as a toggle for data hacks
  • Game Genie and Action Replay codes
  • Input recording and replay files
  • Save states anywhere, menus and pauses included
  • Debug overlay: VRAM, palettes, sprites, a RAM watch and cheat search
  • Run-ahead for lower input latency
  • The cartridge's slowdown as an option, with an "overclock" slider
  • Four players with EA's 4 Way Play adaptor
  • Play in the browser (WebAssembly)
  • A per-game manifest: named RAM, roster and ratings tables to CSV and back, a hack diff tool
  • HD sprite, tile and sound packs
  • Widescreen
  • Lua scripting: overlays, stats, bots
  • Python bindings: headless training environments, automated tests
  • Replays and spectating
  • Rollback netplay online
  • The next game on the same method and runtime

Greets

Big, big greets to everyone whose work this stands on:

  • EA Sports and Park Place Productions, who made NHL '94 in 1993. Still the best.
  • abdulahmad/NHL94Genesis: the bitwise rebuild of the ROM in assembly, with every label and the module split, built in the style of its sibling NHLPA93Genesis. Every C file here maps to one of its files, and every routine has its name.
  • Chaos81/nhl94-disassembly: the IDA database and listing with the names and comments that made the code readable, and Mhopkinsinc/NHLHockey, the NHL Hockey source code it started from.
  • The NHL '94 community and its Discord, keeping the game alive for over thirty years.
  • Genesis Plus GX by Eke-Eke (after Charles MacDonald's Genesis Plus): the oracle. Every routine and every frame here was checked against it. A development tool only, never shipped.
  • Nuked-OPN2 by nukeykt and ymfm by Aaron Giles: the YM2612 cores that make the music.
  • vasm by Volker Barthelmann and Frank Wille: the assembler that rebuilds the ROM byte for byte on Linux.
  • SDL: the window, the sound and the controllers.
  • libretro: the API the oracle is hosted through.
  • The documenters of the hardware: Charles MacDonald (genvdp.txt), Plutiedev, Sega Retro, SMS Power (the SN76489) and the SpritesMind forum (Nemesis's YM2612 and VDP research).
  • Zig, whose bundled Clang checks pulseman against a second compiler.

License

MIT (LICENSE), for the code written for this project. It grants nothing over NHL '94 itself, its name or its data, which belong to Electronic Arts: bring your own copy of the game. pulseman is MIT too; its FM chip cores are Nuked-OPN2 (LGPL-2.1) and ymfm (BSD-3).