# GameFrame > An agentic framework for reverse engineering games: a method, skills and tools for AI agents and people who rebuild games exactly. --- # GameFrame An agentic framework for reverse engineering games. Understand a game from the copy you own, and rebuild it exactly. Its playbook of skills and agents runs inside your coding agent. Its toolkit of tested tools, around Ghidra, Unicorn and REA, checks every result against the original. - **[Game preservation](https://tanepiper.github.io/game-frame/preservation/index.md)**: Many older games no longer run on current computers. Some are still on sale but have not been updated in years. This method works from the copy you bought, and records how the game works as it goes. - **[A taxonomy of games](https://tanepiper.github.io/game-frame/games/index.md)**: Who drives, the game or the player? Ten families of games, each with its rule set, reference games and projects to learn from, and the systems they share. - **[Observe the original](https://tanepiper.github.io/game-frame/observe/index.md)**: Run the game where you can watch, drive and capture it. Every kind of input, every screen, and an object model with entry points for tests and agents. - **[Skills for each step](https://tanepiper.github.io/game-frame/skills/re-start.md)**: Inventory, observe, decompile, trace, decode formats, emulate, reimplement, verify, land. Each skill is a complete guide an agent can follow alone. - **[Measured against the original](https://tanepiper.github.io/game-frame/skills/re-emulate.md)**: Original functions run under emulation for ground truth, and the reimplementation must match their results bit for bit. - **[Case studies](https://tanepiper.github.io/game-frame/case-studies/index.md)**: Un-Banished, Tyrian 2000 and Frontier Elite II, and the tooling each built to capture its game. - **[Within the law](https://tanepiper.github.io/game-frame/legal.md)**: You need your own copy of the game. GameFrame ships no game files. Each project writes new code from documented behaviour. ## The method Each step has a skill. The steps run in order. When a check fails, you go back to the trace step. ![The method as a loop. Inventory, observe, decompile, trace, format, emulate, reimplement, verify and land, each with its skill. When a check fails, the work goes back to the trace.](https://github.com/tanepiper/game-frame/blob/main/docs/assets/method-loop.svg) Agents can also read this site as plain text. [llms.txt](https://tanepiper.github.io/game-frame/llms.txt) lists every page with a short description, and [llms-full.txt](https://tanepiper.github.io/game-frame/llms-full.txt) holds all of them in one file. Each page has a Markdown copy at the same address with `.md` at the end, such as [/games/interaction.md](https://tanepiper.github.io/game-frame/games/interaction.md). --- # GameFrame GameFrame is an agentic framework for reverse engineering games. Its playbook of skills and agents runs inside your coding agent. Its toolkit of tested tools checks every result against the original. AI agents and people use it to study a game from its shipped files. They document its formats and rules, and reimplement it so it behaves exactly like the original. The toolkit works with [Ghidra](https://ghidra-sre.org), the [Unicorn](https://www.unicorn-engine.org) emulator, [REA](https://github.com/morluto/rea) (Reverse Engineer Anything) and, for DOS games, [DOSBox-X](https://dosbox-x.com) and its debugger. GameFrame grew out of [Un-Banished](https://github.com/tanepiper/unbanished), a 1:1 reimplementation of the game Banished built this way; see [the case studies](https://tanepiper.github.io/game-frame/case-studies/index.md). ## Set it up with your agent Copy this prompt into your coding agent (Claude Code, Codex, Cursor or another). It clones GameFrame and checks your tools. It asks before it installs anything, then starts a project for your game. ```text Set up GameFrame on this computer for me. 1. Clone https://github.com/tanepiper/game-frame into ~/code/game-frame (or update it with git pull if it is already there). 2. Read AGENTS.md and .claude/skills/re-setup/SKILL.md. 3. Run scripts/setup.sh and show me the table of what is present and what is missing. 4. Before you install anything, tell me what scripts/setup.sh --install will install and how large each part is. Wait for my answer, then run it. 5. Run tests/run.sh --ghidra and report how many checks passed. If any failed, show the failures and fix the setup before going on. 6. Run scripts/setup.sh --rea and help me register REA with this agent. 7. Ask me for the name of the game and the folder of my own copy of it. Then run scripts/new-project.sh ~/re/ "" "". 8. Open the new project, read its AGENTS.md, and start with the re-start skill. Never copy the game's files, extracted assets or decompiled code into a git repository. ``` To set it up by hand instead, follow the [quick start](#quick-start). ## Framework, playbook and toolkit GameFrame is the whole framework. It has two parts. The **playbook** is the method and the rules an agent follows: - the skills in `.claude/skills/re-*/`, one per step of the method; - the stage agents in `agents/`, packaged with the skills as a Claude Code plugin (`.claude-plugin/`); - the rules in `AGENTS.md` and, for each game, in the project's own `AGENTS.md` (`templates/project/`). The **toolkit** is the tested tools the playbook runs: - the tools in `tools/`, such as the Ghidra export and query, the Unicorn emulators, format validation and the bit-exact comparison tools; - the scripts in `scripts/`, such as setup, new-project and the Ghidra runs; - the checks in `tests/run.sh`, which prove every tool on the samples in `examples/`. ## Quick start ```bash git clone https://github.com/tanepiper/game-frame.git cd game-frame scripts/setup.sh --install # Ghidra, JDK 21, objdump, Node, Zig, and .venv (unicorn, pefile, capstone) tests/run.sh --ghidra # proves every tool on the sample game (about 1½ minutes) scripts/new-project.sh ~/re/mygame "My Game" "/path/to/My Game" # a project for one game ``` Then open the new project with your agent and ask it to start with the **re-start** skill. Claude Code finds the skills in `.claude/skills/` automatically; other agents read `AGENTS.md` and the `SKILL.md` files. ### As a Claude Code plugin The skills and the stage agents are also a Claude Code plugin. In Claude Code: ```text /plugin marketplace add tanepiper/game-frame /plugin install gameframe@game-frame ``` The plugin brings the skills, the agents and the MCP servers in `.mcp.json`. The tools still run from a clone of GameFrame or from a project made with it: `re-start` says so first. ## Find your way with `gameframe` `./gameframe` is a small command for help and discovery. It reads the skills and the tools' own help from the files, so it never goes out of date. It needs only Python 3. Every new project gets a copy. ```bash ./gameframe steps # the method's loop, each step with its skill ./gameframe skills # every skill in one line ./gameframe skill re-trace --sections # a skill's headings; --section TITLE prints one part ./gameframe tools # every tool and script, with its first line of help ./gameframe tool decomp_query # one tool's full help ./gameframe run peinfo target/game.exe # run a tool with GameFrame's .venv ./gameframe find vtable # which skills and tools mention a word ./gameframe doctor # the toolchain check (scripts/setup.sh) ``` `steps`, `skills`, `tools`, `tool` and `find` also print JSON with `--json`, for agents and scripts. ## The method ![The method as a loop. Inventory, observe, decompile, trace, format, emulate, reimplement, verify and land, each with its skill. When a check fails, the work goes back to the trace.](https://github.com/tanepiper/game-frame/blob/main/docs/assets/method-loop.svg) Write each claim with its evidence (an address, a value, a measurement) and one label: *observed*, *measured*, *inferred* or *unknown*. Measure each number the reimplementation must match by running the original function under the emulator. The reimplementation must give the same result bit for bit. ## Skills Each skill is a self-contained guide for one step: when to use it, exact commands, what evidence to produce, and the pitfalls that cost time. | Skill | Step | |---|---| | [re-start](https://tanepiper.github.io/game-frame/skills/re-start.md) | Rules, the loop, a first-session checklist, routing to the other skills | | [re-setup](https://tanepiper.github.io/game-frame/skills/re-setup.md) | Install and check the toolchain; register REA for your agent | | [re-inventory](https://tanepiper.github.io/game-frame/skills/re-inventory.md) | Which files hold the game's code; native, managed, scripted or packed; engine and middleware; data containers; what kind of game it is | | [re-observe](https://tanepiper.github.io/game-frame/skills/re-observe.md) | Run the original where you can watch, drive and capture it; every input; every screen; an object model with entry points for tests and agents | | [re-rea](https://tanepiper.github.io/game-frame/skills/re-rea.md) | When to use REA's MCP tools, and how they fit with the text export | | [re-ghidra](https://tanepiper.github.io/game-frame/skills/re-ghidra.md) | Headless export, queries, one-off decompiles, vtables, constants, checking the disassembly | | [re-trace](https://tanepiper.github.io/game-frame/skills/re-trace.md) | From a string, import, export or class name to the function, its fields and constants | | [re-format](https://tanepiper.github.io/game-frame/skills/re-format.md) | Decode archives, resources and saves; prove decoders on every file | | [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md) | Run original functions under Unicorn for ground truth; trace random draws | | [re-port](https://tanepiper.github.io/game-frame/skills/re-port.md) | Reimplement exactly: precision, order of operations, draw order, iteration order; an exact core with commands, digests and a headless runner; additions (multiplayer, mods) kept apart | | [re-verify](https://tanepiper.github.io/game-frame/skills/re-verify.md) | Bit-exact parity, draw order, determinism with digests and on every platform, recordings, saves, pictures, version against version, test tiers | | [re-land](https://tanepiper.github.io/game-frame/skills/re-land.md) | Document in evidence style, label, commit safely | ## Stage agents One agent per stage of the method, in [agents/](https://github.com/tanepiper/game-frame/tree/main/agents). Each has its skills preloaded, does only its stage and ends with a hand-off that names the next agent. The main session sends them one at a time; only the lander commits. `re-start` ("Stage agents") has the table of hand-offs. `re-surveyor` → `re-observer`, `re-decompiler` → `re-tracer` → `re-decoder`, `re-measurer` → `re-porter` → `re-verifier` → `re-lander`, and back to the tracer or the porter when a check fails. Eight task agents do one job each, in any stage: `re-identifier` (engine and libraries), `re-randomiser` (random generators and draw order), `re-listener` (every input), `re-cataloguer` (every screen), `re-mapper` (hardware and memory), `re-matcher` (a crate or library against the game), `re-editor` (documents to the writing rules) and `re-checker` (claims and sources). `re-start` ("Task agents") has their table. ## Scripts | Script | What it does | |---|---| | `scripts/setup.sh [--install] [--rea]` | Checks every tool; installs what is missing; shows REA's registration plan | | `source scripts/env.sh` | Sets `GAMEFRAME_ROOT`, loads `.env`, puts `gameframe` and the `.venv` Python on `PATH`, for each shell or agent session | | `scripts/new-project.sh DIR "Name" [GAME]` | Creates a project for one game: rules, skills, tools, ignored folders for the game and its derivatives | | `scripts/ghidra-export.sh [--labels FILE] BINARY...` | Headless Ghidra analysis and a text export: pseudocode, functions, strings, imports, exports; names applied from a labels file | | `scripts/ghidra-run.sh BINARY decompile\|named\|refs\|vtable\|labels OUT ARGS` | One-off jobs in the analysed project, without re-analysis | | `scripts/extract-installer.sh [--detect] INSTALLER OUT` | Unpacks an installer or package without running it: Inno Setup (GOG), NSIS, InstallShield, MSI, archives, `.dmg`, `.pkg`, GOG's `.sh` | | `scripts/dosbox-run.sh [--fileio] [--break] [--mcp PORT] DIR PROGRAM` | Runs a DOS program in DOSBox-X without a window: exit code, file I/O log, debugger control | | `./gameframe COMMAND` | Help and discovery: the steps, the skills, every tool's own help; runs a tool with the `.venv` | | `tests/run.sh [--ghidra]` | 103 checks of every tool against the two sample games | ## Tools | Tool | What it answers | |---|---| | `tools/triage/compare_trees.py` | Two copies or releases compared file by file (SHA-256, case-insensitive paths); hashes to publish | | `tools/pe/peinfo.py` | What is this binary: architecture, compiler, PDB path, imports, exports, RTTI, engine and packer fingerprints | | `tools/pe/readdata.py` | The real value of a `DAT_` constant or table | | `tools/pe/vtable.py` | The functions in a table of function pointers | | `tools/triage/decomp_query.py` | A function with your labels; callers; callees; who uses a string; who writes a field; where the work is | | `tools/triage/deviations.py` | Every `Deviation:` and `not ported` comment in the reimplementation, as one ledger; `--check` for tests | | `tools/triage/coverage.py` | How much of the original (by count and by size) the reimplementation cites, and the largest functions nobody cites yet | | `tools/emu/pe_emu.py` | Call any function of a Windows x64 EXE or DLL with chosen inputs and read the result | | `tools/emu/call_trace.py` | The calls an emulated function makes (random draws), in order, with call sites | | `tools/formats/reader.py`, `validate.py` | Prototype a format decoder; prove it consumes every byte of every file | | `tools/verify/bitcompare.py` | Ground truth against reimplementation, bit for bit, with ULP distance | | `tools/verify/seqdiff.py` | The first random draw (or any event) where two traces differ | | `tools/ghidra/*.java` | The Ghidra scripts the shell scripts run (`ApplyLabels.java` names functions and creates missed ones) | | `tools/dos/dosinfo.py` | What is this DOS binary: MZ, NE or LE; segments with Ghidra's selectors; imports; compiler, extender, packer fingerprints; debug symbols; Pascal strings | | `tools/dos/tdinfo.py` | Borland Turbo Debugger symbols: original procedure, global and type names, parameters, record layouts, source lines, labels for Ghidra, Rust readers for records | | `tools/dos/nedis.py` | 16-bit disassembly with relocations applied: far calls and DS globals by name | | `tools/dos/readseg.py` | Typed values at `seg:off`, including Turbo Pascal `Real` and Pascal strings | | `tools/emu/dos_emu/` | Call any function of a 16-bit NE (protected mode) or MZ (real mode) program, at Ghidra's addresses | | `tools/emu/dos_env/` | A small DOS, BIOS, DPMI host and PC for dos_emu: run a whole DOS program headless, with files, timer interrupts, keys, and screenshots | | `tools/emu/fragments.py` | Capture fragments of a DOS program's main loop from one headless run: DS, stack, screens, open files and heap blocks before and after each fragment, for the port's tests | | `tools/dos/fileio_trace.py` | From a DOSBox-X file log: every file's reads in order, with record patterns | | `tools/dos/dbx_control.py` | Drive DOSBox-X's debugger from a script: breakpoints, registers, memory | ## Crates Clean Rust implementations of chips and runtimes that many old games need, each checked against an original program in one of the case studies. They live in `crates/`, a Cargo workspace, under Apache 2.0. See [docs/crates](https://tanepiper.github.io/game-frame/crates/index.md). | Crate | What it is | |---|---| | [gameframe-opl2](https://tanepiper.github.io/game-frame/crates/opl2.md) | The YM3812 (OPL2) FM chip of the AdLib and Sound Blaster, with a resampler | | [gameframe-wma](https://tanepiper.github.io/game-frame/crates/wma.md) | A WMA v2 decoder for xWMA and ASF streams, building for WebAssembly | | [gameframe-msvc-crt](https://tanepiper.github.io/game-frame/crates/msvc-crt.md) | MSVC's `sinf`, `cosf`, `acosf`, `atan2f`, `qsort` order and `rand`, bit for bit | | [gameframe-turbo-pascal](https://tanepiper.github.io/game-frame/crates/turbo-pascal.md) | Turbo Pascal's 6-byte `Real`, `Random` and Pascal data layouts | ## Worked example [examples/sample-game](https://tanepiper.github.io/game-frame/examples/sample-game.md) is a tiny Windows x64 game (an EXE and an engine DLL, built from C with Zig on any host) with the parts real games have: a fixed-step update, a random number generator, a constant table, objects with a vtable, and a data file. It comes with a ground-truth script, a reimplementation that matches the original bit for bit on 20 seeds, and a deliberately naive reimplementation (doubles instead of floats) that the checks catch. [examples/dos-sample](https://tanepiper.github.io/game-frame/examples/dos-sample.md) does the same for DOS: 16-bit Pascal-style code assembled with Zig and linked by our own script into an NE program (with Borland debug symbols), and a real-mode MZ program, checked under the emulator and under DOSBox-X. ## Documents The documentation is in [docs/](https://tanepiper.github.io/game-frame/guide.md). It is also built as a website (`npm --prefix site run dev`; see [site/README.md](https://github.com/tanepiper/game-frame/blob/main/site/README.md)). - **[A taxonomy of games](https://tanepiper.github.io/game-frame/games/index.md)**: who drives (the game or the player), the axes every game sits on, ten families from arcade action to online games, each with a rule set, reference games and projects to learn from, and the systems they share. - **[Software engines and middleware](https://tanepiper.github.io/game-frame/engines/index.md)**: how to recognise an engine, the id, Build and Unreal engines, other licensed and studio engines, and middleware. - **[Hardware of the era](https://tanepiper.github.io/game-frame/hardware/index.md)**: CPUs from the 8086 to SSE2, fixed point and the x87 FPU, VGA and Mode X, 3dfx and the other 3D cards, sound cards and FM synthesis. - **[Console games](https://tanepiper.github.io/game-frame/consoles/index.md)**: the platforms from the NES to the sixth generation, the tools, the routes to a port, and matching decompilation. - **[Working on a project](https://tanepiper.github.io/game-frame/working/index.md)**: how agents work well on this, and how to write findings in plain English. - **[Observing the original](https://tanepiper.github.io/game-frame/observe/index.md)**: running the original, every kind of input, capturing the interface, and an object model with entry points for tests and agents. - **[Case studies](https://tanepiper.github.io/game-frame/case-studies/index.md)**: Un-Banished, Tyrian 2000 and Frontier: Elite II, and the tooling each built to capture its game. - [docs/GAME-PATTERNS.md](https://tanepiper.github.io/game-frame/game-patterns.md): main loops, random generators, entities, resources, saves, rendering, as they appear in machine code. - [docs/DOS.md](https://tanepiper.github.io/game-frame/dos.md): 16-bit DOS programs, Borland debug symbols, the DOS emulators. - [docs/ABI.md](https://tanepiper.github.io/game-frame/abi.md): calling conventions, C++ layout, floating-point and integer details that a bit-exact port must copy. - [docs/OUTPUTS.md](https://tanepiper.github.io/game-frame/outputs.md): what a project can ship: documents, a website, programs for each system built by CI and attached to releases, a browser version, a server, tools. - [docs/LEGAL.md](https://tanepiper.github.io/game-frame/legal.md): working from your own copy, sharing none of the game's files, and writing new code from documented behaviour. - [docs/SETUP.md](https://tanepiper.github.io/game-frame/setup.md): the toolchain, and tools for targets beyond native code. ## Scope Native Windows x64 games and 16-bit DOS games (MZ real mode, NE protected mode, Borland Pascal and C) are covered end to end; 32-bit LE/LX DOS-extender games are recognised and run in DOSBox-X, but have no loader or emulator here yet. 32-bit, Linux and macOS binaries work with Ghidra and the PE tools where they apply; the emulator needs a variant for them (`re-emulate` lists what changes). Managed (.NET, Unity), scripted and packed games are routed to the right tools by `re-inventory`. Use it on games you own, keep the game's files and anything derived from them out of repositories, and write new code from documented behaviour: see [docs/LEGAL.md](https://tanepiper.github.io/game-frame/legal.md). ## Licence GameFrame uses two licences, one for each kind of work. | What | Licence | |---|---| | **Code**: `tools/`, `scripts/`, `tests/`, `examples/`, `templates/`, `site/` (its configuration and code), `.claude-plugin/`, the Ghidra scripts | [Apache License 2.0](https://github.com/tanepiper/game-frame/blob/main/LICENSE-APACHE) | | **Content**: the documentation (`docs/`), the skills (`.claude/skills/`), the agents (`agents/`), the READMEs and `AGENTS.md`, and the website's text | [CC0 1.0](https://github.com/tanepiper/game-frame/blob/main/LICENSE-CC0-1.0), dedicated to the public domain | CC0 1.0 gives up all copyright in the content, as far as the law allows. Anyone may copy, adapt, translate and remix it for any purpose, commercial included, without asking and without giving credit. The screenshots in `docs/assets/case-studies/` show the games' own art. That art belongs to the games' rights holders, is shown to illustrate the case studies, and is under neither licence. Projects made with `scripts/new-project.sh` start with the same two licences: Apache 2.0 for their code and CC0 1.0 for their documentation. --- # Documentation GameFrame is an agentic framework for reverse engineering games. Its playbook of skills and agents runs inside your coding agent. Its toolkit of tested tools checks every result against the original. These pages cover reverse engineering games, apart from the step-by-step skills (in `.claude/skills/`, listed in the [README](https://tanepiper.github.io/game-frame/overview.md#skills)). Start with the kind of game you have, then how to observe it, then the reference pages as you need them. The website is built from these files where they are (see [site/README.md](https://github.com/tanepiper/game-frame/blob/main/site/README.md)): the order of this page and of each folder's `README.md` is the order of the site's menu. ## Game preservation - [Game preservation](https://tanepiper.github.io/game-frame/preservation/index.md): why older games disappear, what GameFrame does to keep them playable, the law, AI, and the work it builds on. ## Kinds of games - [A taxonomy of games](https://tanepiper.github.io/game-frame/games/index.md): who drives, the axes, the families, the connections between them. ## Platforms - [Home computers and the PC](https://tanepiper.github.io/game-frame/computers/index.md): the CPU families, the 8-bit and 16-bit home computers, the PC eras and the Mac, with the tools and timing for each. - [Console games](https://tanepiper.github.io/game-frame/consoles/index.md): the home consoles by generation, the tools, the routes to a port, and matching decompilation. - [Handhelds](https://tanepiper.github.io/game-frame/handhelds/index.md): from the Game & Watch and the Game Boy family to the Switch, the Steam Deck and phone games, with saves, clocks, links and LCD behaviour. - [Arcade](https://tanepiper.github.io/game-frame/arcade/index.md): the arcade boards, with MAME's source as the reference for each board's clocks, encryption and refresh rate. ## Technology of the era - [Hardware of the era](https://tanepiper.github.io/game-frame/hardware/index.md): CPUs and floating point, 2D graphics, 3D accelerators, sound, memory, storage, input and networks. - [Software engines and middleware](https://tanepiper.github.io/game-frame/engines/index.md): recognising an engine, the id and Build engines, Unreal, other licensed engines, studio engines, middleware. ## Crates - [Crates](https://tanepiper.github.io/game-frame/crates/index.md): clean Rust implementations of chips and runtimes old games need, such as the OPL2 FM chip, a WMA decoder, the MSVC maths functions and Turbo Pascal's `Real`. ## Observing the original - [Observing the original](https://tanepiper.github.io/game-frame/observe/index.md): running the original, inputs, the interface, an object model of the game. ## Working on a project - [Working on a project](https://tanepiper.github.io/game-frame/working/index.md): how agents work well on this, and how to write findings. ## Reference - [Game patterns](https://tanepiper.github.io/game-frame/game-patterns.md): the parts most games are built from, as they appear in machine code. - [DOS games](https://tanepiper.github.io/game-frame/dos.md): 16-bit programs, Borland debug symbols, the DOS emulators. - [Calling conventions and floating point](https://tanepiper.github.io/game-frame/abi.md): the details a bit-exact port must copy. - [What a project can ship](https://tanepiper.github.io/game-frame/outputs.md): documents, a website, programs, releases. - [Setup](https://tanepiper.github.io/game-frame/setup.md): the toolchain. - [Legal](https://tanepiper.github.io/game-frame/legal.md): working within the law and with respect for the original. ## Case studies - [Case studies](https://tanepiper.github.io/game-frame/case-studies/index.md): three reimplementations and the tooling they built. --- # Game preservation Many older games can no longer be bought. Many games people own no longer run on current computers. Some games are still on sale, but nobody has updated them in years. GameFrame helps the owner of such a game keep it playable. It works from the copy the owner bought. It rebuilds the game in new code that behaves exactly like the original. Along the way, it writes down how the game works. GameFrame does not share games. It ships no game files, assets or decompiled code. It is tested only on sample programs written for it. The chapters are: 1. [Why games disappear](https://tanepiper.github.io/game-frame/preservation/why-games-disappear.md): games that are out of print, games that no longer run, games their makers stopped updating, and games whose source code is lost. 2. [What GameFrame does](https://tanepiper.github.io/game-frame/preservation/what-gameframe-does.md): the documents and code a project makes, how they are checked against the original, and what GameFrame cannot do. 3. [Your own copy and the law](https://tanepiper.github.io/game-frame/preservation/law/index.md): the law in 35 countries, with one page per region. It explains why "abandonware" has no legal standing. It also lists past takedowns of game projects. 4. [AI and reverse engineering](https://tanepiper.github.io/game-frame/preservation/ai.md): the concern about AI copying games, and what GameFrame does about it. 5. [Built on others' work](https://tanepiper.github.io/game-frame/preservation/others-work.md): the projects and institutions GameFrame learns from, and where to read more. 6. [For developers and publishers](https://tanepiper.github.io/game-frame/preservation/rights-holders.md): what we promise the people who made these games, and how to reach us. This section is not legal advice. For the working rules of a project, read [Legal](https://tanepiper.github.io/game-frame/legal.md). --- # Why games disappear A game can be lost in several ways. Its maker can stop selling it. A new computer can stop running it. Its maker can stop updating it while it is still on sale. Its source code and its discs can decay or go missing. ## Games that are out of print In July 2023 the Video Game History Foundation and the Software Preservation Network published a study of the US reissue market. It sampled 1,500 games released in the US before 2010. Only 13% of them can be bought today ([VGHF study explainer](https://gamehistory.org/study-explainer/)). In every five-year period of the sample, fewer than 20% of the games are in print. The share is lower for some platforms. The study found 4.5% of Commodore 64 games in print. Almost all of those come from one service, Antstream. It found 5.87% of the Game Boy library in print. Fewer than 3% of games from before 1985 are in print. About 1,000 games left sale when Nintendo closed the 3DS and Wii U eShops in March 2023. The 13% figure counts the games you can legally buy and play today. Many of the others still exist on original discs and cartridges that nobody sells today. ## Games that stop running A game bought years ago can stop running on a new computer or a new operating system. - **64-bit Windows does not run 16-bit programs** ([Microsoft KB 896458](https://support.microsoft.com/kb/896458)). Windows 11 has no 32-bit edition, so no Windows 11 computer runs 16-bit programs. Most DOS games and early Windows games are 16-bit. - **Windows 10 dropped the drivers for SafeDisc and older SecuROM** copy protection. Many games from about 2003 to 2008 depend on them and no longer start ([PC Gamer](https://www.pcgamer.com/report-games-with-securom-and-safedisc-drm-wont-run-under-windows-10/)). - **macOS Catalina (2019) removed support for 32-bit apps** ([MacRumors](https://www.macrumors.com/guide/32-bit-mac-apps/)). - **Apple is retiring Rosetta 2**, which runs Intel programs on Apple silicon. Apple's plan keeps it for general use through macOS 27. From macOS 28 it keeps only a part of it, aimed at older games that their makers no longer maintain ([MacRumors](https://www.macrumors.com/2026/02/16/macos-tahoe-26-4-rosetta-2-warnings/)). - **Games written for 3dfx Glide**, a 3D card interface of the late 1990s, need a third-party wrapper to run at all ([3D accelerators](https://tanepiper.github.io/game-frame/hardware/graphics-3d.md)). A game that needs an online service stops working when the service closes. Ubisoft closed the servers of The Crew on 31 March 2024 and removed the game from players' libraries. ## Games on sale that nobody updates Some games are still sold, but their makers have stopped working on them. Banished is one example. Shining Rock Software, a studio of one person, released it on 18 February 2014 ([Wikipedia](https://en.wikipedia.org/wiki/Banished_(video_game))). Its last build, 1.0.7, is dated 14 September 2017 in GOG's build history ([GOGDB](https://www.gogdb.org/product/1207660783)). The game is still on sale on Steam and GOG. If a later version of Windows or macOS breaks the game, there may be no update to fix it. Nobody has counted how many games are in this position. Many games from small studios are in it. [Un-Banished](https://tanepiper.github.io/game-frame/case-studies/banished.md) is GameFrame's case study of one. ## Lost source code and media Studios lose their own code. Konami no longer had the final code of Silent Hill 2 and 3. Its HD Collection was built from incomplete code ([Push Square](https://www.pushsquare.com/news/2012/05/silent_hill_hd_collection_ported_from_incomplete_code)). Square Enix said data from the first Kingdom Hearts was lost. Its HD version recreated it ([Game Revolution](https://www.gamerevolution.com/news/5796-square-lost-kingdom-hearts-assets-forced-to-recreate-hd-version-from-scratch)). Discs decay. The Canadian Conservation Institute gives recordable discs lifetimes between a few years and a century, depending on the dye and the metal layer ([CCI Notes 19/1](https://www.canada.ca/en/conservation-institute/services/conservation-preservation-publications/canadian-conservation-institute-notes/longevity-recordable-cds-dvds.html)). Some games cannot be sold because nobody can say for certain who owns them. No One Lives Forever (2000) could still not be bought legally at its 25th anniversary in 2025 for this reason. When a studio loses its source code, the copies players bought are often all that is left. GameFrame works from those copies. --- # What GameFrame does GameFrame helps one person study a game they own and rebuild it in new code. The new code runs on current computers. It reads the owner's original game files when it runs. It must give the same results as the original, measured on the original's own code. ## Documents of how the game works MAME, the arcade emulator, describes its own source code as documentation of the machines it emulates. The MAME team says that playing a game mainly shows that the documentation is right ([MAME](https://github.com/mamedev/mame)). GameFrame applies this idea to a single game. - The findings are written down as documents, with the address in the original that each claim comes from ([Writing findings](https://tanepiper.github.io/game-frame/working/writing.md)). - Functions of the original run under emulation to give exact answers for chosen inputs ([re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). - The new code must give the same answers to the last bit. A port that does not match fails its tests ([re-verify](https://tanepiper.github.io/game-frame/skills/re-verify.md)). - File formats are decoded and proved against every file of their type ([re-format](https://tanepiper.github.io/game-frame/skills/re-format.md)). A later project can use the documents without the port's code. ## Other preservation work Different groups preserve different parts of a game. | Who | What they keep | |---|---| | Museums and archives (The Strong, the Video Game History Foundation) | Hardware, discs, boxes, magazines, design documents, sometimes source code | | Emulators (MAME, DOSBox, Dolphin) | The machine, so the original program runs unchanged | | Matching decompilations (Super Mario 64, Ocarina of Time) | Source code that compiles to the identical original program | | Reimplementations (ScummVM, OpenRCT2, DevilutionX) | New code that runs the original game's data on current systems | GameFrame belongs to the last group. Its playbook gives an agent the method as skills to follow step by step. Its toolkit checks the new code against the original's own code, one function at a time. ## What it cannot do - It cannot help anyone who does not own the game. Every project starts from a bought copy. - It cannot bring back a game whose files are gone, or an online game whose server code was never released. - It does not make a game available to people who cannot buy it. Most of the 87% of games out of print stay out of print. - A careful reimplementation of a small game takes weeks of work by a person and an agent together. ## What a project ships A project made with GameFrame may publish its own new code and its documents. It never publishes the game's files, extracted assets or decompiled code. Players bring their own copy of the game ([What a project can ship](https://tanepiper.github.io/game-frame/outputs.md)). --- # Your own copy and the law This chapter summarises the law in 35 countries. It is not legal advice. The law of the country where you live applies to you, wherever the code is hosted. A lawyer who knows software law in your country can advise on a project. For the rules every project follows, read [Legal](https://tanepiper.github.io/game-frame/legal.md). ## Owning and sharing a game Most countries let a person who owns a game study how it works. No country lets that person share the game. Every project made with GameFrame starts from a copy that the person owns. Where the game is still sold, the person can buy it from the store. GameFrame never links to download sites for games. ## Three groups of countries Most countries follow one of three patterns. | Group | What it allows | Examples | |---|---|---| | The EU model | Observing, studying and testing a program you own. Decompiling only to make an independent program work with it | The EU, the UK, South Korea, Singapore, New Zealand, Ukraine, Norway, and partly India | | The US trade-agreement model | The same study rights, with a strict ban on breaking locks and a closed list of exceptions that includes interoperability | The US, Canada, Mexico, Colombia, Australia | | Back-up and research only | A back-up copy, sometimes a research copy. No express permission to decompile | Brazil, Argentina, Egypt, Indonesia, Thailand, Vietnam, Malaysia, the UAE, South Africa under its current Act | Observing a program while it runs is the widest permission. It covers running the original, capturing frames and state, and recording calls. These are the playbook's observe and emulate steps ([re-observe](https://tanepiper.github.io/game-frame/skills/re-observe.md), [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). Decompiling is the narrowest permission. Many countries allow it only for interoperability, and about a third of the countries covered give it no express cover. Of the playbook's steps, decompiling has the least legal cover. ## GameFrame's defaults GameFrame's defaults are based on these laws. The user decides what their own project does ([The user decides](https://tanepiper.github.io/game-frame/working/agents.md#the-user-decides)). - **Sharing.** No country covered allows sharing game files, assets or decompiled code. Every decompilation exception also forbids passing the information on. - **New code.** Singapore, New Zealand, South Korea, Turkey and the EU forbid using decompiled information to make a "substantially similar" program. A reimplementation that behaves like the original and uses its own new code has the lowest risk under these rules ([Writing findings](https://tanepiper.github.io/game-frame/working/writing.md)). - **Locks.** Lock rules differ more between countries than any other point. Japan has no known interoperability exception to its lock rules. Thailand removed its exceptions in 2022. Mexico makes circumvention for profit a crime. Australia's Federal Court held in 2024 that a game's online checks count as locks. Where a game's files are encrypted or protected, the regional pages say what is known for each country. - **Services.** South Korea fines people who run private servers, even for a single-player game. Japan has arrested people who sold edited save files. ## "Abandonware" A game stays under copyright when its maker stops selling it. Copyright lasts at least the author's life plus 50 years in every country covered. No commercial game from the 1970s onward is out of copyright anywhere. ScummVM's FAQ says the word "is not a proper legal concept in any country" ([ScummVM FAQ](https://docs.scummvm.org/en/latest/help/faq.html)). Sharing an out-of-print game is still unlawful. ## Hosting GitHub applies the US takedown process (the DMCA) to every repository, wherever its owner lives ([GitHub DMCA policy](https://docs.github.com/en/site-policy/content-removal-policies/dmca-takedown-policy)). The law of your own country still applies to your work. A project that holds only its own new code and documents contains no game material for a rights holder to take down. ## Past takedowns - Nintendo removed builds of the Super Mario 64 PC port that included the game's assets. - Take-Two removed the re3 and reVC projects for GTA III and Vice City in February 2021 ([GitHub DMCA notice](https://github.com/github/dmca/blob/master/2021/02/2021-02-19-take-two.md)), then sued their authors. Those projects needed the player's own data. Their code came from a decompilation of the original programs. Requiring the player's own data did not protect re3 and reVC. A GameFrame project publishes only new code, written from documented behaviour. Each project writes down its process so that anyone can check it. ## Pages in this chapter 1. [Treaties](https://tanepiper.github.io/game-frame/preservation/law/treaties.md): the rules all countries share, and the points each country decides. 2. [United States, Europe and the United Kingdom](https://tanepiper.github.io/game-frame/preservation/law/us-europe-uk.md): the US, the EU, the UK, Norway and Switzerland. 3. [East Asia](https://tanepiper.github.io/game-frame/preservation/law/east-asia.md): Japan, China, South Korea, Taiwan, Hong Kong. 4. [South and Southeast Asia and Oceania](https://tanepiper.github.io/game-frame/preservation/law/asia-pacific.md): India, Singapore, Malaysia, Indonesia, the Philippines, Thailand, Vietnam, Australia, New Zealand. 5. [The Americas](https://tanepiper.github.io/game-frame/preservation/law/americas.md): Canada, Mexico, Brazil, Argentina, Chile, Colombia. 6. [Africa, the Middle East, Other Nations](https://tanepiper.github.io/game-frame/preservation/law/africa-middle-east.md): South Africa, Nigeria, Kenya, Egypt, Israel, Turkey, the UAE, Saudi Arabia, Russia, Ukraine. 7. [35 countries compared](https://tanepiper.github.io/game-frame/preservation/law/compared.md): one table. Each country section uses the same headings: studying a program, locks, libraries and archives, how long copyright lasts, owners who cannot be found, cases, and games that shut down. "(not confirmed)" marks a point that has not been checked against the official text. --- # Treaties This page is not legal advice. The law of the country where you live applies to you. International treaties set minimum rules that most countries share. They neither give a right to study a program nor forbid it. Each country decides this within the limits the treaties set. ## What the treaties require - **Programs are literary works.** The TRIPS Agreement (Art. 10(1)) binds every WTO member to protect programs, in source or object code, as literary works ([TRIPS](https://www.wto.org/english/docs_e/legal_e/27-trips_04_e.htm)). The WIPO Copyright Treaty says the same in Art. 4 ([WCT](https://www.wipo.int/treaties/en/ip/wct/)). - **Copyright lasts at least the author's life plus 50 years.** The Berne Convention sets this in Art. 7(1). TRIPS Art. 12 sets at least 50 years from publication for works owned by a company, which covers most games ([Berne](https://www.wipo.int/wipolex/en/text/283698)). - **No registration is needed.** Berne Art. 5(2) forbids formalities. A foreign game is protected in each member country as if it were local (Art. 5(1)). - **Exceptions must pass the three-step test.** Berne Art. 9(2), TRIPS Art. 13 and WCT Art. 10 allow exceptions "in certain special cases" that do not conflict with normal use of the work and do not unreasonably harm the author. The WCT's agreed statement lets countries extend their exceptions to digital uses and create new ones ([WCT agreed statements](https://www.wipo.int/wipolex/en/text/295456)). - **Locks get "adequate legal protection".** WCT Art. 11 asks for protection against breaking technical locks that restrict acts "not authorized ... or permitted by law". It says nothing about tools. Under this wording, a country can allow circumvention for a use an exception already permits. WIPO lists 119 parties to the WCT ([WIPO Lex](https://www.wipo.int/wipolex/en/treaties/ShowResults?search_what=C&treaty_id=16)). Brazil, Egypt, Pakistan, Iran, Bangladesh and Sri Lanka are not among them. Saudi Arabia is not listed (not confirmed). Israel, Kenya and South Africa signed and have not ratified. Taiwan cannot join WIPO treaties, but it is a WTO member. ## Trade agreements that go further US free trade agreements contain stricter rules. The USMCA (CUSMA) between the US, Mexico and Canada is an example ([USMCA chapter 20](https://ustr.gov/sites/default/files/files/agreements/FTA/USMCA/Text/20-Intellectual-Property-Rights.pdf)). - Art. 20.66 makes breaking an access lock a wrong of its own, separate from infringement. It also covers making or selling tools. - Exceptions come from a closed list. The first is reverse engineering a lawfully obtained program "for the sole purpose of achieving interoperability of an independently created computer program". - Libraries may break a lock only to decide whether to buy a work. - Art. 20.62 sets copyright at life plus 70 years, or 75 years from publication for company works. Canada moved to life plus 70 on 30 December 2022 for this reason. The US agreements with Australia, Singapore, Chile, Korea, Jordan, Morocco, Bahrain and Central America have similar rules ([EFF comparison](https://www.eff.org/pages/seven-lessons-comparison-technological-protection-measure-provisions)). When the US left the Trans-Pacific Partnership, the other eleven countries suspended its term and lock articles in the CPTPP ([DFAT](https://www.dfat.gov.au/trade/agreements/in-force/cptpp/outcomes-documents/Pages/cptpp-suspensions-explained)). ## Libraries and archives A 2017 WIPO study covered 191 countries. It found 161 with at least one library or archive exception, 102 that allow preservation copies and 53 that let libraries break locks. Many countries exclude computer programs from library copying ([SCCR/35/6](https://www.wipo.int/edocs/mdocs/copyright/en/sccr_35/sccr_35_6.docx)). WIPO's committee has not agreed a treaty. A draft from October 2026 defines preservation to include moving works "to stable technological formats" ([SCCR/49/5](https://www.wipo.int/edocs/mdocs/copyright/en/sccr_49/sccr_49_5.pdf)). The draft is not binding. It covers only institutions. No country gives a private owner a right to preserve a game. A private owner can rely only on the software exceptions on the regional pages. ## How countries classify games WIPO's 2013 study found that no country has settled how to classify games ([WIPO study](https://www.wipo.int/edocs/pubdocs/en/wipo_report_cr_vg.pdf)). Canada, China, Russia, Singapore and Spain treat games mainly as programs. The US, Germany, Japan, India and others protect each part by its own kind: code as a program, art as art, music as music. Kenya and South Korea treat games mainly as audiovisual works. The copyright term and the exceptions that apply to a game depend on how the country classifies it. ## Heritage UNESCO's 2003 Charter on the Preservation of the Digital Heritage names software, and asks countries for legal frameworks that support preservation ([UNESCO Charter](https://webarchive.unesco.org/web/20170127184223/http://unesdoc.unesco.org/images/0013/001311/131178e.pdf)). UNESCO backs the Software Heritage archive of source code. UNESCO's texts treat software as heritage. They do not give anyone a legal permission. --- # United States, Europe and the United Kingdom This page is not legal advice. The law of the country where you live applies to you. ## United States - **Studying a program.** Section 1201(f) of the Copyright Act lets a person who has lawfully obtained the right to use a program analyse it to make an independently created program work with it ([17 U.S.C. 1201](https://www.law.cornell.edu/uscode/text/17/1201)). In *Sega v. Accolade* (1992), the court held that disassembling a game to learn how it worked with the console was fair use ([Copyright Office summary](https://www.copyright.gov/fair-use/summaries/segaenters-accolade-9thcir1992.pdf)). In *Sony v. Connectix* (2000), it held the same for copies made while building an emulator ([Copyright Office summary](https://www.copyright.gov/fair-use/summaries/sony-connectix-9thcir2000.pdf)). - **Locks.** Breaking an access lock and selling tools are both banned. The Copyright Office adds exemptions every three years. The current ones run from October 2024 to October 2027. A person may restore a game whose servers have been shut for at least six months, for "personal, local gameplay" ([37 CFR 201.40](https://www.ecfr.gov/current/title-37/chapter-II/subchapter-A/part-201/section-201.40)). - **Libraries and archives.** Libraries, archives and museums may keep games playable on their own premises. In October 2024 the Copyright Office refused to let them offer out-of-print games remotely for research. It said the proposed safeguards were too weak ([VGHF statement](https://gamehistory.org/dmca-2024-statement/)). The next rulemaking opened in June 2026 ([Copyright Office](https://www.copyright.gov/1201/2027/)). - **How long copyright lasts.** 95 years from publication for works made by a company (not confirmed). - **Cases.** Nintendo's case against the Yuzu emulator settled in 2024 for $2.4 million, on circumvention claims ([ChannelNews](https://www.channelnews.com.au/nintendo-settles-lawsuit-with-tropic-haze/)). - **Games that shut down.** No US law covers them. ## European Union - **Studying a program.** Article 5(3) of the Software Directive lets a lawful user observe, study and test a program while running it, to find the ideas behind it. Article 6 allows decompiling where it is indispensable to make an independently created program work with it. Article 6(2)(c) forbids using what you learn to make a program "substantially similar in its expression". Article 8 voids contract terms that override these rights ([Directive 2009/24/EC](https://eur-lex.europa.eu/eli/dir/2009/24/oj)). In *SAS Institute v World Programming* (2012), the Court of Justice held that a program's functionality, its programming language and its data file formats are not protected as expression ([SCL](https://www.scl.org/2451-sas-institute-inc-v-world-programming-ltd-report-and-analysis/)). - **Locks.** Breaking locks and selling tools are banned. - **Libraries and archives.** Article 6 of the 2019 copyright directive lets cultural heritage institutions make preservation copies. EFGAMP, the European federation of game archives, says it covers games ([EFGAMP](https://efgamp.eu/2019/10/22/winner-games-heritage-games-preservation-advanced-by-eu-copyright-directive/)). - **How long copyright lasts.** The author's life plus 70 years. - **Games that shut down.** The Stop Destroying Videogames citizens' initiative gathered 1,294,188 verified signatures. In June 2026 the European Commission chose a voluntary industry code of conduct over new law ([Lewis Silkin](https://www.lewissilkin.com/insights/2026/06/24/european-commission-stops-short-of-imposing-mandatory-requirements-following-citi-102n4yy)). In France, the consumer group UFC-Que Choisir began legal action over shutdowns on 31 March 2026 ([IRIS](https://merlin.obs.coe.int/article/10576)). ## United Kingdom - **Studying a program.** Section 50BA of the Copyright, Designs and Patents Act 1988 lets a lawful user observe, study and test a program while running it ([s.50BA](https://www.legislation.gov.uk/ukpga/1988/48/section/50BA)). Section 50B lets a lawful user decompile a program to create an independent program that works with it ([s.50B](https://www.legislation.gov.uk/ukpga/1988/48/section/50B)). - **Locks.** Breaking locks and selling tools are banned. - **How long copyright lasts.** The author's life plus 70 years. - **Games that shut down.** A petition reached 189,887 signatures. The government replied that it has "no plans to amend UK consumer law" ([UK Parliament](https://petition.parliament.uk/petitions/702074)). Parliament debated it on 3 November 2025 ([Hansard](https://hansard.parliament.uk/commons/2025-11-03/debates/C603C07C-1BB5-41EF-A3C1-1AE1FEBBB95D/VideoGamesConsumerLaw)). ## Norway Norway applies the EU Software Directive through the European Economic Area. - **Studying a program.** Sections 41 and 42 of the Copyright Act 2018 cover use, back-up copies, study and decompiling (not confirmed) ([Lovdata](https://lovdata.no/dokument/NL/lov/2018-06-15-40)). - **How long copyright lasts.** The author's life plus 70 years (not confirmed). ## Switzerland - **Studying a program.** Art. 21 of the Copyright Act allows decompiling to get interface information. Art. 24(2) allows one back-up copy, and a contract cannot exclude it (not confirmed) ([Fedlex](https://www.fedlex.admin.ch/eli/cc/1993/1798_1798_1798/en)). - **Locks.** Art. 39a(4) lifts the ban for circumvention done only for a permitted use (not confirmed). - **Libraries and archives.** Art. 24(1bis) lets memory institutions copy to preserve their holdings (not confirmed). - **How long copyright lasts.** The author's life plus 50 years for programs (not confirmed). - **Owners who cannot be found.** Art. 22b (not confirmed). ## What this means for a project The laws of these countries permit observing the running game. They permit decompiling it for interoperability. They do not permit sharing the game or breaking locks. Under the EU and UK rules, new code that copies none of the original's expression has the lowest risk. --- # East Asia This page is not legal advice. The law of the country where you live applies to you. South Korea and China have written software exceptions. Japan has a general exception for uses that are not for enjoying the work. Taiwan and Hong Kong rely on fair use or fair dealing. In this region, rights holders and police have acted mostly against people who sell devices, services or edited game data. ## Japan - **Studying a program.** Japan has no decompilation article. Art. 30-4 of the Copyright Act, in force from 1 January 2019, permits uses that are not for enjoying the work, unless they unreasonably harm the owner ([CRIC translation](https://www.cric.or.jp/english/clj/cloj7.html)). Analysing code counts as a use that is not for enjoyment. Playing the game counts as enjoyment. Art. 47-3 lets the owner of a program copy adapt it for use on a computer (not confirmed). - **Locks.** Two Japanese laws cover locks. The Copyright Act penalises dealing in tools (Art. 120-2, not confirmed). The Unfair Competition Prevention Act was amended in 2018 to cover licence checks and circumvention services ([METI](https://www.meti.go.jp/english/policy/economy/chizai/chiteki/pdf/english_2018rev.pdf)). The maximum penalty is five years in prison and a fine of 5 million yen. **No interoperability or preservation exception to Japan's lock rules is known.** Japan is the strictest country covered for breaking locks. - **Libraries and archives.** Libraries may copy under Art. 31. The National Diet Library receives physical games by legal deposit and holds over 9,600 titles. It has said Switch 2 Game-Key Cards do not qualify ([Automaton](https://automaton-media.com/en/news/nintendo-switch-2-game-key-cards-not-eligible-for-preservation-by-japans-national-diet-library-officials-say/)). - **How long copyright lasts.** The author's life plus 70 years, and 70 years from publication for company works, from 30 December 2018 (not confirmed). - **Owners who cannot be found.** A licensing scheme for "unmanaged" published works came fully into force on 26 May 2026 ([Agency for Cultural Affairs](https://www.bunka.go.jp/english/policy/copyright/amendments_2023/)). - **Cases.** Nintendo and 54 software makers won an injunction against sellers of R4 flash carts on 27 February 2009. In July 2013 the Tokyo District Court awarded 95,625,000 yen in damages, and the judgment became final in January 2016 ([Nintendo](https://www.nintendo.co.jp/corporate/release/2013/130709.html)). Police arrested sellers of edited Pokémon in February 2021 and of modified Breath of the Wild saves in July 2021 ([Nintendo Wire](https://nintendowire.com/news/2021/07/09/breath-of-the-wild-hacker-arrested-in-japan-for-selling-modified-save-games/)). Everyone sued or arrested in these cases sold commercially. - **Games that shut down.** No rule is known. ## China - **Studying a program.** Art. 17 of the Regulations on the Protection of Computer Software lets a person who installs, displays, transmits or stores software study "the design ideas and principles" without permission or fee ([State Council](https://www.gov.cn/zhengce/2013-02/08/content_2602615.htm)). It does not mention decompiling. The Supreme People's Court's 2020 trade-secret rules accept reverse engineering of a product obtained from public channels as a defence ([China IP Law Update](https://www.chinaiplawupdate.com/2020/09/chinas-supreme-peoples-court-releases-provisions-on-several-issues-concerning-the-application-of-law-in-trial-of-civil-cases-of-infringement-of-trade-secrets/)). - **Locks.** The 2020 Copyright Law, in force from 1 June 2021, bans circumvention in Art. 49. **Art. 50 allows circumvention for reverse engineering research of software**, as long as no tools are given to others ([China Justice Observer](https://chinajusticeobserver.com/law/x/copyright-law-of-china-20201111)). - **Libraries and archives.** Art. 24(1)(8) lets libraries, archives, memorial halls, museums and galleries copy works in their collections for preservation. - **How long copyright lasts.** 50 years from publication for company works (Art. 23). - **Owners who cannot be found.** No scheme is known. - **Other rules.** Every game sold in China needs a publishing licence from the National Press and Publication Administration, through a local partner. It approved 1,771 games in 2025 ([Niko Partners](https://substack.nikopartners.com/p/chinas-nppa-approved-1771-video-games)). - **Games that shut down.** No rule is known. ## South Korea - **Studying a program.** Art. 101-4 of the Copyright Act lets a lawful user decompile a program for interoperability, where the information is not easy to get. The information may not be used for other purposes, given to others, or used to build a substantially similar program ([KLRI](https://elaw.klri.re.kr/kor_service/lawViewTitle.do?hseq=42726)). Art. 101-3(1)(6) lets a lawful user examine, research or test a program to find its ideas. - **Locks.** Art. 104-2 bans circumventing access controls. Its exceptions include reverse analysis of program code. The ministry adds further exceptions by notice, which last three years ([Korea Copyright Commission](https://www.copyright.or.kr/eng/doc/quarterly_pdf/254%202018%20Anti-circumvention%20Rules%20of%20Korea%20Copyright%20Act.pdf)). - **Libraries and archives.** Art. 31 allows preservation copies. - **How long copyright lasts.** The author's life plus 70 years (not confirmed). - **Owners who cannot be found.** The ministry may license such works under Art. 50. - **Cases.** In 2024 the Supreme Court upheld a 5 million won fine for a man who ran a private server for GTA San Andreas, a single-player game, and took donations ([Inven Global](https://www.invenglobal.com/articles/23798/democratic-party-rep-jung-joon-ho-proposes-bill-to-strengthen-penalties-for-private-servers)). The offence is in the Game Industry Promotion Act. - **Games that shut down.** Korea is the only country in the region with consumer rules for closed games. Standard terms in force from 22 March 2024 give refunds and a route to compensation ([Korea Times](https://koreatimes.co.kr/www/nation/2024/11/419_387275.html)). They cover money that players spent. They do not give a right to keep playing. ## Taiwan - **Studying a program.** Taiwan has no decompilation article. Art. 59 of the Copyright Act lets the owner of a lawful copy modify it for use on a machine and keep back-ups for personal use. Art. 65 is a fair use clause ([Laws & Regulations Database](https://law.moj.gov.tw/ENG/LawClass/LawAll.aspx?pcode=J0070017)). - **Locks.** Art. 80-2 lists "reverse engineering" among its exceptions. Dealing in tools can bring a year in prison under Art. 96-1. - **Libraries and archives.** Art. 48 lets libraries and archives copy to avoid loss. - **How long copyright lasts.** 50 years from release for company works (Art. 33). ## Hong Kong - **Studying a program.** The 1997 copyright bill had a decompilation clause. The legislature deleted it and widened fair dealing for research and private study instead ([LegCo minutes](https://legco.gov.hk/yr96-97/english/bc/bc14/minutes/bc143004.htm)). Section 60 allows back-up copies. - **Locks.** Dealing in tools has been a crime since 2007. Section 273D excuses circumvention to make an independently created program work with another ([CEDB](https://www.cedb.gov.hk/assets/resources/citb/consultations-and-publications/Note_for_Proponents_Circumvention_Eng.pdf)). - **How long copyright lasts.** The author's life plus 50 years (not confirmed). - **Cases.** In 2002 Nintendo, Sony and Microsoft stopped the retailer Lik Sang from selling mod chips ([The Register](https://www.theregister.com/2002/10/30/attack_of_the_mod_squads/)). ## What this means for a project Every country on this page permits study and observation of a program. In Japan, breaking a lock has no known exception. Selling edited save data has led to arrests. In South Korea, running an online service for a game has led to fines. --- # South and Southeast Asia and Oceania This page is not legal advice. The law of the country where you live applies to you. Australia, New Zealand, Singapore and India have software exceptions for study and for decompiling. Malaysia, Indonesia, Thailand and Vietnam have back-up and research rules with no decompilation clause. ## India - **Studying a program.** Section 52(1) of the Copyright Act 1957 has four software clauses, added in 2000. Clause (ab) allows acts needed to make an independently created program interoperable. Clause (ac) allows "observation, study or test of functioning". Clause (ad) allows copies or adaptations "from a personally legally obtained copy for non-commercial personal use" ([s.52](https://lawgist.in/copyright-act/52)). - **Locks.** Section 65A punishes circumvention only "with the intention of infringing". It excuses circumvention for any purpose the Act does not forbid ([s.65A](https://lawgist.in/copyright-act/65A)). India is one of the most permissive countries covered for studying a game you own. - **Libraries and archives.** Section 52(1)(n) lets a library store a work electronically for preservation, if it holds a non-digital copy. - **How long copyright lasts.** The author's life plus 60 years for programs. - **Owners who cannot be found.** Section 31A allows a licence ([s.31A](https://lawgist.in/copyright-act/31A)). ## Singapore - **Studying a program.** The Copyright Act 2021 follows the EU model. Section 239 allows decompiling under nine conditions, one of which forbids making a substantially similar program ([s.239](https://lawplayer.com/sg/act/CA2021/239)). Section 240 allows observing, studying and testing. - **Locks.** Section 430 excuses circumvention to make an independently created program work with another ([s.430](https://lawplayer.com/sg/act/CA2021/430)). - **Libraries and archives.** An old lock exception for obsolete games in library collections lapsed on 31 December 2024. Regulations in force until the end of 2028 let public collections break locks to make preservation copies under s.232 ([Rajah & Tann](https://sg.rajahtannasia.com/?p=87460)). - **How long copyright lasts.** The author's life plus 70 years. ## Malaysia - **Studying a program.** Section 40 of the Copyright Act 1987 allows a back-up copy, whatever a contract says. There is no decompilation exception ([WIPO Lex](https://www.wipo.int/wipolex/en/text/597931)). - **Locks.** Section 36A excuses circumvention for interoperability, security testing, encryption research and privacy. A consultation in July 2026 proposes stronger lock rules ([Baker McKenzie](https://www.bakermckenzie.com/en/insight/publications/2026/07/malaysia-public-consultation-on-proposed-copyright-act-1987-reforms)). - **How long copyright lasts.** The author's life plus 50 years. ## Indonesia - **Studying a program.** Indonesia's 2014 law names video games ("permainan video") as a protected kind of work. Pasal 45 lets a lawful user make one copy for research and development or as a back-up. The copy must be destroyed after use ([Pasal 45](https://pasal.id/peraturan/uu/uu-no-28-tahun-2014/pasal-45)). - **Locks.** Pasal 52 bans disabling technical controls, with no interoperability exception ([Pasal 52](https://pasal.id/peraturan/uu/uu-no-28-tahun-2014/pasal-52)). - **How long copyright lasts.** 50 years from publication for games and programs ([Pasal 59](https://pasal.id/peraturan/uu/uu-no-28-tahun-2014/pasal-59)). ## The Philippines - **Studying a program.** Since 2013, decompiling for interoperability counts as a form of fair use under s.185.1 of the Intellectual Property Code. Section 189.1 allows one back-up or adaptation ([RA 10372](https://lawphil.net/statutes/repacts/ra2013/ra_10372_2013.html)). - **Locks.** Circumvention doubles damages under s.216.1(b). The section has no exceptions. - **Libraries and archives.** Non-profit libraries may make limited copies under s.188.1. - **How long copyright lasts.** The author's life plus 50 years. ## Thailand - **Studying a program.** Section 35 of the Copyright Act lets people do several non-profit acts on a program, including "research or study" ([translation](https://en.wikisource.org/wiki/Translation:Copyright_Act,_2537_BE)). - **Locks.** The 2022 amendment banned circumventing access controls and removed the exceptions from the Act. It promised ministerial regulations, which are not known to exist ([Tilleke & Gibbins](https://www.tilleke.com/insights/the-2022-amendment-of-thailands-copyright-act-the-good-the-bad-and-the-backstory)). - **Libraries and archives.** Librarians may copy under s.34. - **How long copyright lasts.** The author's life plus 50 years, or 50 years for company works. ## Vietnam - **Studying a program.** The law allows one back-up copy that cannot be passed on ([LawNet](https://lawnet.vn/en/laws/vietnam-can-organizations-and-individuals-allowed-to-use-a-copy-of-a-computer-program-make-a-backup-25583.html)). Amendments in force from 1 April 2026 added a research exception ([Baker McKenzie](https://www.bakermckenzie.com/en/insight/publications/alerts/2025/12/vietnam-2025-ip-law-overhaul)). No decompilation exception is known. - **Locks.** Lock rules were added in 2022. Their exceptions are unclear. - **Libraries and archives.** Libraries may copy for preservation. - **How long copyright lasts.** The author's life plus 50 years. Films get 75 years from publication. ## Australia - **Studying a program.** Division 4A of the Copyright Act 1968 has sections for normal use or study (s.47B), back-ups (s.47C), making interoperable products (s.47D), correcting errors (s.47E) and security testing (s.47F) ([Federal Register of Legislation](https://www.legislation.gov.au/C1968A00063/latest/text)). How far s.47D reaches is not confirmed. - **Locks.** Section 116AN gives a civil action against breaking access controls, with an interoperability exception (not confirmed). In *Take-Two v Anderson* (2024), the Federal Court held that GTA V's online access checks were locks and that a cheat mod was a circumvention device ([Piper Alderman](https://piperalderman.com.au/insight/whats-in-a-game-technological-protection-measures-under-the-copyright-act/)). **A tool that patches a running game can be a circumvention device even when the user owns the game.** - **Libraries and archives.** Sections 113H to 113M, from 2017, let libraries, archives and key cultural institutions preserve any "copyright material". Programs are not excluded. This is the broadest preservation rule covered. - **How long copyright lasts.** The author's life plus 70 years (not confirmed). - **Owners who cannot be found.** A scheme started on 1 April 2026 ([King & Wood Mallesons](https://pulse.mallesons.com/copyright/opening-the-archives-australias-new-orphan-works-scheme/)). - **Cases.** In *Stevens v Sony* (2005), the High Court held that PlayStation mod chips were not unlawful devices under the law as it then stood ([judgment](https://www.hcourt.gov.au/sites/default/files/eresources/2005/HCA/58.pdf)). Later law changed that result (not confirmed). - **Games that shut down.** The consumer regulator, the ACCC, won A$3 million from Valve over Steam refunds ([ACCC](https://www.accc.gov.au/media-release/valve-to-pay-3-million-in-penalties-for-misrepresenting-gamers-consumer-guarantee-rights)) and A$3.5 million from Sony. No court has decided whether shutting down a paid game breaches consumer law. ## New Zealand - **Studying a program.** Section 80A of the Copyright Act 1994 allows decompiling for interoperability and forbids a substantially similar program. Section 80C allows observing, studying and testing. Section 80D voids contract terms that override these rights ([s.80C](https://www.nzlii.org/nz/legis/consol_act/ca1994133/s80c.html)). - **Locks.** Since 2018, access controls are protected, with a general exception for acts that do not infringe (s.226E, not confirmed). Section 226F allows getting past region locks. - **Libraries and archives.** New preservation and orphan-works exceptions are planned, with a bill possible in 2027 ([British Copyright Council](https://www.britishcopyright.org/new-zealand-copyright-update/)). - **How long copyright lasts.** The author's life plus 50 years, rising to 70 under trade agreements. ## What this means for a project India, Singapore, Australia and New Zealand permit study and observation of a program. The other countries on this page permit them in narrower terms. Indonesia, Thailand and Vietnam do not expressly permit decompiling. In Australia, a tool that patches the running game can be unlawful. --- # The Americas This page is not legal advice. The law of the country where you live applies to you. The United States is on [its own page](https://tanepiper.github.io/game-frame/preservation/law/us-europe-uk.md). Canada has interoperability exceptions both for studying a program and for locks. Mexico makes circumvention for profit a crime. Brazil and Argentina have no decompilation exception. ## Canada - **Studying a program.** Section 30.6 of the Copyright Act lets the owner of a program copy adapt it where this is essential to run it on a computer, for their own use. Section 30.61, from 2012, allows copying "for the sole purpose of obtaining information" to make a program interoperable ([Justice Laws](https://laws-lois.justice.gc.ca/eng/acts/c-42/page-9.html)). - **Locks.** Section 41.1 bans breaking access controls and dealing in tools. An individual who breaks a lock only for private purposes faces no statutory damages. The interoperability exception in s.41.12 was widened in November 2024 to cover "a device in which it is embedded", and s.41.121 added a repair exception ([Justice Laws](https://laws-lois.justice.gc.ca/eng/acts/c-42/page-12.html)). - **Libraries and archives.** Section 30.1 lets libraries, archives and museums copy works in an obsolete format, unless a copy is on sale ([Justice Laws](https://laws-lois.justice.gc.ca/eng/acts/c-42/page-8.html)). - **How long copyright lasts.** The author's life plus 70 years since 30 December 2022. Works whose copyright had already ended did not get it back. - **Owners who cannot be found.** The Copyright Board can license them ([Copyright Board](https://www.cb-cda.gc.ca/en/unlocatable-owners)). It granted 277 licences from 1990 to 2013, none for games. - **Cases.** In *Nintendo v. King* (2017), the Federal Court awarded $12.76 million against a seller of game copiers and mod chips. The court rejected the defence that buyers used them for homebrew, because the seller gave no evidence for it ([Smart & Biggar](https://smartbiggar.ca/insights/publication/nintendo-awarded-12-7-million-for-circumvention-of-technological-protection-measures-in-precedent-setting-case)). ## Mexico - **Studying a program.** Art. 105 of the Federal Copyright Law allows one use or back-up copy ([LFDA art. 105](https://mley.mx/LFDA/articulo/105/index.html.md)). The only reverse engineering rule is in the lock article. - **Locks.** Art. 114 Quáter, from July 2020, excuses breaking a lock to make an independently created program interoperable ([LFDA art. 114 Quáter](https://mley.mx/LFDA/articulo/114-qu%C3%A1ter/index.html.md)). **Federal Penal Code art. 427 Bis punishes knowing circumvention for profit with six months to six years in prison** ([CPF art. 427 bis](https://mley.mx/CPF/articulo/427-bis/index.html.md)). The Supreme Court upheld these articles on 3 June 2024. - **Libraries and archives.** Art. 148 V lets a library make one copy of a work that is out of print and at risk of disappearing. - **How long copyright lasts.** The author's life plus 100 years, the longest term in the world ([LFDA art. 29](https://mley.mx/LFDA/articulo/29/index.html.md)). - **Cases.** The authorities enforce the law by raids. The industrial property office IMPI closed Mexico City's Friki Plaza in one of them. ## Brazil - **Studying a program.** Art. 6 of the Software Law (Lei 9.609 of 1998) lists the only acts that do not infringe: a back-up copy, quotations for teaching, similarity forced by function, and integration needed for the user's own use. **There is no decompilation or interoperability exception** ([Lei 9.609](https://www.planalto.gov.br/ccivil_03/leis/l9609.htm)). - **Locks.** Art. 107 of the Copyright Law makes disabling copy controls a civil wrong, with no exceptions ([Lei 9.610](https://www.planalto.gov.br/ccivil_03/leis/l9610.htm)). Brazil is not a party to the WIPO Copyright Treaty. - **Libraries and archives.** No preservation exception. - **How long copyright lasts.** 50 years from publication for software. Copyright in a game's art, music and film parts lasts longer. - **Games that shut down.** Bill PL 3612/2026 would require two years of support and 180 days' notice. After a shutdown, a company would have to offer an offline version, community server tools or a partial refund ([Game Overdrive](https://gameoverdrive.com.br/pl-3612-2026-preservacao-jogos/)). It has not passed. ## Argentina - **Studying a program.** Ley 11.723 protects source and object code and excludes "ideas, procedures, methods and concepts" as such. Art. 9 allows one back-up copy. **The law has no decompilation exception, no lock rule and no library exception** ([InfoLeg](http://servicios.infoleg.gob.ar/infolegInternet/anexos/40000-44999/42755/texact.htm)). - **How long copyright lasts.** The author's life plus 70 years. ## Chile - **Studying a program.** Art. 71 Ñ of Ley 17.336, from 2010, is the broadest software exception in the region. It allows reverse engineering of a lawful copy for interoperability "or for research and development purposes" ([LeyChile](https://www.leychile.cl/Consulta/obtxml?opt=7&idNorma=28933)). - **Locks.** Chile has no circumvention ban. - **Libraries and archives.** Art. 71 I lets non-profit libraries copy works that have been off the market for three years. Art. 71 K allows on-site terminals. - **How long copyright lasts.** The author's life plus 70 years, or 70 years from publication for company works. ## Colombia - **Studying a program.** Andean Decision 351 allows a copy or adaptation that is indispensable for use. - **Locks.** Ley 1915 of 2018 makes circumvention a civil wrong (art. 12). Art. 13(a) excuses reverse engineering for interoperability. The copyright office can add exceptions every three years ([Ley 1915](http://www.secretariasenado.gov.co/senado/basedoc/ley_1915_2018.html)). - **Libraries and archives.** Art. 16 allows lending and on-site terminals. - **How long copyright lasts.** 70 years from publication for company works. - **Owners who cannot be found.** Arts. 18 to 21 let institutions use orphan works. Software is not named. ## What this means for a project Canada and Chile permit study of a program. Mexico and Colombia permit it through their lock exceptions. Brazil and Argentina do not expressly permit decompiling. There, observing the running game and writing new code that copies no expression carry the least risk. In Mexico, breaking a lock for profit is a crime. --- # Africa, the Middle East, Other Nations This page is not legal advice. The law of the country where you live applies to you. Most of these countries have a software exception on the EU model. Many article numbers below are marked "(not confirmed)". ## South Africa - **Studying a program.** The Copyright Act 1978 protects computer programs as their own category. It has no decompilation or interoperability exception, and no lock rules (not confirmed). - **Proposed law.** The Copyright Amendment Bill would add fair use, software and library exceptions, orphan works and lock rules. In 2026 the Constitutional Court struck down only its education clauses, which Parliament is now reviewing. The Bill was not law in July 2026 ([ENSafrica](https://www.ensafrica.com/news/detail/12103/a-decade-in-the-making-constitutional-court-r)). - **How long copyright lasts.** 50 years from release for programs (not confirmed). ## Nigeria The Copyright Act 2022, signed on 17 March 2023, gives users more exceptions than most laws covered ([Gazette text](https://placng.org/i/wp-content/uploads/2023/04/Copyright-Act-2022.pdf)). - **Studying a program.** Section 20(1) is an open fair dealing clause. Section 20(2) allows copies of a program for use, archiving and repair. **Section 20(3) voids any contract term that restricts what the Act permits.** - **Locks.** Section 50 bans circumvention. Section 50(8) lets a lawful user circumvent to find what is needed for interoperability. - **Libraries and archives.** Section 25 lets archives, libraries, museums and galleries copy for preservation. They may also copy works that are out of trade. - **How long copyright lasts.** The author's life plus 70 years for literary works, and 50 years from release for audiovisual works. ## Kenya - **Studying a program.** Section 26A, from 2019, allows copies to fix errors, back up or test a program. It permits decompiling to get the information needed to work with other programs ([CIPIT, Strathmore University](https://cipit.strathmore.edu/what-can-i-get-away-with-exceptions-to-computer-programme-copyright-infringement/)). - **Locks.** Added in 2019. The details are not confirmed. ## Egypt - **Studying a program.** Art. 171 of Law 82 of 2002 allows a back-up or use copy. There is no decompilation exception and no fair use (not confirmed). - **Locks.** Art. 181 makes dealing in tools an offence (not confirmed). Egypt is not a party to the WIPO Copyright Treaty. - **How long copyright lasts.** The author's life plus 50 years (not confirmed). ## Israel - **Studying a program.** The Copyright Act 2007 has an open fair use clause in s.19. Section 24 covers back-ups, maintenance and interoperability information (not confirmed). - **Locks.** Israel has no anti-circumvention law (not confirmed). - **Libraries and archives.** Section 30 allows preservation copies (not confirmed). - **How long copyright lasts.** The author's life plus 70 years (not confirmed). ## Turkey - **Studying a program.** Art. 38 of Law 5846 follows the EU model, with observing, studying and testing, and decompiling for interoperability (not confirmed) ([mevzuat.gov.tr](https://www.mevzuat.gov.tr/mevzuatmetin/1.3.5846.pdf)). - **Locks.** Art. 72 makes dealing in tools an offence (not confirmed). - **How long copyright lasts.** The author's life plus 70 years (not confirmed). ## The United Arab Emirates - **Studying a program.** Federal Decree-Law 38 of 2021, in force from 2 January 2022, protects "smart applications, computer programmes". It allows a back-up copy. Whether it covers decompiling is not confirmed ([Gowling WLG](https://gowlingwlg.com/en/insights-resources/articles/2022/the-new-uae-copyright-law-2021-key-takeaways)). ## Saudi Arabia - **Studying a program.** A new Copyright Law came into force on 12 August 2026. It allows lawful users to make back-ups, correct errors, achieve interoperability and test security. Whether it covers decompiling is not stated ([Baker McKenzie](https://www.bakermckenzie.com/en/insight/publications/2026/07/saudi-arabia-new-copyright-law-modernises-ksa-ip-framework)). - **Locks.** The law introduces lock protection. Its regulations are due within 180 days. - **Owners who cannot be found.** A state body manages them. ## Russia - **Studying a program.** Art. 1280 of the Civil Code follows the EU model. It allows adapting a program, back-ups, decompiling for interoperability and studying the running program (not confirmed) ([consultant.ru](https://www.consultant.ru/document/cons_doc_LAW_64629/)). - **Locks.** Art. 1299 bans circumvention and tools (not confirmed). - **Libraries and archives.** Art. 1275 allows preservation copies (not confirmed). - **How long copyright lasts.** The author's life plus 70 years (not confirmed). - **Availability.** Since March 2022 many Western stores have stopped selling games in Russia ([PC Gamer](https://www.pcgamer.com/steam-ukraine-russia-payments/)). Where a game is no longer sold in Russia, players often have only the copies they already own. ## Ukraine - **Studying a program.** Art. 25 of the 2022 Law on Copyright and Related Rights allows back-ups, decompiling for interoperability, and observing and studying a program ([zakon.rada.gov.ua](https://zakon.rada.gov.ua/laws/show/2811-20)). - **Locks.** Art. 53(3) exempts the free uses, including the software exceptions, from the lock rules. - **Libraries and archives.** Art. 24 allows preservation copies. - **How long copyright lasts.** The author's life plus 70 years. - **Owners who cannot be found.** Art. 29 allows orphan works to be used for preservation. ## What this means for a project Nigeria, Kenya and Ukraine permit study and decompiling for interoperability. The unconfirmed texts of Russia and Turkey permit them too. South Africa, Egypt and the UAE do not expressly permit decompiling. Observing the running game carries the least risk. Most of these countries ban breaking locks. --- # 35 countries compared This page is not legal advice. The law of the country where you live applies to you. Each country has its own section on the region pages of [this chapter](https://tanepiper.github.io/game-frame/preservation/law/index.md). "OST" means observe, study and test. "Interop" means decompilation or reverse engineering for interoperability. "(nc)" means not confirmed. A dash means the point is not known. | Country | Study and decompile | Lock ban and its exceptions | Preservation exception | Term for a game's code | Orphan works | Shut-down games | |---|---|---|---|---|---|---| | United States | Fair use (Sega, Connectix); 1201(f) interop | Act and tools banned; interop; triennial exemptions incl. server-dead games | 1201 exemption, playable on premises only | 95 years from publication for company works (nc) | — | No law | | European Union | OST Art. 5(3); interop Art. 6; contract cannot override | Act and tools banned | DSM Art. 6 (EFGAMP says it covers games) | Life + 70 (nc) | — | ECI answered with a voluntary code, June 2026 | | United Kingdom | OST s.50BA; interop s.50B | Act and tools banned | — | Life + 70 (nc) | — | "No plans" to change law | | Japan | Non-enjoyment use Art. 30-4 | Two regimes; UCPA crimes for services; no interop exception known | Art. 31; NDL deposit of physical games | 70 years from publication (nc) | Art. 67; new scheme May 2026 | — | | China | Study design ideas, Art. 17 | Banned; RE research exception Art. 50(5) | Art. 24(1)(8) incl. museums | 50 years from publication | None known | NPPA licence for any release | | South Korea | Interop Art. 101-4; examine and test 101-3 | Banned; RE exception; 3-year notices | Art. 31 | Life + 70 (nc) | Art. 50 ministerial approval | Refunds and compensation from 2024; private servers criminal | | Taiwan | Fair use Art. 65; adapt for own machine Art. 59 | Banned; RE exception Art. 80-2 | Art. 48 | 50 years from release | None known | — | | Hong Kong | Fair dealing; decompilation clause deleted 1997 | Tool crimes; interop s.273D | ss.46–53 (nc) | Life + 50 (nc) | None known | — | | India | OST, interop and personal copies, s.52(1)(aa)–(ad) | Crime only with intent to infringe; lawful purposes excused | s.52(1)(n) if non-digital copy held | Life + 60 | s.31A licence | — | | Singapore | OST s.240; interop s.239 | Banned; interop s.430 | s.232; may circumvent for it from 2025 | Life + 70 | None known | — | | Malaysia | Back-up s.40 only | Banned; interop s.36A | State bodies s.13(2)(i) | Life + 50 | None known | — | | Indonesia | One R&D or back-up copy, Pasal 45 | Banned; no interop exception | Pasal 47 (nc) | 50 years from publication | None known | — | | Philippines | Interop decompilation as fair use s.185.1 | Double damages; no exceptions | s.188.1 non-profit libraries | Life + 50 | None known | — | | Thailand | Research or study s.35 | Banned; statutory exceptions removed 2022 | s.34; s.35(9) | Life + 50; company 50 years | None known | — | | Vietnam | Back-up only | Added 2022; exceptions unclear | Art. 25(1)(e) | Life + 50 | None known | — | | Australia | Study s.47B; interop s.47D | Civil action; interop exception; Take-Two 2024 | ss.113H–113M, any material | Life + 70 (nc) | New scheme April 2026 | ACCC refund wins; shutdowns untested | | New Zealand | OST s.80C; interop s.80A; terms void s.80D | Access controls; general non-infringing exception s.226E | Planned for 2027 | Life + 50, rising to 70 by 2028 | Planned | — | | Canada | Interop s.30.61; compatibility s.30.6 | Banned; interop widened 2024; repair | s.30.1 obsolete formats | Life + 70 | Copyright Board s.77 | — | | Mexico | Back-up only; interop in lock article | Banned; crime for profit; interop exception | One copy of out-of-print work | Life + 100 | None known | — | | Brazil | None beyond back-up | Civil, copy control only; no exceptions | None | 50 years from publication | None known | PL 3612/2026 pending | | Argentina | None beyond back-up | None | None | Life + 70 | None known | — | | Chile | Interop and R&D, art. 71 Ñ | None | art. 71 I, off market 3 years | Life + 70 | None known | — | | Colombia | Use copies; interop in lock law | Civil; interop exception; DNDA review | Lending and terminals | 70 years from publication for company works | Institutions, not software | Lock disclosure under consumer law | | South Africa | Narrow; Bill pending | None in current Act (nc) | Bill only | 50 years from release (nc) | Bill only | — | | Nigeria | Open fair dealing; program copies; terms void | Banned; interop s.50(8) | s.25, broad | Life + 70 | Informal via s.25(1)(e) | — | | Kenya | Interop decompilation s.26A | Added 2019, details unclear | — | Extended 2019, unclear | — | — | | Egypt | Back-up only (nc) | Tool offence (nc) | — | Life + 50 (nc) | — | — | | Israel | Fair use; interop s.24 (nc) | None (nc) | s.30 (nc) | Life + 70 (nc) | — | — | | Turkey | OST and interop, art. 38 (nc) | Tool offence (nc) | — | Life + 70 (nc) | — | — | | UAE | Back-up (nc) | Yes, details unclear | — | Life + 50 (nc) | — | — | | Saudi Arabia | Interop acts, new 2026 law | New, details unclear | Library exception widened | Largely unchanged | State body | — | | Russia | OST and interop, art. 1280 (nc) | Banned (nc) | art. 1275 (nc) | Life + 70 (nc) | — | Draft game law | | Ukraine | OST and interop, art. 25 | Banned; free uses exempt | art. 24 | Life + 70 | art. 29 | — | | Switzerland | Interop art. 21 (nc) | Banned; permitted uses exempt (nc) | art. 24(1bis) (nc) | Life + 50 for programs (nc) | art. 22b (nc) | — | | Norway | OST and interop, §§ 41–42 (nc) | EU model (nc) | Yes (nc) | Life + 70 (nc) | Likely, EEA | — | The countries fall into three groups. The EU-model group is the EU, the UK, Korea, Singapore, New Zealand, Russia, Ukraine, Turkey, Norway and partly India. Its laws clearly permit observation and permit decompilation only for narrow purposes. The trade-agreement group is Canada, Mexico, Colombia, Australia and others. Its laws add strict lock bans with an interoperability exception. The third group is Brazil, Argentina, Egypt, Indonesia, Thailand, Vietnam, Malaysia, the UAE and South Africa under its current Act. Its laws have only back-up and research rules. They do not expressly permit decompilation. --- # AI and reverse engineering GameFrame is written for AI agents working with people. Many people who reverse engineer games by hand are worried about AI. This chapter describes that worry and what GameFrame does about it. ## The concern On 15 October 2026, 404 Media reported that decomp.dev was shutting down. decomp.dev was a website for the community that decompiles games by hand. Its administrator wrote that AI had broken "the community aspect of decompilation" ([404 Media](https://www.404media.co/video-game-reverse-engineering-community-shuts-down-because-ai-has-made-it-too-easy/)). People using AI now produce projects faster than people working by hand. It has become hard to tell which projects a person led. AI tools also make it easy to produce a cheap imitation of a game. An imitation can copy how a game looks without behaving like it. It can also be a way to copy a game that is still on sale. We share these concerns. An AI agent can read old code quickly. A port made quickly can still behave differently from the original. ## What GameFrame does about it GameFrame asks an agent for evidence at every step. - **The original is required.** Every project starts from the owner's own copy. The agent reads that program. It has no other source. - **Results are measured.** The agent runs functions of the original under emulation to get exact answers ([re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). - **The port must match.** The new code must give the same answers to the last bit. A port that does not match fails its tests ([re-verify](https://tanepiper.github.io/game-frame/skills/re-verify.md)). - **Every claim is cited.** Each finding names the place in the original it came from, and says whether it was read, measured or guessed ([Writing findings](https://tanepiper.github.io/game-frame/working/writing.md)). - **A person reviews the work.** The agent lands each piece of work as a small commit that a person reads ([re-land](https://tanepiper.github.io/game-frame/skills/re-land.md)). - **Nothing from the game is shared.** Decompiled code stays on the owner's computer. The published code is new ([Legal](https://tanepiper.github.io/game-frame/legal.md)). This takes longer than making an imitation. Anyone with the same copy of the game can run the same tests and get the same answers. ## Earlier work The methods in GameFrame come from people who did this work by hand for decades: the ScummVM developers, the decompilation communities, the authors of OpenRCT2, OpenTTD, DevilutionX and many more ([Built on others' work](https://tanepiper.github.io/game-frame/preservation/others-work.md)). The playbook tells agents to read their documents. --- # Built on others' work GameFrame learns from projects that kept games playable long before it existed. Each project below needs the player's own copy of the game and links to a store that sells it. ## Projects | Project | Game | What it does | |---|---|---| | [ScummVM](https://www.scummvm.org/) | over 325 adventure games | Rewrites the games' engines in new code. It began in 2001 and celebrated 25 years in 2026 | | [OpenRCT2](https://github.com/OpenRCT2/OpenRCT2) | RollerCoaster Tycoon 2 | Replaced the original program one function at a time while the game kept running | | [OpenTTD](https://www.openttd.org/) | Transport Tycoon Deluxe | Began from a disassembly. It has since added many features the original lacked. Since March 2026, the Steam and GOG versions need a bought copy of the original game | | [DevilutionX](https://github.com/diasurgical/devilutionX) | Diablo | Runs the player's own Diablo data on current systems. GOG lists it as a free add-on for its copy of Diablo | | [The Force Engine](https://github.com/luciusDXL/TheForceEngine) | Dark Forces, Outlaws | Rebuilds LucasArts' Jedi engine from reverse engineering | | [dethrace](https://github.com/dethrace-labs/dethrace) | Carmageddon | Replaces the original's code one function at a time. Each new function is checked against the original | | [MAME](https://github.com/mamedev/mame) | thousands of arcade machines | Its code documents each machine. Playing the games tests that documentation | The [taxonomy of games](https://tanepiper.github.io/game-frame/games/index.md) lists many more for each kind of game. ## Rights holders who helped - Electronic Arts released the source code of Command & Conquer and Red Alert in 2020, and of more Command & Conquer games in February 2025. Players still buy the game data. - GOG started its Preservation Program in late 2024. It keeps about 100 classic games working on current systems. It has added more since ([GOG](https://www.gog.com/en/gog-preservation-program)). - Atari re-released Transport Tycoon Deluxe in March 2026 and contributes to OpenTTD's server costs ([OpenTTD](https://www.openttd.org/news/2026/03/19/steam-changes-update)). ## Further reading - [Video Game History Foundation](https://gamehistory.org/), including its [digital library](https://library.gamehistory.org/) of magazines and documents. - [The Strong National Museum of Play](https://www.museumofplay.org/), which holds hardware, documents and source code. - [Software Heritage](https://www.softwareheritage.org/), an archive of source code. - [Digital Preservation Coalition](https://www.dpconline.org/). --- # For developers and publishers We respect the people who made these games. This page says what GameFrame promises you. - GameFrame never ships your game, its files or its assets. - Every project made with it needs a copy of the game that the player bought. - Where your game is still sold, our guides point players to your store page. - GameFrame is tested only on sample programs written for it. Its case studies name the games they study, credit their makers and link to where the games are sold. - A project's published code is new, written from documented behaviour ([Legal](https://tanepiper.github.io/game-frame/legal.md)). No developer or publisher has endorsed GameFrame. Projects made with it are independent of the games' makers. ## If you have a concern Please [open an issue](https://github.com/tanepiper/game-frame/issues) on the GameFrame repository. Tell us what concerns you. We will reply. We will remove anything you reasonably object to. If your concern is about a project built with GameFrame, contact that project too. Each project lives in its own repository under its own maintainers. --- # A taxonomy of games Games are not all reverse engineered the same way. The hard part differs by kind of game: the frame loop in a shooter, the simulation and saves in a city builder, the script engine in an adventure game. This section sorts games into families, gives each family a **rule set** (what must be exact, where the rules live, what to capture, how to check), names reference games, and lists public projects that rebuilt such games, so you can learn from them. Use it at the start of a project, together with the `re-inventory` skill: 1. Decide who drives: the game or the player ([Who drives](https://tanepiper.github.io/game-frame/games/interaction.md)). 2. Place the game on the axes below. 3. Read its family page (or pages, for a mixed game) and copy the rule set into the project's `AGENTS.md`. 4. Follow the connections to the systems your game shares with other families. ## Ask your agent Classify my game. Copy this prompt into your coding agent: ```text In my GameFrame project, classify the game. 1. Read docs/games/README.md and docs/games/interaction.md. 2. Look at the game's files and its manual. Ask me how it plays if you cannot tell. 3. Decide who drives (a stream, a simulation, an environment, or turns), the time model, where the rules live and what must repeat exactly. Name the family. 4. Read that family's page. Write the kind of game, its axes and its family into docs/FINDINGS.md, with the evidence for each. 5. Copy the family's rule set into the project's AGENTS.md and adjust it to this game. 6. Ask me how exact the interface must be, and record my answer. ``` ## Axes Find your game's value on each of these axes. The values show where most of the work will be. | Axis | Values | Why it matters | |---|---|---| | Who drives | a stream with controls; software over a simulation; an environment; turns and choices ([details](https://tanepiper.github.io/game-frame/games/interaction.md)) | Where the behaviour lives: a frame loop, a simulation step, triggers, or handlers and scripts | | Time model | fixed-step real time; variable-step real time; pausable real time; turn-based; waits for input | What "the same run" means, and how to replay one ([The main loop](https://tanepiper.github.io/game-frame/game-patterns.md#the-main-loop)) | | Where the rules live | machine code; data tables; a script language and its interpreter; an engine plus data | Whether you port code, read data, or write an interpreter | | What must repeat exactly | demos; lockstep multiplayer; saves; nothing beyond play | How strict the verification must be, down to float rounding | | Era and platform | DOS real mode; DOS protected mode; Windows 9x with DirectX; 64-bit Windows; consoles; managed engines (Unity, .NET) | Tools, ABI, and how to run the original ([DOS games](https://tanepiper.github.io/game-frame/dos.md), [ABI](https://tanepiper.github.io/game-frame/abi.md), `re-inventory`) | | Presentation | text; 2D tiles and sprites; 2.5D (ray casting, sectors); 3D | What the renderer must reproduce, and how pictures are compared | | Interface | drawn by the game's own code; data-driven layouts; a platform toolkit | How much of the interface is code to trace ([Capturing the interface](https://tanepiper.github.io/game-frame/observe/ui.md)) | ## Families | Family | Who drives | Typical time model | The hardest part | |---|---|---|---| | [Arcade action](https://tanepiper.github.io/game-frame/games/arcade-action.md): shoot 'em ups, platformers, beat 'em ups, fighting | stream | fixed step, 60 or 70 per second | frame-exact order, collision, demo sync | | [First-person and 3D action](https://tanepiper.github.io/game-frame/games/first-person.md) | stream, environment | fixed tick or variable | the renderer, movement physics, demo sync | | [Racing and sports](https://tanepiper.github.io/game-frame/games/racing-sports.md) | stream | fixed step | vehicle or ball physics, AI drivers and players | | [Vehicle and space simulations](https://tanepiper.github.io/game-frame/games/vehicle-sims.md) | stream, environment | fixed step, time acceleration | fixed-point or float maths, orbits, flight models | | [City building and management](https://tanepiper.github.io/game-frame/games/management.md) | simulation | fixed step, game speeds | the simulation step, random draws, saves | | [Strategy](https://tanepiper.github.io/game-frame/games/strategy.md): real-time, turn-based, tactics | simulation, turns | lockstep ticks or turns | determinism for multiplayer, AI, pathfinding | | [Role-playing games](https://tanepiper.github.io/game-frame/games/rpg.md) | environment, turns | mixed | rules tables, scripts, dialogue, saves | | [Adventure and story games](https://tanepiper.github.io/game-frame/games/adventure.md) | turns, waiting | waits for input | the script engine and its byte code | | [Puzzle, board and card games](https://tanepiper.github.io/game-frame/games/puzzle-board.md) | turns, stream | turns or fixed step | exact rules and random draws; often small | | [Online games](https://tanepiper.github.io/game-frame/games/online.md) | environment, simulation | server ticks | the client-server split; legal limits | ## Connections Many systems appear in several families. A family page says "see also" when a system it describes also appears in another kind of game. You trace such a system once. The table shows the main shared systems and where to read about each. | System | Families where it matters most | Read | |---|---|---| | Fixed-step frame loop, steps per frame | arcade action, first-person, racing, vehicle sims, management, real-time strategy | [The main loop](https://tanepiper.github.io/game-frame/game-patterns.md#the-main-loop) | | Input recorded per frame (demos, replays) | arcade action, first-person, racing, strategy | [Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md) | | Lockstep determinism | real-time strategy, management with multiplayer, fighting games with rollback | [Networking](https://tanepiper.github.io/game-frame/game-patterns.md#networking-if-any) | | Random generators and draw order | every family; most critical in management, strategy, RPGs, card games | [Random number generators](https://tanepiper.github.io/game-frame/game-patterns.md#random-number-generators) | | Tile maps and layers | platformers, shoot 'em ups, strategy, RPGs, management | [Arcade action](https://tanepiper.github.io/game-frame/games/arcade-action.md), [Strategy](https://tanepiper.github.io/game-frame/games/strategy.md) | | Pathfinding | strategy, management, RPGs | [Management](https://tanepiper.github.io/game-frame/games/management.md), [Strategy](https://tanepiper.github.io/game-frame/games/strategy.md) | | Fixed-point maths | DOS-era games of every family; vehicle sims, first-person | [DOS games](https://tanepiper.github.io/game-frame/dos.md), [ABI](https://tanepiper.github.io/game-frame/abi.md) | | Software 3D rendering | first-person, vehicle sims, racing | [First-person](https://tanepiper.github.io/game-frame/games/first-person.md), [Vehicle sims](https://tanepiper.github.io/game-frame/games/vehicle-sims.md) | | Physics (vehicles, balls, bodies) | racing, sports, vehicle sims, pinball | [Racing and sports](https://tanepiper.github.io/game-frame/games/racing-sports.md) | | Script languages and byte-code interpreters | adventure, RPGs, some strategy (mission scripts), visual novels | [Adventure](https://tanepiper.github.io/game-frame/games/adventure.md) | | Data tables of rules (units, items, spells) | strategy, RPGs, management, shoot 'em ups (weapons) | [Resources and data](https://tanepiper.github.io/game-frame/game-patterns.md#resources-and-data) | | Procedural generation from a seed | vehicle sims (galaxies), management (maps), roguelikes, strategy (random maps) | [Vehicle sims](https://tanepiper.github.io/game-frame/games/vehicle-sims.md) | | A world simulated while unseen | management, strategy, RPGs, online | [Management](https://tanepiper.github.io/game-frame/games/management.md) | | Saves as a full world state | management, strategy, RPGs | [Saves](https://tanepiper.github.io/game-frame/game-patterns.md#saves) | | Event and trigger scripts in levels | shoot 'em ups, first-person, RPGs | [Arcade action](https://tanepiper.github.io/game-frame/games/arcade-action.md) | ## Approaches you will see in other projects The family pages list public projects that rebuilt games. What you can learn from each one depends on the route it took. | Approach | What it means | What you can learn from it | |---|---|---| | **Reimplementation** | New code written from reverse engineering the shipped program, usually checked against it | Method, structure, how they verified; never copy their code into a clean-room project | | **Engine reimplementation** | A new engine that runs the original game's data (scripts, maps, models) | How the data and scripts work | | **Matching decompilation** | Source code that compiles back to the identical original binary | The original's structure, exactly; it is the slowest route | | **Source port** | A port of source code the rights holder released | How the original was built; no reverse engineering was needed | A clean-room project (see [Legal](https://tanepiper.github.io/game-frame/legal.md)) reads other projects' documents and method, not their code. Write in your project's rules which of them may be read. Projects change, move and sometimes are taken down. Check each link before relying on it. Check the descriptions here before you cite them. --- # Who drives: the game or the player Before you read a game's code, decide what kind of control system you are rebuilding. The answer tells you where the hard parts are, what must be exact, and how to check the result. This page describes the four kinds of interaction; the [family pages](https://tanepiper.github.io/game-frame/games/index.md#families) apply them to each kind of game. ## Games that run on their own clock Most software waits for the user. It shows a window, the user presses a button, and the program reacts through an event handler. Most games work the other way round. The game makes things happen on its own clock (enemies arrive, shots fly, the level scrolls, the town grows), and the player reacts through the few controls the game gives. This affects the code in these ways: - **Look for the loop.** Find the loop that runs every frame or every tick, before the code behind any button. Almost everything happens inside it, in a fixed order. Find that order first. Write it down as a table. - **Input is usually state, read at fixed points.** Keys and buttons are often bytes in memory (a key table that an interrupt handler fills, a button array) that the frame reads at one point. A recorded demo is then just the input for each frame, and replaying it reproduces the game. Many games work this way, but not all. Some games read input in several places in a frame (the menu loop and the game loop each read it), and some act on key events inside the window's message handler. Find every place that reads input; [Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md) lists where to look. - **The game draws its own interface.** The HUD, status bar and messages are drawn by the frame itself, often straight to the screen, with the game's own font and shapes. Menus are usually loops of their own, with their own input reading. Trace each screen like game logic ([Capturing the interface](https://tanepiper.github.io/game-frame/observe/ui.md)). - **Time and order change the results.** Because the game starts most events, the frame count, the order of the steps in a frame and the random draws determine what happens. Verify frame by frame against the original. Keep the port's order exactly. - **Take everything from outside through one entry point.** The player's input comes from outside the port. So do the clock, the random seed, files and saves, settings, the hardware the game detects and, on Windows, the order of window messages also come from outside. Pass all of them in as one record per frame or per tick. Then a demo, a recording or a test script can drive the port. Two runs with the same record must give the same frames. [Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md) has the full list. - **Send the outputs through the same record.** The frame's outputs leave the same way: the finished picture and the songs and sounds the frame asked for. The core can then run headless from a demo, a test or a player. Its outputs can be compared exactly with the original's. ## Games that wait for the player Turn-based games, adventure games, puzzle and board games, visual novels and most menus work like ordinary software: nothing happens until the player acts. The code is a set of handlers around a state, or a script interpreter that stops at each choice. Here: - The **rules** say what a move or a command does to the state. Port them from the handler or the script engine. Check them move by move. - There may still be a loop that animates while the game waits. Its timing usually changes nothing in the state. Find out which parts are only animation and which change the state. - **Order of resolution** inside one turn (who moves first, in which order effects apply, which random draws happen) changes the result, as frame order does in a real-time game. - A recording is the list of commands. Many real-time games also wait at times: a pause, a dialogue, an inventory screen. Find the state that switches between "the game runs" and "the game waits". ## The four kinds Look at the game before its code. Play it, or watch it played. Then decide which kind (or which mix) you have. | Kind | What it is like | Where to look, what to keep exact | Examples | |---|---|---|---| | **A stream with controls** | Things arrive at the player every frame; the player steers, fires, dodges | The frame loop and its order, the input bytes read each frame, collision boxes, timing to the frame. A recorded demo replays everything | Tyrian, Doom, Super Mario Bros., Street Fighter II | | **Software over a simulation** | The world runs slowly on its own; events happen; the player reacts through windows, menus and clicks | Two halves: an exact simulation step (fixed rate, random draws, saves) and an interface that behaves like event-driven software | Banished, SimCity 2000, RollerCoaster Tycoon, Caesar III, Command & Conquer | | **An environment the player moves through** | The player moves freely; some things happen to them (walking into an area starts a fight), others they choose (talk, open, take) | Triggers and areas (what fires on entering, on being seen, on time), the interaction rules, the state they change; for online games, what the client decides and what the server decides | Morrowind, Ultima VII, Daggerfall, Frontier: Elite II, World of Warcraft | | **Turns and choices** | Nothing happens until the player acts; then the game resolves the move | The rules of each move, the order of resolution inside a turn, the script engine, the save state | Civilization, X-COM's battles, Heroes of Might and Magic, Monkey Island, Zork, chess | **How exact must the interface be?** Each project decides from its goal. A 1:1 reimplementation (such as Un-Banished) ports the interface's layout, text, timing and quirks as exactly as the simulation, because the player sees them. A project that only wants the game's rules on a new interface can rebuild the screens by look and function, but it should say so in its deviations ledger. Decide this at the start and write it in the project's `AGENTS.md`. ## Mixed games Many games mix the kinds. A shooter's menus and shop are software that waits; a strategy game's battles may be a stream; X-COM alternates a real-time world map with turn-based battles; Frontier mixes a real-time flight simulation with docked screens that wait. Name each mode, find the state that switches between them, and treat each mode by its kind. ## What to record at the start Write these in the project's findings before porting anything: 1. The kind of each mode, and how the game switches between them. 2. The time model: fixed-step (how many steps per second), variable-step, turn-based or waiting. See [The main loop](https://tanepiper.github.io/game-frame/game-patterns.md#the-main-loop). 3. Every input from outside, and where it is read ([Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md)). 4. How the original can be run and watched ([Running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md)). 5. How exact the interface must be, and why. --- # Arcade action: shoot 'em ups, platformers, beat 'em ups, fighting Games where things come at the player every frame and the player steers, jumps, fires and dodges. They are [streams with controls](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds): the game drives and the player reacts. The game runs its rules inside one frame loop at a fixed rate. ## Reference games | Game | Year, platform | Why it is a good example | |---|---|---| | Tyrian 2000 | 1999, DOS | Vertical shooter in Borland Pascal; level event scripts, per-frame demos, an AdLib music driver ([case study](https://tanepiper.github.io/game-frame/case-studies/tyrian.md)) | | Commander Keen 4 | 1991, DOS | Smooth-scrolling platformer on EGA; tile maps, sprite actors with state tables | | Prince of Persia | 1989, DOS | Platformer with rotoscoped animation tables and exact frame timing | | Jazz Jackrabbit | 1994, DOS | Fast platformer with a custom mode-X engine | | Super Mario Bros. | 1985, NES | Sub-pixel physics in fixed point; well documented by speedrunners | | Street Fighter II | 1991, arcade | Frame data, hit boxes, input buffers for special moves | | Double Dragon | 1987, arcade | Beat 'em up: lanes, enemy waves, simple AI states | ## Projects to learn from | Project | Game | Approach | |---|---|---| | Omnispeak | Commander Keen 4-6 | reimplementation from disassembly | | SDLPoP | Prince of Persia | reimplementation based on a disassembly | | OpenTyrian | Tyrian 2.1 | source port (the original Pascal source was released, then converted to C) | | OpenClaw | Captain Claw | reimplementation | | OpenTitus | Titus the Fox, Moktar | reimplementation | | sm64 (n64decomp) | Super Mario 64 | matching decompilation | | MAME | thousands of arcade games | emulation of the hardware; its drivers document each board | ## The rule set **What must be exact.** The order of the steps in a frame; positions in the game's own units (often fixed point, sub-pixels); collision boxes and the order of collision tests; animation frame counts; every random draw. Speedrunners and players notice a one-frame difference. **Where the rules live.** In code, in the frame loop and the actor update functions. Levels are data: tile maps, enemy placement, and often an **event script** (a list of "at position *n*, do *x*" records) that starts waves, changes speed and ends the level. Weapon and enemy stats are usually tables. Read the tables and the event format before porting the code that uses them. **Inputs.** Keys, joystick and mouse, read once a frame in most games. A demo is the input of each frame plus the starting seed. Fighting games buffer the last few frames of input to detect special moves. Port the buffer length and the matching rules exactly. **Interface.** The HUD (score, lives, energy bars) is drawn by the frame, usually straight to video memory. Menus, shops and the title screen are separate loops. Fonts are the game's own bitmaps. Port each screen from its code. **Time and determinism.** A fixed rate set by a timer interrupt or the display's refresh (70 Hz on VGA mode 13h; 60 Hz on NTSC consoles; 50 Hz on PAL). If the game waits for the vertical retrace, the refresh rate sets its speed. On consoles and arcade boards, a port may need to copy the slowdown (fewer updates when the screen is busy). **How to verify.** Play the game's own demos in the original and in the port, and compare every frame's state and screen. Fuzz the frame's fragments from real states (random enemies, shots, positions) to reach branches the demos never reach. Check collision code with many random pairs of boxes. **Pitfalls.** - An actor list processed in slot order, with new actors taking the first free slot: the slot order changes who is drawn on top and who is hit first. - Hit tests against the previous frame's positions. - Integer division and shifts on negative positions (see [ABI](https://tanepiper.github.io/game-frame/abi.md#integers)). - Palette effects (flashes, fades, colour cycling) are part of the frame and are easy to miss. - Music and sound effects often run on their own timer interrupt at a different rate from the frame. ## Connections - [First-person and 3D action](https://tanepiper.github.io/game-frame/games/first-person.md) shares the frame loop, demos and actor states. - [Strategy](https://tanepiper.github.io/game-frame/games/strategy.md) and [RPGs](https://tanepiper.github.io/game-frame/games/rpg.md) share tile maps and layered scrolling. - [Racing and sports](https://tanepiper.github.io/game-frame/games/racing-sports.md) shares per-frame physics in fixed point. --- # First-person and 3D action Shooters and action games seen from the player's eyes or just behind them: Wolfenstein 3D to Half-Life, and the immersive simulations (System Shock, Thief). They are [streams with controls](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds) in the fight, and [environments](https://tanepiper.github.io/game-frame/games/interaction.md) in the exploration: levels full of triggers, doors, switches and scripted events. ## Reference games | Game | Year, platform | Why it is a good example | |---|---|---| | Wolfenstein 3D | 1992, DOS | Ray-casting renderer on a grid; the simplest complete first-person engine | | Doom | 1993, DOS | BSP renderer, fixed-point maths, 35 Hz ticks, demos that are pure input | | Duke Nukem 3D | 1996, DOS | Build engine: sectors, portals, a game written on top of a licensed engine | | Quake | 1996, DOS | True 3D, a byte-code language (QuakeC) for game logic, client-server even in single player | | System Shock | 1994, DOS | Immersive simulation: physics, an interface inside the game view, scripted level events | | Half-Life | 1998, Windows | A licensed engine (GoldSrc) with a game DLL, scripted sequences, entity I/O | ## Projects to learn from | Project | Game | Approach | |---|---|---| | Chocolate Doom | Doom | source port that keeps every quirk, so old demos still play in sync | | Wolf4SDL | Wolfenstein 3D | source port | | EDuke32 | Duke Nukem 3D | source port of the Build engine and the game | | NBlood | Blood | reimplementation of the game code from reverse engineering, on EDuke32's engine | | ioquake3, QuakeSpasm | Quake III, Quake | source ports | | Shockolate | System Shock | source port | | Xash3D FWGS | Half-Life | engine reimplementation that runs the original game code and data | Doom and Quake were released as source code, so their ports are not reverse engineering. They are still the best references for how such engines work. Chocolate Doom shows how much work it takes to keep every quirk. Old demos play in sync only when every quirk is kept. ## The rule set **What must be exact.** Movement: acceleration, friction, step-up height, sliding along walls, all in the game's units (often 16.16 fixed point). The tick rate and the order of thinkers (the per-object update functions). Hit scans and projectile collision. Random draws: Doom reads a fixed table of 256 numbers through an index instead of running a generator. Demos stay in sync only if the index matches. **Where the rules live.** In code (thinkers, weapons, monsters as state tables of frames, durations and actions) and in level data: sectors or brushes, special lines and tags that link switches to doors, scripted sequences. Later games move logic into a script language or a game DLL with an entity interface. Read the level format and the state tables before porting code. **Inputs.** Keyboard and mouse. Mouse movement adds up between ticks; the game reads the total once per tick and scales it. Record the per-tick command (forward move, side move, turn, buttons). Most of these games store that command in their demo files. **Interface.** The status bar, automap and menus are drawn by the game. The weapon sprite and crosshair are part of the 3D view. Text uses the game's own fonts. **Time and determinism.** A fixed tick (35 Hz in Doom), with the renderer drawing as fast as it can, sometimes interpolating between ticks. The tick must be exact; the renderer need not run at the same rate in the port. **How to verify.** Play recorded demos and compare object positions and states each tick. For the renderer, compare frames of the same tick from the original and the port. Software renderers are exact enough to compare pixel by pixel; hardware renderers need image comparison with a tolerance. **Pitfalls.** - Thinker lists in insertion order, with removal during iteration. - Fixed-point multiply and divide helpers with their own rounding and overflow checks. - Trigonometry from lookup tables (fine angles), not from a maths library. - Renderers that depend on the screen size (field of view, aspect correction) differently from what the documentation says. ## Connections - [Arcade action](https://tanepiper.github.io/game-frame/games/arcade-action.md): frame loops, actor state tables, demos. - [Vehicle and space simulations](https://tanepiper.github.io/game-frame/games/vehicle-sims.md): software 3D, fixed-point maths. - [Adventure](https://tanepiper.github.io/game-frame/games/adventure.md): script interpreters (QuakeC, scripted sequences). - [Online games](https://tanepiper.github.io/game-frame/games/online.md): client-server split, prediction. --- # Racing and sports Driving games and simulations of sports: the player controls a car or a team, the game runs physics and AI opponents. They are [streams with controls](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds), with a large share of simulation (opponents, a championship, transfers in a football game). ## Reference games | Game | Year, platform | Why it is a good example | |---|---|---| | Stunts (4D Sports Driving) | 1990, DOS | 3D racing with a track editor and replays; fixed-point physics | | Carmageddon | 1997, DOS and Windows | Vehicle physics with damage, pedestrians, a debug-symbol demo build | | Need for Speed | 1994, DOS | Track and car data formats, AI drivers on racing lines | | Sensible World of Soccer | 1994, Amiga and DOS | Ball physics, player AI, a huge career database | | Super Mario Kart | 1992, SNES | Mode 7 tracks, item random draws weighted by position | ## Projects to learn from | Project | Game | Approach | |---|---|---| | Restunts | Stunts | reverse engineering of the DOS program toward a reimplementation | | dethrace | Carmageddon | reimplementation from reverse engineering, helped by Watcom debug symbols for an earlier internal build, found on the Splat Pack CD | | SWOS 96/97 port (by zlatkok) | Sensible World of Soccer 96/97 | reimplementation from disassembly | | OpenNFS | Need for Speed 1 to 5 | new engine that reads the original track and car data; NFS 2 and 3 load most completely | ## The rule set **What must be exact.** The physics step: integration order, friction and grip curves (often tables), collision response, and the tick rate. AI drivers and players: their decisions per tick and their random draws. The race state: positions, laps, timing to the tick. **Where the rules live.** In code (physics, AI) and in data: car parameters, track geometry, racing lines for the AI, team and player databases in sports games. Grip curves and gear ratios are usually tables: read them, do not refit them. **Inputs.** Analogue steering and throttle (joystick, wheel, mouse) or digital keys with a ramp the game applies. Record the value the game reads each tick, after its own filtering. Also find the filtering (dead zones, ramps, calibration). **Interface.** Speedometers, position and lap counters, mini-maps are drawn by the game. Sports games have large management interfaces (teams, transfers, tactics) that behave like [management games](https://tanepiper.github.io/game-frame/games/management.md). **Time and determinism.** A fixed physics step, sometimes several per drawn frame. Replays in most of these games are recorded inputs plus the seed, so the physics must repeat exactly. **How to verify.** Record a lap in the original (input per tick, or the game's own replay file) and play it in the port; compare the car's position, speed and rotation each tick. Small differences grow fast in physics: compare from the first tick. Stop at the first difference. **Pitfalls.** - Floating-point physics on x87 (32-bit games) rounds differently from SSE: see [ABI](https://tanepiper.github.io/game-frame/abi.md#floating-point). - Collision against the track in a fixed order of segments. - "Rubber banding": AI speed that depends on the player's position. - Time trials and championships store the best times in files. Treat those files as part of the save state. ## Connections - [Vehicle and space simulations](https://tanepiper.github.io/game-frame/games/vehicle-sims.md): physics, 3D, fixed point. - [Arcade action](https://tanepiper.github.io/game-frame/games/arcade-action.md): frame loops and per-frame input. - [Management](https://tanepiper.github.io/game-frame/games/management.md): career modes and databases. --- # Vehicle and space simulations Flight simulators, space combat and space trading games: the player flies something through a large world simulated in 3D. They mix a [stream with controls](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds) (combat, landing) with an [environment](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds) the player moves through (a galaxy, a theatre of war), and often with docked screens that wait for the player (trading, missions, ship fitting). ## Reference games | Game | Year, platform | Why it is a good example | |---|---|---| | Elite | 1984, BBC Micro | A galaxy of eight times 256 systems generated from one seed with a small generator | | Frontier: Elite II | 1993, DOS | Real orbits, a whole galaxy generated, a software 3D engine in overlays ([case study](https://tanepiper.github.io/game-frame/case-studies/frontier.md)) | | Wing Commander | 1990, DOS | Scaled sprite ships, a branching campaign, cut scenes in the engine | | X-Wing | 1993, DOS | Flat-shaded 3D, mission scripts with goals and triggers | | Falcon 3.0 | 1991, DOS | A full flight model and a dynamic campaign | | Descent | 1995, DOS | Six degrees of freedom inside portal-connected cubes | ## Projects to learn from | Project | Game | Approach | |---|---|---| | Elite, fully documented (bbcelite.com, by Mark Moxon) | Elite (BBC Micro and others) | the original source code, commented line by line, with articles on how each part works | | JJFFE | Frontier: First Encounters | reverse engineered from the DOS program into portable code | | GLFrontier (by Tom Morton) | Frontier: Elite II | the Amiga program's 68000 code translated to C | | fs2_open | FreeSpace 2 | source port | | DXX-Rebirth | Descent 1 and 2 | source port | ## The rule set **What must be exact.** The maths core: fixed-point or float formats, multiply and divide helpers, square roots, trigonometry tables, matrix and vector routines, and 64-bit positions where the world is large. Orbits and physics integration (the step and its order). Generation of the world from a seed: one wrong draw changes the whole galaxy. Players remember the names of the systems, so a wrong name is noticed. **Where the rules live.** Mostly in code, often hand-written assembly for the maths. Ships, weapons and goods are tables. Mission scripts and campaign trees are data. A generated world is stored only as a seed, so the generator code must be exact. **Inputs.** Joystick axes, mouse flight, keyboard. Throttle and view controls. Time acceleration is an input that changes how many steps a frame runs: record it. **Interface.** Cockpit instruments, radar and scanners are drawn by the game, often by the same 3D engine. Docked screens (markets, ship fitting) are software that waits. Star maps are 3D views with their own picking. **Time and determinism.** A fixed step multiplied by time acceleration. Far objects may be updated less often than near ones. Orbits are often computed in closed form from time, not integrated. The positions then depend on the clock's format and precision. **How to verify.** Emulate the maths routines on millions of random inputs and compare bit for bit. These routines are small and pure. Generate the galaxy or star systems from many seeds and compare every field. Compare frames of the 3D view from the same state. **Pitfalls.** - Routines that pass values in registers or register pairs rather than by the calling convention: emulate with the exact register set ([DOS games](https://tanepiper.github.io/game-frame/dos.md)). - Overlays: code loaded on demand into the same addresses. Know which overlay is loaded when you read an address. - Custom float formats in software (16-bit mantissa plus exponent), with their own rounding. - Perspective and clipping that differ by screen size. ## Connections - [First-person and 3D action](https://tanepiper.github.io/game-frame/games/first-person.md): software rendering, fixed point. - [Racing and sports](https://tanepiper.github.io/game-frame/games/racing-sports.md): physics and flight-model tables. - [RPGs](https://tanepiper.github.io/game-frame/games/rpg.md) and [Management](https://tanepiper.github.io/game-frame/games/management.md): trading and economies that run while unseen. --- # City building and management City builders, tycoon games and colony simulations: a world that runs on its own, slowly, and a player who arranges it through windows, menus and clicks. They are [software over a simulation](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds). The simulation must be exact; how exact the interface must be depends on the project's goal ([Who drives](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds)). ## Reference games | Game | Year, platform | Why it is a good example | |---|---|---| | Banished | 2014, Windows x64 | A complete C++ engine and game; a deterministic town simulation; a data-driven interface ([case study](https://tanepiper.github.io/game-frame/case-studies/banished.md)) | | SimCity 2000 | 1993, DOS | Grid simulation with zoning, budgets, disasters; sub-simulations at different rates | | Transport Tycoon Deluxe | 1995, DOS | Hand-written x86 assembly; vehicles, signals, an economy | | RollerCoaster Tycoon 2 | 2002, Windows | Hand-written assembly; thousands of guests each with needs and thoughts | | Caesar III | 1998, Windows | Walkers that deliver services along roads; the city's needs drawn from them | | Theme Hospital | 1997, DOS | Patients, staff and rooms as queues | | The Settlers II | 1996, DOS | Carriers on a road network, an economy of goods in flags and buildings | | Dungeon Keeper | 1997, DOS | Management of creatures with needs, plus a real-time strategy layer | ## Projects to learn from | Project | Game | Approach | |---|---|---| | Un-Banished | Banished | reimplementation from reverse engineering, checked bit for bit against the original | | OpenTTD | Transport Tycoon Deluxe | began as a reimplementation from disassembly; now extended far beyond the original | | OpenRCT2 | RollerCoaster Tycoon 2 | reimplementation from disassembly, function by function, while the game kept running | | OpenLoco | Chris Sawyer's Locomotion | reimplementation from disassembly, the same way as OpenRCT2 | | Julius, Augustus | Caesar III | reimplementation; Julius keeps the original's behaviour, Augustus changes it on purpose | | CorsixTH | Theme Hospital | engine reimplementation using the original data | | Return to the Roots | The Settlers II | reimplementation | | KeeperFX | Dungeon Keeper | rewrite that started from a disassembly and replaced functions step by step | | Micropolis | SimCity (1989) | source port of the released code | OpenRCT2 and OpenLoco are useful models for method: they linked their new code into the original program and replaced it one function at a time, so the game kept running and each replaced function could be compared. ## The rule set **What must be exact.** The simulation step and its order of systems; every random draw and its order; time and the calendar in the game's own units; the economy (prices, stock, demand); agent decisions (where a worker goes, what a guest wants). The save format, if the project loads the original's saves. **Where the rules live.** In code for the agents and the step, in data for buildings, goods, needs and prices. Agents are usually state machines with timers; the jobs or tasks they take come from queues with a fixed order. Read the data first. Many rules are numbers in tables. **Inputs.** Mostly clicks on the interface and on the map, which become **commands** (place a building, set a tax, assign a worker). Model the input as commands, each applied at a known step. A recording is then a list of commands with step numbers. The same commands can drive lockstep multiplayer. Game speed is an input: it changes how many steps run per frame. **Interface.** Large: many windows, lists, tooltips, graphs, a build menu, a map overlay. In older games every window is code; newer games read layouts from data. Either way the interface reads the simulation and sends commands: keep it out of the simulation's code in the port. [Capturing the interface](https://tanepiper.github.io/game-frame/observe/ui.md) explains how to inventory it. **Time and determinism.** A fixed step (often 10 to 60 per second) and speeds that run more steps per frame. Sub-systems may run every *n* steps or spread their work over several steps (a slice of the map each step). The simulation must give the same world from the same seed and commands. **How to verify.** Load the original's saves and continue them; compare the state after *n* steps. Run the original and the port from one seed with the same commands and compare digests of the world each step. Emulate the small pure parts (random generator, noise, terrain, pathfinding, name generators) on thousands of inputs. **Pitfalls.** - Lists of agents or buildings iterated in creation order, with holes from removals reused. - Sorting with an unstable sort (`qsort`): equal elements come out in an order you must copy. - Work spread over steps ("update a tenth of the map each step"): keep the slice order. - Numbers shown in the interface rounded differently from the numbers the simulation uses. - Per-player state (settings, achievements, difficulty) read by the simulation: it breaks lockstep multiplayer ([Networking](https://tanepiper.github.io/game-frame/game-patterns.md#networking-if-any)). ## Connections - [Strategy](https://tanepiper.github.io/game-frame/games/strategy.md): pathfinding, lockstep, commands. - [RPGs](https://tanepiper.github.io/game-frame/games/rpg.md): agents with needs and schedules. - [Racing and sports](https://tanepiper.github.io/game-frame/games/racing-sports.md): management modes of sports games. --- # Strategy: real time, turns and tactics Games where the player commands many units against opponents: real-time strategy (RTS), turn-based empire games (4X), and squad tactics. Real-time strategy is [software over a simulation](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds) that must stay identical on every player's machine; turn-based strategy is [turns and choices](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds). Many games mix them (a real-time world map with turn-based battles). ## Reference games | Game | Year, platform | Why it is a good example | |---|---|---| | Dune II | 1992, DOS | The first modern RTS: units, harvesting, a sidebar to build from | | Command & Conquer | 1995, DOS | Lockstep multiplayer, unit tables in INI files | | Warcraft II | 1995, DOS | Tile-based maps, lockstep over IPX and modem | | StarCraft | 1998, Windows | Exact lockstep, replays as commands, unit AI and pathfinding studied in depth | | Civilization | 1991, DOS | Turn-based empire on a grid; AI players; a world generated from a seed | | UFO: Enemy Unknown (X-COM) | 1994, DOS | A real-time world map and turn-based battles; time units, line of fire | | Heroes of Might and Magic II and III | 1996 and 1999 | Adventure map in turns, battles on a hex grid, large rule tables | ## Projects to learn from | Project | Game | Approach | |---|---|---| | OpenDUNE | Dune II | reimplementation from disassembly, keeping the original's behaviour and bugs | | OpenRA | Command & Conquer, Red Alert, Dune 2000 | new engine using the original data; changes the rules on purpose | | Wargus (on Stratagus) | Warcraft II | the original data on a general RTS engine | | OpenBW | StarCraft: Brood War | reimplementation of the simulation, exact enough to play replays | | OpenXcom | UFO: Enemy Unknown, Terror from the Deep | reimplementation | | OpenApoc | X-COM: Apocalypse | reimplementation | | fheroes2 | Heroes of Might and Magic II | reimplementation | | VCMI | Heroes of Might and Magic III | engine reimplementation using the original data | | JA2 Stracciatella | Jagged Alliance 2 | source port | Electronic Arts released the source of Command & Conquer and Red Alert in 2020. Where source exists, read it before reverse engineering. ## The rule set **What must be exact.** For real time: the simulation tick, the order units are updated, every random draw, pathfinding and its tie-breaks, combat formulas, and integer rounding. Lockstep multiplayer and replays fail on the first difference. For turns: the rules of each action, the order of resolution (who moves first, when effects apply), the AI's choices and draws. **Where the rules live.** Unit, weapon and building stats in data (tables, INI files, binary records). Behaviour in code: unit state machines, the AI, pathfinding. Missions in scripts or trigger tables (when *x* happens, do *y*). Generated maps from a seed. **Inputs.** Commands: select, move, attack, build, each with targets. In lockstep games the commands are what travels over the network and what a replay stores, each tagged with the tick it takes effect. Find that command format early. The port can use it as its input record. Selections and the camera are not commands in most games; check yours. **Interface.** Sidebars, minimaps, unit panels, the selection box, and menus. Players rely on hotkeys and grouping (Ctrl + number), so port them exactly. Turn-based games have dense screens of numbers and lists. **Time and determinism.** A fixed tick for real time, with a game speed that changes ticks per second. Turns for turn-based games, with animations that do not change the state. Lockstep requires that nothing per-player (the local clock, settings, the camera) reaches the simulation. **How to verify.** Replays give the best evidence. If the original stores replays as commands, play them in the port and compare the state at every tick. Without replays, record commands from the original running under a debugger, or script both. Pathfinding: emulate the original finder on many start and end points and compare every path. **Pitfalls.** - Units updated in an order that changes when units are created and destroyed. - Pathfinding ties broken by the order of the open list. - Fog of war and visibility updated on a different schedule from movement. - AI players that read state the human player cannot see, or that use their own random generator. ## Connections - [Management](https://tanepiper.github.io/game-frame/games/management.md): simulation steps, commands, saves. - [RPGs](https://tanepiper.github.io/game-frame/games/rpg.md): rule tables, turn-based battles. - [Online games](https://tanepiper.github.io/game-frame/games/online.md): lockstep against client-server. --- # Role-playing games Games where the player builds characters and explores a world: computer RPGs, dungeon crawlers, roguelikes and Japanese RPGs. They are [environments](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds) the player moves through, often with [turn-based](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds) battles, dialogue that waits for choices, and a large world state. ## Reference games | Game | Year, platform | Why it is a good example | |---|---|---| | Dungeon Master | 1987, Atari ST | Real-time dungeon crawling on a grid; a world full of moving objects | | Ultima VII | 1992, DOS | A world where every person follows a schedule; its own script byte code | | Ultima Underworld | 1992, DOS | First-person 3D dungeon before Doom, with physics and conversations | | The Elder Scrolls II: Daggerfall | 1996, DOS | A huge generated world, quests from templates | | Diablo | 1996, Windows | Real-time action RPG; random dungeons, item generation, a debug build whose symbols helped Devilution | | Fallout and Fallout 2 | 1997 and 1998, Windows | Turn-based combat, dialogue scripts in byte code, a large save state | | Baldur's Gate | 1998, Windows | Infinity Engine: real-time with pause, D&D rules as tables and scripts | | Morrowind | 2002, Windows | An open world, a script language for quests, plugins that can change any part of the game | | NetHack | 1987 onward, many | Roguelike with released source; the reference for turn-based dungeon rules | ## Projects to learn from | Project | Game | Approach | |---|---|---| | DevilutionX (from Devilution) | Diablo | Devilution reconstructed the source with help from debug symbols; DevilutionX ports it | | Fallout 2 Community Edition | Fallout 2 | reimplementation from reverse engineering | | GemRB | Baldur's Gate and other Infinity Engine games | engine reimplementation using the original data | | Exult | Ultima VII | engine reimplementation, including the script interpreter | | Underworld Adventures | Ultima Underworld | engine reimplementation | | OpenMW | Morrowind | engine reimplementation, compatible with the original data and plugins | | Daggerfall Unity | Daggerfall | engine reimplementation in Unity | | OpenEnroth | Might and Magic VI to VIII | reimplementation | | ReDMCSB (by Christophe Fontanel) | Dungeon Master, Chaos Strikes Back | source code reconstructed from the binaries of many versions | | pokered, pokecrystal (pret) | Pokémon Red, Crystal | matching decompilations | ## The rule set **What must be exact.** Character rules: attributes, levels, experience curves, skill checks, hit and damage formulas, every random draw and its order. Item and monster generation. Dialogue and quest state: which flags a conversation reads and sets. Turn order in battle. **Where the rules live.** Much is in data: tables of classes, items, spells, monsters; maps with objects and triggers; and **scripts**, often in a byte code the game interprets (Ultima VII's usecode, Fallout's scripts, Infinity Engine's BCS). Once the interpreter is ported, the port runs the game's own scripts. The core formulas (to-hit, damage, experience) are in code. **Inputs.** Movement (keys or clicks to move), commands (talk, take, use, cast), menu and dialogue choices. A turn-based battle takes one command per turn; real-time games read input per frame. For recordings, store commands with their step or turn. **Interface.** Large and custom: inventories with drag and drop, character sheets, spell books, dialogue windows, maps and journals. The interface often holds rules too (what can be dropped where, stack sizes, encumbrance). Inventory every screen ([Capturing the interface](https://tanepiper.github.io/game-frame/observe/ui.md)). **Time and determinism.** Mixed: real time while exploring, turns in battle, a game clock with days and schedules. Things away from the player may be simulated in less detail or frozen. **How to verify.** Save in the original, load in the port, and compare the whole state. Run battles from a saved state and the same commands. Emulate formulas and generators (loot, dungeons) on many seeds. Script conversations through every branch and compare the flags. **Pitfalls.** - Scripts that depend on the order objects are stored in a map file. - Global flags shared between quests, set in unexpected places. - Random draws spent on cosmetic things (an idle animation) that shift every later draw. - Saves that store only part of the world and regenerate the rest from a seed. ## Connections - [Adventure](https://tanepiper.github.io/game-frame/games/adventure.md): script interpreters and dialogue. - [Strategy](https://tanepiper.github.io/game-frame/games/strategy.md): turn-based battles and rule tables. - [First-person](https://tanepiper.github.io/game-frame/games/first-person.md): 3D exploration and triggers. - [Management](https://tanepiper.github.io/game-frame/games/management.md): schedules and needs of people in the world. --- # Adventure and story games Point-and-click adventures, text adventures, interactive fiction and visual novels. They are [turns and choices](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds): nothing happens until the player acts. The story is a script that waits at every choice. Most were built on a **script engine**: the game is byte code in data files, run by an interpreter in the executable. ## Reference games | Game | Year, platform | Why it is a good example | |---|---|---| | Zork I | 1980, many | Z-machine byte code: the same story file runs on any interpreter | | King's Quest | 1984, PC | Sierra's AGI: logic scripts, pictures drawn from vector commands | | Maniac Mansion, The Secret of Monkey Island | 1987 and 1990 | LucasArts' SCUMM: rooms, verbs, scripts and threads | | Space Quest IV | 1991, DOS | Sierra's SCI: an object-oriented script language and its VM | | Myst | 1993, Mac and Windows | Slide-show navigation over HyperCard-like stacks | | Grim Fandango | 1998, Windows | 3D characters on pre-rendered rooms; Lua scripts | | Visual novels on NScripter | 1990s onward, Windows | A text script with commands for images, music and choices | ## Projects to learn from | Project | Games | Approach | |---|---|---| | ScummVM | SCUMM, AGI, SCI, Myst (Mohawk), Grim Fandango and hundreds more | engine reimplementations, one per original engine | | Frotz and other Z-machine interpreters | Infocom games | interpreters written from the published Z-machine standard | | ONScripter | NScripter visual novels | engine reimplementation | ScummVM is the largest body of work on this family. Its engines and wiki document many script formats and interpreter quirks. ## The rule set **What must be exact.** The interpreter: every opcode, its operand formats, its effect on variables, flags and objects, and its timing where scripts wait. Text, parsers and verb handling. The object and room state that saves record. **Where the rules live.** Almost entirely in data, as scripts. The executable is the interpreter, the graphics and sound drivers, and a few hard-coded puzzles. Find the interpreter's dispatch table first: it is a loop over opcodes calling one function each. **Inputs.** Clicks on verbs and objects, typed commands (text parsers), dialogue choices, and keyboard shortcuts. Store recordings as commands. **Interface.** The verb bar, inventory, dialogue choices, text windows, the cursor and its changes over hotspots. Fonts and text rendering, including line breaking, are part of the look. **Time and determinism.** Mostly waits for the player. Animations and timed scenes run on a tick the scripts can wait on. A few puzzles are timed or random: find those scripts. **How to verify.** Run the same command list in the original and the port and compare the variables after each command. Walk through the whole game from a known solution script. Disassemble every script with your decoder and check it consumes every byte of every file. **Pitfalls.** - Several versions of the same engine, with opcodes added, removed or changed between games. - Scripts that rely on interpreter bugs (an uninitialised variable, an overflow). - Text parsers with dictionaries and synonyms in data, and word matching that truncates. - Copy protection inside the scripts (questions from the manual). Do not bypass protection in a way the law where you live forbids ([Legal](https://tanepiper.github.io/game-frame/legal.md)). ## Connections - [RPGs](https://tanepiper.github.io/game-frame/games/rpg.md): dialogue scripts and byte-code interpreters. - [Puzzle and board games](https://tanepiper.github.io/game-frame/games/puzzle-board.md): logic that waits for the player. - [First-person](https://tanepiper.github.io/game-frame/games/first-person.md): scripted sequences and in-game script languages. --- # Puzzle, board and card games Falling-block and logic puzzles, board games, card games, and the small games that shipped with operating systems. Board and card games are [turns and choices](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds); action puzzles (Tetris, Lemmings) are [streams with controls](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds). They are often small, so they make good first projects for practising the whole method. ## Reference games | Game | Year, platform | Why it is a good example | |---|---|---| | Tetris | 1989, NES and Game Boy | A random piece generator, gravity tables per level, input delays, all well documented | | Lemmings | 1991, Amiga and DOS | Each lemming a small state machine on a pixel-exact terrain mask | | The Incredible Machine | 1993, DOS | A small physics engine and parts with rules | | Microsoft Solitaire and FreeCell | 1990 onward, Windows | Shuffles from a seed: FreeCell's numbered deals come from the C runtime's `rand` | | Chessmaster | 1986 onward | A search engine with an opening book, a board and a rules core | | Minesweeper | 1990 onward, Windows | Board generation from a random seed, the first-click rule | ## Projects to learn from Few public reimplementations exist for this family: most games are small enough to rebuild from observation, and many clones copy only the idea. Two kinds of work are useful: | Work | What it shows | |---|---| | Disassemblies of NES and Game Boy Tetris by the speedrunning community | Exact piece randomisers, gravity, entry delays; the rules that competitive play depends on | | Write-ups of FreeCell deal numbers | How a shuffle from the C runtime's `rand` makes numbered deals reproducible | ## The rule set **What must be exact.** The random generator and how it picks pieces, cards or mines (with any "bag" or anti-repeat rule); the rules of legal moves; scoring; timers and delays in frames. **Where the rules live.** Almost all in code, and small. Levels and puzzles are data. Card deals and boards come from the random generator, often the C runtime's `rand` seeded with a number shown to the player (a deal number) or with the clock. **Inputs.** Keys or clicks, sometimes with repeat delays (auto-shift in Tetris) that the port must copy. Record per frame for action puzzles, per move for board and card games. **Interface.** Simple. Players know where everything is, so keep every position exact. Animations (a card flying, a line clearing) may block input for a number of frames. Port those frame counts. **Time and determinism.** Turn-based games wait. Action puzzles run at the console's or the screen's refresh rate; gravity is a table of frames per row. **How to verify.** Emulate the generator and compare thousands of deals or pieces from many seeds. Play recorded games move by move and compare the board. **Pitfalls.** - A generator seeded from the clock: record the seed, or make it an input. - Animation frames that delay input (the "entry delay"), easy to miss without frame stepping. - Scores that overflow or cap at the number of digits shown. ## Connections - [Arcade action](https://tanepiper.github.io/game-frame/games/arcade-action.md): frame-exact input for action puzzles. - [Adventure](https://tanepiper.github.io/game-frame/games/adventure.md): logic that waits for the player. - [Random number generators](https://tanepiper.github.io/game-frame/game-patterns.md#random-number-generators): most of what must be exact in these games. --- # Online games Massively multiplayer games and other games where a server owned by the publisher holds the world. The main rules (combat results, loot, the economy) run on a server you do not have. They are [environments](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds) shared by many players. Read [Legal](https://tanepiper.github.io/game-frame/legal.md) before starting. Online games add limits that single-player games do not have: terms of service you agreed to, servers whose owners forbid testing against them, client protection that many countries' laws cover, and rights holders who have taken projects to court. ## Reference games | Game | Year | Why it is a good example | |---|---|---| | Ultima Online | 1997 | Early MMO with a documented packet protocol and long-running server emulators | | EverQuest | 1999 | 3D MMO; zones as separate servers; a server emulator project for decades | | Phantasy Star Online | 2000, Dreamcast and PC | A console online game still played on community servers | | City of Heroes | 2004 | Shut down in 2012; a community server was later licensed by the publisher | | World of Warcraft | 2004 | The best known case of server emulation, and of legal action against private servers | ## Projects to learn from | Project | Game | Approach | |---|---|---| | RunUO, ServUO | Ultima Online | server emulators | | EQEmu | EverQuest | server emulator | | newserv | Phantasy Star Online | server emulator | | City of Heroes: Homecoming | City of Heroes | a community server, licensed by NCSoft in 2024 | These rebuilt the **server** from observing the client and its traffic, because the client was available and the server was not. Their rules (combat formulas, loot tables) are often approximations rebuilt from what players observed. ## The rule set **What must be exact.** The protocol: message formats, encryption or obfuscation, ordering and timing, so the original client accepts the server. Server rules can only be as exact as the evidence: captured traffic, client-side formulas, players' documentation. Mark them *inferred*. **Where the rules live.** The rules are split. The client has the interface, rendering, prediction and some formulas (to show numbers before the server answers). The server has the authority. Data files on the client often include tables the server also uses. **Inputs.** For the client: the player's input and the server's messages. For a server: the clients' messages. Record traffic only from your own sessions, on servers you are allowed to use. **Interface.** As large as an RPG's, plus chat, groups, trading, mail and auction houses. **Time and determinism.** Server ticks; the client interpolates and predicts. A port is exact when it follows the protocol and gives the server's answers. **How to verify.** Against captured traffic from your own sessions (while the official service exists and allows it), and against the client: does it accept every message, and show the same thing? **Pitfalls.** - Testing against a live service without permission breaks its terms. It can lead to bans or legal action. - Anti-cheat and protection in the client: bypassing it may be illegal. It is not needed to study a game you run offline. - Traffic that contains other players' data: keep it private. Do not publish it. ## Connections - [RPGs](https://tanepiper.github.io/game-frame/games/rpg.md): character rules, items, quests. - [Strategy](https://tanepiper.github.io/game-frame/games/strategy.md): lockstep compared with client-server. - [First-person](https://tanepiper.github.io/game-frame/games/first-person.md): client prediction and server authority in shooters. --- # Home computers and the PC This section covers the home computers, the PC and the Mac that games were written for. Each page covers one family of machines. An exact reimplementation of a game needs three facts about its machine: the CPU family and its arithmetic quirks, the memory and banking state when the code ran, and the timing the game was written against. On 8-bit machines the game logic depends on that timing. ## Ask your agent Start on a home computer game. Copy this prompt into your coding agent: ```text In my GameFrame project, start work on a game for . 1. Read docs/computers/README.md, docs/computers/cpu-families.md and the page for . 2. Identify the media image format and the exact release. Record its checksum. 3. Set up the emulator with a debugger that the page names. Tell me what it needs before you install anything. 4. Run the game past its loader and save a memory snapshot once the game code is in memory. 5. Record the CPU, the memory banking and the frame timing in docs/FINDINGS.md. ``` ## Across all machines - [CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md): the eleven CPU families, their byte order, Ghidra, Unicorn and Capstone support, small cores for ground truth, and the arithmetic traps. ## 8-bit home computers - [Sinclair ZX81 and ZX Spectrum](https://tanepiper.github.io/game-frame/computers/sinclair.md): paging ports, contended memory per model, frame lengths, TZX and the snapshot formats, SkoolKit. - [BBC Micro and Acorn Electron](https://tanepiper.github.io/game-frame/computers/acorn-8bit.md): sideways ROMs, the 8271 and 1770 disk controllers, UEF and SSD, Elite on the 6502. - [Commodore VIC-20 and C64](https://tanepiper.github.io/game-frame/computers/commodore-8bit.md): `$01` banking, VIC-II cycles and badlines, SID revisions, 1541 drive code, the image formats, the VICE monitor. - [Amstrad CPC](https://tanepiper.github.io/game-frame/computers/amstrad-cpc.md): NOP-time timing, CRTC types, gate array interrupts, CDT and extended DSK. - [MSX, PC-8801 and Sharp X1](https://tanepiper.github.io/game-frame/computers/msx-and-japan-8bit.md): slots, MegaROM mappers, D88, openMSX. - [Atari 400/800, XL and XE](https://tanepiper.github.io/game-frame/computers/atari-8bit.md): ANTIC display lists, display list interrupts, PORTB banking, ATR, XEX and ATX, Altirra. - [Apple II](https://tanepiper.github.io/game-frame/computers/apple-ii.md): soft switches, the language card, GCR disk protection, WOZ. - [Other 8-bit computers](https://tanepiper.github.io/game-frame/computers/other-8bit.md): the Dragon and CoCo, the Oric, the TI-99/4A. ## 16-bit and 32-bit home computers - [Commodore Amiga](https://tanepiper.github.io/game-frame/computers/amiga.md): OCS, ECS and AGA, the Copper, Blitter and Paula, takeover games, ADF and IPF, Copylock, ghidra-amiga, WinUAE. - [Atari ST family](https://tanepiper.github.io/game-frame/computers/atari-st.md): the Shifter, the STE additions, ST, MSA and STX images, the Hatari debugger, glFrontier and ReDMCSB. - [Acorn Archimedes and Risc PC](https://tanepiper.github.io/game-frame/computers/acorn-32bit.md): 26-bit ARM2 and ARM3 code, MEMC, VIDC and IOC, the ARMv4 floor in Ghidra. - [Japanese 16-bit and 32-bit computers](https://tanepiper.github.io/game-frame/computers/japan-16-32bit.md): the X68000, PC-98 and FM Towns. ## The PC and the Mac - [The IBM PC and compatibles](https://tanepiper.github.io/game-frame/computers/ibm-pc.md): the eras and their executable formats, Tandy paths, compiler fingerprints, copy protection, what runs where today. - [The Macintosh](https://tanepiper.github.io/game-frame/computers/macintosh.md): 68k resource forks and the A5 world, PEF, Mach-O, Rosetta, the emulator for each era, Marathon and Aleph One. - [Engine-era PC games](https://tanepiper.github.io/game-frame/computers/modern-pc-engines.md): Unity with Mono and IL2CPP, Unreal `.pak` files, Godot `.pck` files, anti-cheat, Linux builds as a second binary. ## The shape of each page Each family page opens with one short paragraph. Then it has one section per machine. Each machine section has the same headings in the same order. 1. **Hardware**: the years, the CPU, memory and banking, the video and sound chips, timing. 2. **How games were made and loaded**: languages, loaders, operating systems. 3. **Media and image formats**: tape, disk and cartridge images, and what each one keeps. 4. **Tools**: emulators with debuggers, Ghidra and Unicorn support. 5. **Projects to learn from**: disassemblies, source releases and reimplementations. 6. **For an exact reimplementation**: what to settle before porting. An agent can load one page and act from it alone. The [CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md) page holds the facts that several pages share. A fact marked "(not confirmed)" has one source and lacks a second. Treat it as a lead and check it before you rely on it. ## Four groups of machines The machines split into four groups with different first steps. | Group | First step | Pages | |---|---|---| | 8-bit home computers | Get past the loader, then learn the frame timing | Sinclair to other 8-bit | | 16-bit and 32-bit home computers | Learn the custom chips, because many games bypass the OS | Amiga, Atari ST, Acorn 32-bit, Japanese 16/32-bit | | The PC and the Mac | Identify the executable format of the era. The loader and the tools depend on it | IBM PC, Macintosh, engine-era games | | Consoles | See [Console games](https://tanepiper.github.io/game-frame/consoles/index.md) | | ## Snapshot after the loader The first job on a tape or disk game is usually to get past the loader. Run the game under an emulator until the game code sits in memory. Then take a snapshot. A reimplementation does not need to copy the protection code that runs before the game starts. It therefore normally starts from the decrypted image in memory. Keep the lossless media image (TZX, G64, WOZ, EDSK, ATX, IPF, STX) as the record of what shipped. Copy protection is described factually on each page. The law on working around it differs by country ([Legal](https://tanepiper.github.io/game-frame/legal.md), [the law worldwide](https://tanepiper.github.io/game-frame/preservation/law/index.md)). You decide which route to take. ## How games were written on 8-bit machines Commercial games were mostly hand-written assembly. BASIC appears in loaders and in budget and type-in games. Tape was the main medium in the UK and Europe. Disk was the main medium in the US and Japan. Cartridges were common on the MSX, the VIC-20 and the Atari. On four machines the game logic depends closely on exact timing. The Spectrum has contended memory. The C64 has VIC-II badlines and raster interrupts. The Atari has ANTIC DMA and display list interrupts. The CPC has wait states that round every access to 4 cycles. ## Related pages - [CPUs and floating point](https://tanepiper.github.io/game-frame/hardware/cpus.md): the x86 generations and the x87 FPU. - [Hardware of the era](https://tanepiper.github.io/game-frame/hardware/index.md): PC graphics, sound, memory and storage. - [DOS games](https://tanepiper.github.io/game-frame/dos.md): 16-bit programs, Borland debug symbols, the DOS emulators. - [Console games](https://tanepiper.github.io/game-frame/consoles/index.md) and [tools for console work](https://tanepiper.github.io/game-frame/consoles/tools.md). - [Software engines and middleware](https://tanepiper.github.io/game-frame/engines/index.md). - The skills for each step: [re-inventory](https://tanepiper.github.io/game-frame/skills/re-inventory.md), [re-observe](https://tanepiper.github.io/game-frame/skills/re-observe.md), [re-ghidra](https://tanepiper.github.io/game-frame/skills/re-ghidra.md), [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md), [re-format](https://tanepiper.github.io/game-frame/skills/re-format.md), [re-port](https://tanepiper.github.io/game-frame/skills/re-port.md) and [re-verify](https://tanepiper.github.io/game-frame/skills/re-verify.md). ## Not covered yet The Sinclair QL (68008) and the SAM Coupé (Z80B) belong with these machines. Their pages still need sources. --- # CPU families Eleven CPU families cover almost every game platform: the 8080/Z80/SM83 line, the 6502 line, the 6809, the 68000, the TMS9900, the CP1600, x86, ARM, MIPS, SuperH and PowerPC. A few one-off co-processors add to them, such as the Cell SPU and the Jaguar's RISC cores. The CPU family tells you the byte order, the disassembler to use, the emulator cores that can give ground truth, and the arithmetic a port must copy. This page covers every family except the x86 details, which are in [CPUs and floating point](https://tanepiper.github.io/game-frame/hardware/cpus.md). ## Byte order Every 8-bit family here is little-endian except the 6809. Every 16-bit console CPU except x86 is big-endian. A data-file decoder must follow the byte order of the machine that wrote the file ([re-format](https://tanepiper.github.io/game-frame/skills/re-format.md)). A file copied between a 68000 machine and an ARM or x86 machine may have been converted to the other byte order. ## The 8080, Z80 and SM83 The **Z80** launched in July 1976 and runs Intel 8080 code. It adds an alternate register set (AF', BC', DE', HL'), the index registers IX and IY, and the I and R registers. Its undocumented instructions include the 8-bit halves of IX and IY ([Wikipedia: Zilog Z80](https://en.wikipedia.org/wiki/Zilog_Z80)). Ghidra's Z80 spec defines those IXL, IXH, IYL and IYH halves ([z80.slaspec](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/Z80/data/languages/z80.slaspec)). The Z80 has no multiply instruction. The Game Boy's **Sharp SM83** is a cut-down relative. Its registers are AF, BC, DE, HL, SP and PC. The flags Z, N, H and C sit in bits 7 to 4. Only DAA reads N and H ([Pan Docs: CPU Registers and Flags](https://gbdev.io/pandocs/CPU_Registers_and_Flags.html)). The Game Boy Advance carries both an ARM7TDMI and an SM83 for older cartridges ([Copetti: Game Boy Advance](https://www.copetti.org/writings/consoles/game-boy-advance/)). The Z80 runs the ZX81, Spectrum, CPC, MSX, PC-8801 and X1, and several consoles. ## The 6502 line The **6502** appeared in September 1975. It has 8-bit A, X, Y and S registers, a status register P and a 16-bit PC. Zero page (`$00`–`$FF`) takes a one-byte address and runs faster. The stack is fixed at `$0100`–`$01FF`. The NMOS part leaves 105 of 256 opcodes undefined. Its `JMP ($xxFF)` indirect bug was fixed in the 65C02. The 6507 (Atari 2600) has 8 KB of address space and no interrupts. The 6510 (C64) adds an I/O port at `$0000`/`$0001` ([Wikipedia: MOS Technology 6502](https://en.wikipedia.org/wiki/MOS_Technology_6502)). The NES **2A03** is a 6502 core without decimal mode, with an APU and OAM DMA at `$4014`. The PAL RP2A07 uses a different clock divider ([NESdev Wiki: 2A03](https://www.nesdev.org/wiki/2A03)). The PC Engine's **HuC6280** is a 65C02-style core with extra instructions, an MMU that maps 64 KB onto 2 MB, a timer and a PSG ([Wikipedia: Hudson Soft HuC6280](https://en.wikipedia.org/wiki/Hudson_Soft_HuC6280)). The **65C816** (1985) resets into a 65C02-like emulation mode. In native mode the m and x flags set the widths of the accumulator and the index registers. Addresses become 24-bit, with DB and DP registers. The SNES 5A22 and the Apple IIgs use it ([Wikipedia: WDC 65C816](https://en.wikipedia.org/wiki/WDC_65C816)). The same opcode bytes decode differently by those flags. A disassembler must track the REP and SEP state. If the disassembler guesses a width wrong, the instructions after the first immediate operand decode wrongly. The 6502 family runs the BBC Micro and Electron, VIC-20, C64, Atari 8-bit, Oric and Apple II. ## The 6809 The **6809** (1978) joins A and B into a 16-bit D register. It has X, Y, U and S, an 8-bit direct page register and an 8x8 hardware multiply. Its PC-relative addressing suits position-independent code. It ran the Vectrex, Williams arcade games, the Dragon and the CoCo. The Hitachi 6309 adds registers, block moves and division ([Wikipedia: Motorola 6809](https://en.wikipedia.org/wiki/Motorola_6809)). Ghidra describes it as big-endian ([6800.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/MC6800/data/languages/6800.ldefs)). ## The 68000 The **68000** (September 1979) has eight 32-bit data registers and eight address registers. A7 is split into user and supervisor stack pointers. It has a 16-bit ALU, a 24-bit address bus, an X (extend) flag, MULU and MULS, DIVU and DIVS, BCD instructions and an address-error exception. It ran the Mega Drive (7.6 MHz), the Saturn's sound CPU, the Neo Geo, the Jaguar, the Amiga, the Atari ST, the X68000 and boards such as System 16 and CPS-2 ([Wikipedia: Motorola 68000](https://en.wikipedia.org/wiki/Motorola_68000)). ## The TMS9900 The **TMS9900** (June 1976) is a 16-bit big-endian CPU with only PC, WP and ST on the chip. Its sixteen general registers are held in RAM at the workspace pointer. BLWP saves WP, PC and ST into workspace registers 13 to 15 ([Wikipedia: TMS9900](https://en.wikipedia.org/wiki/Texas_Instruments_TMS9900)). A register read in its disassembly is a memory read. To trace a function, trace the workspace RAM. ## The CP1600 The **CP1600** (February 1975) addresses only 16-bit words and uses 10 bits of each opcode. R7 is the program counter and R6 the stack pointer. SDBD builds a 16-bit operand from two 10-bit ROM reads. Ghidra describes it as `CP1600:BE:16:default` ([Wikipedia: CP1600](https://en.wikipedia.org/wiki/General_Instrument_CP1600); [CP1600.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/CP1600/data/languages/CP1600.ldefs)). It ran the Intellivision. ## x86 The x86 generations, the x87 FPU, MMX, 3DNow! and SSE are on [CPUs and floating point](https://tanepiper.github.io/game-frame/hardware/cpus.md). The 16-bit DOS details are on [DOS games](https://tanepiper.github.io/game-frame/dos.md). x86 runs the PC, the PC-98, the FM Towns, the WonderSwan, the Xbox and the consoles from the PS4 on. ## ARM On **ARM**, nearly every 32-bit instruction carries a 4-bit condition. Thumb code is 16-bit, with eight registers and no conditional execution. The two kinds mix in one program ([Copetti: Game Boy Advance](https://www.copetti.org/writings/consoles/game-boy-advance/)). The early Archimedes ARM2 and ARM3 use a 26-bit address mode with the flags in R15. Old Archimedes games depend on it ([Acorn 32-bit](https://tanepiper.github.io/game-frame/computers/acorn-32bit.md)). ## MIPS The PlayStation's R3000A-compatible core runs at 33.87 MHz. It has no FPU, a fixed-point GTE, a load delay slot and a branch delay slot ([Copetti: PlayStation](https://www.copetti.org/writings/consoles/playstation/)). The N64's VR4300 is MIPS III and always runs in kernel mode ([Copetti: Nintendo 64](https://www.copetti.org/writings/consoles/nintendo-64/)). The PS2's R5900 adds 128-bit MMI SIMD and two vector units ([Wikipedia: Emotion Engine](https://en.wikipedia.org/wiki/Emotion_Engine)). ## SuperH **SuperH** (1992) uses fixed 16-bit instructions and one branch delay slot. The SH-2 ran the Saturn, the 32X and CPS-3. The SH-4 ran the Dreamcast ([Wikipedia: SuperH](https://en.wikipedia.org/wiki/SuperH)). ## PowerPC The GameCube's Gekko adds about 50 paired-single instructions. The Wii's Broadway and the Wii U's Espresso descend from it ([Wikipedia: Gekko](https://en.wikipedia.org/wiki/Gekko_(processor))). The Xbox 360's Xenon has three in-order big-endian cores with VMX128 ([Wikipedia: Xenon](https://en.wikipedia.org/wiki/Xenon_(processor))). PowerPC also ran the PowerPC Macs ([Macintosh](https://tanepiper.github.io/game-frame/computers/macintosh.md)). ## Co-processors The PS3's **Cell** pairs one PPE with SPUs. Each SPU has its own 128-bit instruction set, a 256 KiB local store and access to main memory only by DMA ([Wikipedia: Cell](https://en.wikipedia.org/wiki/Cell_(processor))). The Jaguar's **Tom and Jerry** are 32-bit RISC cores at 26.591 MHz with 4 KB and 8 KB of local RAM. A flaw in the memory controller makes it hard to run their code from main RAM. Many developers ran their code on the 68000 instead ([Wikipedia: Atari Jaguar](https://en.wikipedia.org/wiki/Atari_Jaguar)). Tom has two banks of thirty-two 32-bit registers (not confirmed) ([Jagdox general](https://www.mulle-kybernetik.com/jagdox/general.html)). ## Tools per family The support below is for Ghidra release `Ghidra_12.1.4_build`, Unicorn 2.1.4 and Capstone's `next` branch at 6.0.0-Alpha11 ([Ghidra processors](https://github.com/NationalSecurityAgency/ghidra/tree/master/Ghidra/Processors); [Unicorn headers](https://github.com/unicorn-engine/unicorn/tree/master/include/unicorn); [capstone.h](https://github.com/capstone-engine/capstone/blob/next/include/capstone/capstone.h)). | Family | Main platforms | Ghidra built in | Ghidra extension | Unicorn 2 | Capstone (`next`) | Small cores for ground truth | |---|---|---|---|---|---|---| | 6502, 65C02 | Atari 2600, Atari 8-bit, 7800, C64, VIC-20, BBC, Apple II, NES, Lynx | `6502`, `65C02` ([6502.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/6502/data/languages/6502.ldefs)) | | None | `MOS65XX_6502`, `_65C02`, `_W65C02` | [py65](https://github.com/mnaberez/py65) (BSD-3), [mre/mos6502](https://github.com/mre/mos6502) (BSD-3), [floooh/chips](https://github.com/floooh/chips) (zlib) | | 65C816 | SNES, Apple IIgs | None | [achan1989/ghidra-65816](https://github.com/achan1989/ghidra-65816) (archived), [kaayzouee/ghidra-65816](https://github.com/kaayzouee/ghidra-65816) | None | `MOS65XX_65816` with LONG_M and LONG_X widths | MAME g65816 | | HuC6280 | PC Engine, PC Engine CD | None | [TiCoKH/Ghidra_HuC6280](https://github.com/TiCoKH/Ghidra_HuC6280) | None | 6502 modes only | MAME h6280 | | Z80, 8080 | Spectrum, CPC, MSX, PC-8801, ColecoVision, Master System, Game Gear | `z80`, `z180` and others ([z80.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/Z80/data/languages/z80.ldefs)); 8080 through `8085` ([8085.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/8085/data/languages/8085.ldefs)) | | None | None | [iz80](https://github.com/ivanizag/iz80) (BSD-3, passes ZEXALL), [redcode/Z80](https://github.com/redcode/Z80) (LGPL-3.0), [superzazu/z80](https://github.com/superzazu/z80) (MIT) | | SM83 | Game Boy, Game Boy Color | None | [Gekkio/GhidraBoy](https://github.com/Gekkio/GhidraBoy) (archived), fork [minorum/ghidra-sm83](https://github.com/minorum/ghidra-sm83) | None | None | MAME lr35902, [mooneye-gb](https://github.com/Gekkio/mooneye-gb) (GPL-3.0) | | 6809, 6309 | Dragon, CoCo, Vectrex | Under `MC6800` ([6800.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/MC6800/data/languages/6800.ldefs)); H6309 is "missing many instructions"; the folder also holds the 6805 ([6805.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/MC6800/data/languages/6805.ldefs)) | | None | `M680X_6809`, `_6309` and others | MAME m6809 | | 68000 family | Amiga, ST, X68000, 68k Mac, Mega Drive, Sega CD, Jaguar, CD-i | Default language is a 68040 superset, plus 68020, 68030, ColdFire, CPU32 ([68000.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/68000/data/languages/68000.ldefs)) | | M68000 to M68060 and ColdFire ([m68k.h](https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/m68k.h)) | `M68K` | [Musashi](https://github.com/kstenerud/Musashi) (MIT-style), [Moira](https://github.com/dirkwhoffmann/Moira) (MIT) | | TMS9900 | TI-99/4A | None | [gnulnulf/Ghidra-TMS9900](https://github.com/gnulnulf/Ghidra-TMS9900) | None | None | MAME tms9900 | | CP1600 | Intellivision | Yes | | None | None | MAME cp1610 | | x86 | PC, PC-98, FM Towns, WonderSwan, Xbox, PS4 onward | Real mode, 16-bit protected, 32, 64, SMM ([x86.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/x86/data/languages/x86.ldefs)) | | x86 16, 32 and 64 | `X86` | GameFrame's `tools/emu` | | ARM | Archimedes, 3DO, GBA, DS, 3DS, Vita, Switch | ARM v4 to v8 with Thumb, little- and big-endian ([ARM.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/ARM/data/languages/ARM.ldefs)); AARCH64 v8.5-A, "missing some 8.5 vector" ([AARCH64.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/AARCH64/data/languages/AARCH64.ldefs)) | | ARM926, 946, 1176, 11MPCore, Cortex; no ARM7TDMI by name ([arm.h](https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/arm.h)) | `ARM`, `AARCH64` | Full emulators | | MIPS | PlayStation, N64, PS2, PSP | MIPS32/64, big- and little-endian; no R3000, R5900 or Allegrex language ([mips.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/MIPS/data/languages/mips.ldefs)) | [ghidra-emotionengine-reloaded](https://github.com/chaoticgd/ghidra-emotionengine-reloaded) (R5900), [ghidra-allegrex](https://github.com/kotcrab/ghidra-allegrex) (PSP) | MIPS32 and MIPS64 (R4000, VR5432); no R5900 ([mips.h](https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/mips.h)) | `MIPS`, `MIPS3` | [PCSX2](https://github.com/PCSX2/pcsx2), [PPSSPP](https://github.com/hrydgard/ppsspp) | | SuperH | Saturn, 32X, Dreamcast | SH-1, SH-2, SH-2A big-endian; SH-4 big- and little-endian ([superh.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/SuperH/data/languages/superh.ldefs); [SuperH4.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/SuperH4/data/languages/SuperH4.ldefs)) | [Ghidra-SegaSaturn-Processor](https://github.com/VGKintsugi/Ghidra-SegaSaturn-Processor) (archived) | None | `SH2`, `SH2A`, `SH3`, `SH4` | MAME sh, [Flycast](https://github.com/flyinghead/flycast) (GPL-2.0) | | PowerPC | PowerPC Mac, GameCube, Wii, Wii U, Xbox 360, PS3 PPE | 32/64 with AltiVec; no paired singles or VMX128 ([ppc.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/PowerPC/data/languages/ppc.ldefs); [ppc_instructions.sinc](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/PowerPC/data/languages/ppc_instructions.sinc)) | [ghidra-gekko-broadway-lang](https://github.com/aldelaro5/ghidra-gekko-broadway-lang) (paired singles) | 601 to 7457, 750CL, 970 ([ppc.h](https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/ppc.h)) | `PPC`, with a paired-singles mode `CS_MODE_PS` | [Dolphin](https://github.com/dolphin-emu/dolphin), [Xenia](https://github.com/xenia-project/xenia), [RPCS3](https://github.com/RPCS3/rpcs3) | | Cell SPU | PS3 | None | [aerosoul94/GhidraSPU](https://github.com/aerosoul94/GhidraSPU) | None | None | RPCS3 | | Jaguar RISC | Jaguar | None | None known ([GitHub search](https://github.com/search?q=ghidra+jaguar&type=repositories)) | None | None | MAME jaguar | ### Ghidra Ghidra's processor list holds 39 folders ([Ghidra processors](https://github.com/NationalSecurityAgency/ghidra/tree/master/Ghidra/Processors)). - Its ARM languages start at v4. The ARMv2 and ARMv3 cores of the Archimedes and the 3DO's ARM60 have no exact built-in match. - Its MIPS spec models branch delay slots with `delayslot` (47 uses). The spec has no load-delay modelling ([mips32Instructions.sinc](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/MIPS/data/languages/mips32Instructions.sinc)). - The default 68000 language accepts 68040 instructions that a real 68000 would fault on. - The original [beardypig/ghidra-emotionengine](https://github.com/beardypig/ghidra-emotionengine) is archived. [astrelsky/ghidra-emotionengine](https://github.com/astrelsky/ghidra-emotionengine/blob/master/README.md) shows how to build on the MIPS module. - An archived module such as GhidraBoy needs a pinned version that matches the Ghidra release. Or use the active fork. Loaders for platforms and console loaders are on [tools for console work](https://tanepiper.github.io/game-frame/consoles/tools.md). The commands for a headless export are in the [re-ghidra](https://tanepiper.github.io/game-frame/skills/re-ghidra.md) skill. ### Unicorn Unicorn 2 offers ten architectures, all based on QEMU targets ([unicorn.h](https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/unicorn.h); [qemu/target](https://github.com/unicorn-engine/unicorn/tree/master/qemu/target); [Unicorn README](https://raw.githubusercontent.com/unicorn-engine/unicorn/master/README.md)). It covers only the 32-bit and 64-bit families (68000, ARM, MIPS, PowerPC, x86). It has no 8-bit CPU at all. Unicorn is GPL-2.0. - QEMU's MIPS headers define `CPU_R5900`. Unicorn's CPU table has no R5900 entry ([mips-defs.h](https://github.com/unicorn-engine/unicorn/blob/master/qemu/target/mips/mips-defs.h); [translate_init.inc.c](https://github.com/unicorn-engine/unicorn/blob/master/qemu/target/mips/translate_init.inc.c)). - Code search finds no `ps_madd` and no `VMX128` in the repository ([Unicorn repository](https://github.com/unicorn-engine/unicorn)). - QEMU executes MIPS32 semantics. PS1 code that reads a stale register in a load delay slot would likely give a different result. This is untested. - For the GBA, the ARM946 or TI925T models are the nearest to the ARM7TDMI. - Unicorn emulates CPUs only. Any function that touches custom chips needs stubs. GameFrame's Unicorn tools and how to use them are in the [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md) skill. ### Capstone and other disassemblers Capstone's `next` branch lists ARM, AARCH64, MIPS, x86, PPC, M68K, M680X, MOS65XX, SH, TRICORE and others. It has no Z80, R5900 or Allegrex mode ([capstone.h](https://github.com/capstone-engine/capstone/blob/next/include/capstone/capstone.h)). For 65816 code, set the Capstone width mode from the M and X flag state of each function. The 8-bit families also have these tools: - [cc65](https://github.com/cc65/cc65) (zlib), with the da65 disassembler. - [SkoolKit](https://github.com/skoolkid/skoolkit) (GPL-3.0) for the Spectrum. - [radare2](https://github.com/radareorg/radare2) and [rizin](https://github.com/rizinorg/rizin) (LGPL-3.0). ### Small cores and licences For CPUs that Unicorn lacks, MAME's m6809, g65816, tms9900, cp1610, jaguar and m68000 core files carry `license:BSD-3-Clause` headers. MAME as a whole is GPL-2.0 ([MAME COPYING](https://github.com/mamedev/mame/blob/master/COPYING); [mame/src/devices/cpu](https://github.com/mamedev/mame/tree/master/src/devices/cpu)). Those BSD and MIT cores can be combined with GameFrame's Apache-2.0 code. GPL emulators can still be used as separate external tools. [TomHarte/CLK](https://github.com/TomHarte/CLK) (MIT) covers many systems. [ares](https://github.com/ares-emulator/ares) covers many consoles. ## Arithmetic traps ### Ghidra's 6502 ADC is not exact Ghidra's 6502 spec computes `ADC` as `C = carry(A, op1); A = A + op1 + tmpC; resultFlags(A); V = C;` ([6502.slaspec lines 127–138](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/6502/data/languages/6502.slaspec#L127-L138)). That p-code has three faults. - It has no decimal mode path. - It leaves the incoming carry out of the carry-out test. - It copies V from C. A real 6502 computes V as signed overflow. The missing decimal mode changes results. `$99 + $01` gives `$00` with carry set in BCD, and `$9A` in binary ([Wikipedia: MOS Technology 6502](https://en.wikipedia.org/wiki/MOS_Technology_6502)). The NES 2A03 has no decimal mode, so the same code gives different results on a stock 6502 ([NESdev Wiki: 2A03](https://www.nesdev.org/wiki/2A03)). Do not use Ghidra's p-code emulation as ground truth for 6502 games. Take ground truth from a tested core such as py65, mos6502, MAME or floooh/chips. The NMOS flag behaviour in decimal mode (N, V and Z) still needs a primary source. ### Other families - Ghidra's Z80 spec implements DAA in full. It uses N to tell addition from subtraction ([z80.slaspec line 1234](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/Z80/data/languages/z80.slaspec#L1234)). - The 8080's DAA is defined only after an addition ([Wikipedia: Zilog Z80](https://en.wikipedia.org/wiki/Zilog_Z80)). - The SM83 sets carry on a subtraction borrow, as the Z80 and x86 do. The 6502 and ARM do the reverse ([Pan Docs](https://gbdev.io/pandocs/CPU_Registers_and_Flags.html)). - The 68000's ABCD, SBCD and NBCD use the separate X flag ([Wikipedia: Motorola 68000](https://en.wikipedia.org/wiki/Motorola_68000)). - The ARM7TDMI has no hardware divide. GBA games call the BIOS for division, square root and arctangent ([Copetti: Game Boy Advance](https://www.copetti.org/writings/consoles/game-boy-advance/)). - Games on the 6502, Z80, SM83 and ARM7 call software multiply and divide routines. Emulate those routines to get exact results. - The SH-1 traps on the SH-2's 64-bit multiplies ([Wikipedia: SuperH](https://en.wikipedia.org/wiki/SuperH)). - The PS1 has only fixed point ([Copetti: PlayStation](https://www.copetti.org/writings/consoles/playstation/)). The N64 FPU follows IEEE 754 ([Copetti: Nintendo 64](https://www.copetti.org/writings/consoles/nintendo-64/)). - The PS2's EE FPU "isn't IEEE 754 compliant" ([Wikipedia: Emotion Engine](https://en.wikipedia.org/wiki/Emotion_Engine)). PCSX2's developers report results that differ from x86 by a few bits. Full accuracy would need a slow software FPU ([PCSX2: Nightmare on Floating-Point Street](https://pcsx2.net/blog/2006/nightmare-on-floating-point-street); [PCSX2: What's clamping](https://pcsx2.net/blog/2009/whats-clamping-why-do-we-need-it)). Their documents liken PS2 add and subtract to SSE round-toward-zero without INF or NaN ([PCSX2 wiki VU documentation](https://wiki.pcsx2.net/Special:MobileDiff/38317)). - Cell SPU single precision rounds toward zero, treats denormals as zero and has no INF or NaN (not confirmed) ([IBM Cell SDK 3.0](https://arcb.csc.ncsu.edu/~mueller/cluster/ps3/SDK3.0/docs/accessibility/sdkpt/cbet_3codemapeqv.html)). Each original SPE gives about 1.8 GFLOPS in double precision ([Wikipedia: Cell](https://en.wikipedia.org/wiki/Cell_(processor))). - A port of PS2 or SPU float code that uses host IEEE floats will give slightly different results. It needs a software float model. - Gekko paired-single results depend on the quantised load and store (GQR) setup. Check the details in Dolphin's code. - On x86, a PC port must copy Turbo Pascal's 6-byte Real arithmetic and each compiler's calling convention ([Turbo Pascal](https://en.wikipedia.org/wiki/Turbo_Pascal); [CPUs and floating point](https://tanepiper.github.io/game-frame/hardware/cpus.md); [Calling conventions](https://tanepiper.github.io/game-frame/abi.md)). --- # Sinclair ZX81 and ZX Spectrum The Sinclair machines use the Z80 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-8080-z80-and-sm83)). On the ZX81 the CPU also generates the display. On the Spectrum, the ULA delays the CPU when both use contended memory. An instruction's timing therefore depends on where the ULA is in the frame. The contention tables differ by model. Spectrum games came mostly on tape, often with turbo or protected loaders. ## Sinclair ZX81 ### Hardware | Fact | Value | |---|---| | Years | 1981–1984; launched in the UK on 5 March 1981 at £69.95 assembled | | Sales | Over 1.5 million | | CPU | Z80A, 3.25 MHz (the source also says 3.5 MHz) | | RAM | 1 KB, with a 16 KB pack | | Video | Ferranti ULA; the CPU generates the display | The CPU generates the display, so FAST mode blanks the screen to run faster ([Wikipedia: ZX81](https://en.wikipedia.org/wiki/ZX81)). ### How games were made and loaded Games loaded from tape at roughly 250–300 bit/s ([Wikipedia: ZX81](https://en.wikipedia.org/wiki/ZX81)). ### Media and image formats The ZX81's tape encoding differs from the Spectrum's. TZX does not describe it natively ([TZX format](https://worldofspectrum.net/TZXformat.html)). ### Tools EightyOne covers the ZX81 (debugger features not confirmed). ### Projects to learn from None listed yet. ### For an exact reimplementation - Record whether the game runs in FAST mode or with the display on. The CPU time left for the game depends on it. ## ZX Spectrum 48K, 128K, +2, +2A and +3 ### Hardware | Model | Launch | CPU | RAM | |---|---|---|---| | 48K | 23 April 1982 | Z80A, 3.5 MHz | 16 or 48 KB | | 128K | Spain September 1985, UK January 1986 | 3.5469 MHz (not confirmed) | 128 KB as eight 16 KB pages | | +2 | 1986 (Amstrad) | As the 128K | 128 KB | | +3 | 1987 | As the 128K | 128 KB | | +2A | Late 1988 | As the 128K | 128 KB | Sources: [Wikipedia: ZX Spectrum](https://en.wikipedia.org/wiki/ZX_Spectrum), [Sinclair Wiki: ZX Spectrum 128](https://sinclair.wiki.zxnet.co.uk/wiki/ZX_Spectrum_128). The ULA gives 256x192 pixels with 8x8 attribute cells. The 128K models add an AY-3-8912 sound chip. **Memory map and paging.** The 48K map puts ROM at `0x0000`–`0x3FFF`, the screen bitmap at `0x4000`–`0x57FF` and the attributes at `0x5800`–`0x5AFF` (not confirmed) ([Sinclair Wiki](https://sinclair.wiki.zxnet.co.uk/wiki/ZX_Spectrum_48K)). On 128K models, port `0x7FFD` selects the RAM page at `0xC000`, the shadow screen, the ROM and a lock bit. The +2A and +3 add port `0x1FFD` for all-RAM modes (not confirmed) ([Sinclair Wiki: ZX Spectrum 128](https://sinclair.wiki.zxnet.co.uk/wiki/ZX_Spectrum_128)). **Contended memory.** The ULA delays CPU accesses to contended memory while it reads the screen. The delays differ by model ([Sinclair Wiki: Contended memory](https://sinclair.wiki.zxnet.co.uk/wiki/Contended_memory)). | Model | Contended area | First contended T-state | Delay pattern per 8 T-states | T-states per line | |---|---|---|---|---| | 48K | `0x4000`–`0x7FFF` | 14335 after the interrupt | 6,5,4,3,2,1,0,0 | 224 | | 128K, +2 | Pages 1, 3, 5 and 7 | 14361 | As the 48K | 228 | | +2A, +3 | Pages 4 to 7, only when MREQ is active | 14363 | 7,6,5,4,3,2,1,0,0 | | "Late timing" ULAs run one T-state later than the 48K figure. **Frames.** A frame lasts 69888 T-states on the 48K and 70908 on the 128K, with one maskable interrupt per frame. Most games use IM 2 (not confirmed) ([World of Spectrum FAQ](https://worldofspectrum.org/faq/reference/48kreference.htm)). ### How games were made and loaded Spectrum tapes carried turbo and protected loaders such as Speedlock and Alkatraz. A loader was often a BASIC stub followed by machine code that decrypts the next stage (not confirmed) ([Sinclair Wiki: Tape protection schemes](https://sinclair.wiki.zxnet.co.uk/wiki/Tape_protection_schemes)). The +3 used 3-inch disks protected with non-standard sectors (not confirmed) ([CPCWiki: Copy protection](https://www.cpcwiki.eu/index.php/Copy_protection)). ### Media and image formats | Format | What it holds | |---|---| | TZX | Tape with exact timing; keeps turbo and protected loaders | | TAP | Only standard ROM-loader blocks | | PZX | Tape (read by SkoolKit) | | SNA, Z80 | Snapshots | | SZX | Snapshot with fuller machine state | | RZX | A recording of input for replay (not confirmed) | TZX was designed to keep turbo and protected loaders working. Revision 1.20 stores pilot, sync and bit timings in T-states at 3.5 MHz. It also stores pulse sequences, direct recordings and generalised symbol encodings. Block `0x10` is standard timing and `0x11` turbo. Block `0x13` is a pulse sequence used by some protections. Block `0x15` is a direct recording and `0x19` generalised data ([TZX format](https://worldofspectrum.net/TZXformat.html)). The snapshot formats are described on the [Sinclair Wiki: file formats](https://sinclair.wiki.zxnet.co.uk/wiki/Category:File_formats) page (not confirmed). ### Tools - **SkoolKit** reads TAP, TZX and PZX tapes and Z80 and SZX snapshots. It also plays RZX recordings. Its `tap2sna.py` simulates tape loading. Its `trace.py` traces execution and can write AY output as WAV ([SkoolKit](https://skoolkit.ca/)). SkoolKit is GPL-3.0. - **Fuse** ([fuse-emulator.sourceforge.net](https://fuse-emulator.sourceforge.net/)), **ZEsarUX** ([github.com/chernandezba/zesarux](https://github.com/chernandezba/zesarux)) and **SpecEmu** ([specemu.zxe.io](http://www.specemu.zxe.io/)) have debuggers. - ZEsarUX has a remote protocol that a script can drive. - Ghidra has the Z80 built in. Unicorn has no Z80. For ground truth use a small core such as iz80 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#tools-per-family)). The community references are Spectrum Computing ([spectrumcomputing.co.uk](https://spectrumcomputing.co.uk/)), World of Spectrum ([worldofspectrum.org](https://worldofspectrum.org/)) and the Sinclair Wiki ([sinclair.wiki.zxnet.co.uk](https://sinclair.wiki.zxnet.co.uk/)). ### Projects to learn from SkoolKit's published disassemblies include Skool Daze, Back to Skool, Contact Sam Cruise, Manic Miner, Jet Set Willy, Hungry Horace and the 48K ROM ([SkoolKit disassemblies](https://skoolkit.ca/disassemblies/)). Each one reassembles byte for byte to the original. A port can check its results against them. Before you copy anything from a disassembly, check whether it includes game bytes and what its licence is. ### For an exact reimplementation - Record the model. The contention tables and the frame length differ between the 48K, the 128K and the +2A/+3. - Record whether the ULA has early or late timing. - Get past the loader in an emulator, then snapshot the game in memory ([snapshot after the loader](https://tanepiper.github.io/game-frame/computers/index.md#snapshot-after-the-loader)). - Keep the TZX image as the record of what shipped. - If the game must match frame timing, model the contention table for the model you chose. --- # BBC Micro and Acorn Electron Acorn's 8-bit machines use the 6502 family ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-6502-line)). The BBC Micro pages sideways ROMs into its memory map. Its disk controller changed between models. The Elite on the 6502 project documents the BBC version of Elite in detail. ## BBC Micro A, B, B+ and Master ### Hardware | Model | Launch | CPU | |---|---|---| | Model A, Model B | 1 December 1981 | 6502A at 2 MHz | | B+ | Mid-1985 | Rockwell 6512A (not confirmed) | | Master | 1986 | 65SC12 (not confirmed) | RAM across the range runs from 16 KB to 128 KB. Sources: [Wikipedia: BBC Micro](https://en.wikipedia.org/wiki/BBC_Micro), [Wikipedia: BBC Master](https://en.wikipedia.org/wiki/BBC_Master). - **Video.** A 6845 CRTC drives modes 0–6. The SAA5050 drives Teletext mode 7. A Video ULA sits beside them (not confirmed) ([BeebWiki: Video ULA](https://beebwiki.mdfs.net/Video_ULA)). - **Sound.** An SN76489. - **ROM.** The MOS sits in a 16 KB ROM. The last official version is OS 1.2. Four 16 KB sideways ROM sockets can be expanded to sixteen ([Wikipedia: BBC Micro](https://en.wikipedia.org/wiki/BBC_Micro)). - **Memory map.** ROMSEL at `&FE30` pages a ROM into `&8000`–`&BFFF`. The MOS sits at `&C000`–`&FFFF` and SHEILA I/O at `&FE00` (not confirmed) ([BeebWiki: Paged ROM](https://beebwiki.mdfs.net/Paged_ROM)). - **Disk controller.** The B+ moved from the Intel 8271 to the WD1770. That change broke some software ([Wikipedia: BBC Micro](https://en.wikipedia.org/wiki/BBC_Micro)). Many protected disks work on one model and fail on another because of it. ### How games were made and loaded Games came on tape and on disk. The change from the 8271 to the 1770 disk controller broke some software, including disk protection. ### Media and image formats | Format | What it holds | |---|---| | UEF | Chunked tape format (not confirmed) | | SSD | Single-sided DFS sector image (not confirmed) | | DSD | Double-sided DFS sector image (not confirmed) | Source: [BeebWiki: UEF](https://beebwiki.mdfs.net/Uniform_Emulator_Format). ### Tools - **b-em** ([github.com/stardot/b-em](https://github.com/stardot/b-em)), **BeebJit** ([github.com/scarybeasts/beebjit](https://github.com/scarybeasts/beebjit)) and **B2** ([github.com/tom-seddon/b2](https://github.com/tom-seddon/b2)) have debuggers. - Ghidra has the 6502 built in. Its `ADC` p-code is not exact ([arithmetic traps](https://tanepiper.github.io/game-frame/computers/cpu-families.md#ghidra-s-6502-adc-is-not-exact)). Unicorn has no 6502. Use py65, mos6502 or floooh/chips for ground truth. Stardot ([stardot.org.uk](https://stardot.org.uk/forums/)) and BeebWiki ([beebwiki.mdfs.net](https://beebwiki.mdfs.net/)) are the community references. ### Projects to learn from **Mark Moxon's "Elite on the 6502"** documents the BBC Micro, Electron, Master, C64, Apple II and NES versions of Elite, with about 130 deep dives ([bbcelite.com](https://www.bbcelite.com/)). Its source reassembles byte for byte to the original. A port can check its results against that source. Ian Bell's site hosts the original BBC source discs (not confirmed) ([iancgbell.clara.net/elite](http://www.iancgbell.clara.net/elite/)). Before you copy anything from such a project, check whether it includes game bytes and what its licence is. ### For an exact reimplementation - Record the model, the MOS version and which sideways ROMs are fitted. - Record the disk controller (8271 or 1770) of the capture machine. - Record the ROMSEL state for each code region that runs from paged ROM. ## Acorn Electron ### Hardware | Fact | Value | |---|---| | Launch | 1983 | | CPU | 6502 | | RAM | 32 KB, 4 bits wide | | Video and sound | One ULA for both (not confirmed) | The Electron's RAM is 4 bits wide. The CPU drops to an effective 1 MHz or less when it touches RAM. It has no mode 7 and one sound channel (not confirmed) ([Wikipedia: Acorn Electron](https://en.wikipedia.org/wiki/Acorn_Electron)). ### How games were made and loaded Elite on the 6502 covers the Electron version of Elite. No other sources are listed yet. ### Media and image formats UEF for tape, as on the BBC Micro (not confirmed). ### Tools Elkulator covers the Electron. Ghidra and Unicorn support is as for the BBC Micro. ### Projects to learn from [Elite on the 6502](https://www.bbcelite.com/) includes the Electron version. ### For an exact reimplementation - Model the slower RAM access. The BBC Micro's timing does not fit the Electron. --- # Commodore VIC-20 and C64 Commodore's 8-bit machines use the 6502 family ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-6502-line)). On the C64, the visible ROMs and I/O depend on the 6510's port at `$01` and on the cartridge lines. The VIC-II steals cycles on badlines. PAL and NTSC versions of a game are often separate builds. The 1541 disk drive has its own 6502. Fast loaders run code on it. ## Commodore VIC-20 ### Hardware | Fact | Value | |---|---| | Years | 1980 in Japan, 1981 worldwide, to 1985; US$299.95 | | CPU | 6502 | | RAM | 5 KB | | Video | VIC chip, 22x23 text; 6560 (NTSC) at 1.0227 MHz and 6561 (PAL) at 1.1087 MHz (not confirmed) | | Sound | Three voices plus noise, in the VIC chip | Sources: [Wikipedia: VIC-20](https://en.wikipedia.org/wiki/Commodore_VIC-20), [Wikipedia: MOS VIC](https://en.wikipedia.org/wiki/MOS_Technology_VIC). ### How games were made and loaded The VIC-20 was the first computer to sell a million. Many of its titles were ROM cartridges ([Wikipedia: VIC-20](https://en.wikipedia.org/wiki/Commodore_VIC-20)). ### Media and image formats Many titles were ROM cartridges. No image format details are listed yet. ### Tools Ghidra has the 6502 built in. Unicorn has no 6502. No emulator is listed yet. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record whether the dump is a PAL or an NTSC build. The VIC chip's clock differs between them. ## Commodore 64 ### Hardware | Fact | Value | |---|---| | Years | Shown at CES in January 1982; sold from August 1982 | | CPU | 6510 at 1.023 MHz (NTSC) or 0.985 MHz (PAL) | | RAM | 64 KB, plus 1 KB of colour RAM | | Video | VIC-II with eight sprites | | Sound | SID 6581 or 8580 | Source: [Wikipedia: Commodore 64](https://en.wikipedia.org/wiki/Commodore_64). **Banking.** The 6510's on-chip port at `$01` switches the BASIC, KERNAL and character ROMs and the `$D000` I/O area in and out ([Wikipedia: Commodore 64](https://en.wikipedia.org/wiki/Commodore_64)). The map below is (not confirmed) ([C64 Wiki: Memory Map](https://www.c64-wiki.com/wiki/Memory_Map)). | Address | Contents | |---|---| | `$A000`–`$BFFF` | BASIC ROM | | `$D000`–`$DFFF` | I/O or character ROM | | `$E000`–`$FFFF` | KERNAL ROM | | `$D000` | VIC-II | | `$D400` | SID | | `$DC00`, `$DD00` | CIA 1, CIA 2; CIA 2 selects the VIC-II's 16 KB bank | Which ROMs and I/O are visible depends on `$01` and on the cartridge lines. A disassembly must record the bank state for each code region. **Timing.** The figures below are (not confirmed) ([Christian Bauer, The MOS 6567/6569 VIC-II](https://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt)). | VIC-II | Lines | Cycles per line | Notes | |---|---|---|---| | 6569 (PAL) | 312 | 63 | 19656 cycles per frame at 50.12 Hz | | 6567 (NTSC) | 263 | 65 | | | 6567 R56A (early NTSC) | 262 | 64 | | Badlines steal 40 cycles every eighth line (not confirmed). Games also use raster interrupts. **Sound.** The two SID revisions differ audibly, mainly in the filter (not confirmed) ([Wikipedia: SID](https://en.wikipedia.org/wiki/MOS_Technology_6581)). ### How games were made and loaded Cartridges auto-start on the "CBM80" signature at `$8000` ([Wikipedia: Commodore 64](https://en.wikipedia.org/wiki/Commodore_64)). The 1541 drive has its own 6502 and ROM. Fast loaders and disk protections run custom code on the drive. Novaload, Freeload, Cyberload and Vorpal are fast loaders. Protections use bad sectors, half tracks and density changes (not confirmed) ([Codebase64](https://codebase64.org/)). ### Media and image formats The formats below are (not confirmed) ([VICE manual: file formats](https://vice-emu.sourceforge.io/vice_17.html)). | Format | What it holds | |---|---| | PRG | A program file with a 2-byte load address | | T64 | A tape container | | TAP | Raw tape pulse lengths | | D64 | A 683-sector disk image of 174,848 bytes | | G64 | A GCR track image that keeps protection | | CRT | A cartridge, with its hardware type | ### Tools - **VICE** has a monitor with breakpoints and a remote monitor protocol ([vice-emu.sourceforge.io](https://vice-emu.sourceforge.io/)). An agent can drive captures through the remote monitor with a script ([re-observe](https://tanepiper.github.io/game-frame/skills/re-observe.md)). - **CCS64** runs on Windows. - Ghidra has the 6502 built in. Its `ADC` p-code is not exact ([arithmetic traps](https://tanepiper.github.io/game-frame/computers/cpu-families.md#ghidra-s-6502-adc-is-not-exact)). Unicorn has no 6502. Use py65, mos6502 or floooh/chips for ground truth. - cc65's da65 disassembles 6502 code. Codebase64 and C64-Wiki ([c64-wiki.com](https://www.c64-wiki.com/)) are the community references. ### Projects to learn from [Elite on the 6502](https://www.bbcelite.com/) documents the C64 version of Elite. ### For an exact reimplementation - Record the bank state (`$01` and the cartridge lines) for every code region you load into Ghidra. - Record whether the build is PAL or NTSC, and which VIC-II revision it ran on. The cycles per frame differ. - Record the SID revision of the reference recording. - Code that runs on the 1541 is part of the loader. Get past it, then snapshot the game in memory ([snapshot after the loader](https://tanepiper.github.io/game-frame/computers/index.md#snapshot-after-the-loader)). - Keep the G64 image as the record of what shipped. G64 keeps the protection. --- # Amstrad CPC The Amstrad CPC uses the Z80 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-8080-z80-and-sm83)). Wait states stretch every instruction to a multiple of 4 T-states. The CRTC type changes the edges of the picture. Games with split screens or overscan depend on those edges. CPC tapes use the TZX structure. ## CPC 464, 664, 6128 and the Plus range ### Hardware | Model | Launch | |---|---| | CPC 464 | June 1984 | | CPC 664 | April 1985 | | CPC 6128 | Mid-1985 | | Plus range and GX4000 | September 1990 | | Fact | Value | |---|---| | CPU | Z80A at 4 MHz; wait states give about 3.3 MHz | | RAM | 64 or 128 KB | | Video | 6845 CRTC plus gate array; 27 colours (4096 on the Plus) | | Modes | 160x200 in 16 colours, 320x200 in 4, 640x200 in 2, at 50 Hz | | Sound | AY-3-8912 | | Disks | 3-inch, typically 180 KB per side | Source: [Wikipedia: Amstrad CPC](https://en.wikipedia.org/wiki/Amstrad_CPC). **Timing.** Every instruction is stretched to a multiple of 4 T-states. The unit is the "NOP time" of 1 microsecond (not confirmed) ([CPCWiki: CPU timing](https://www.cpcwiki.eu/index.php/Z80)). CRTC types 0 to 4 differ at the edges. Games with split screens and overscan depend on these differences (not confirmed) ([CPCWiki: CRTC](https://www.cpcwiki.eu/index.php/CRTC)). The gate array interrupts every 52 lines, six times a frame (not confirmed) ([CPCWiki: Gate Array](https://www.cpcwiki.eu/index.php/Gate_Array)). ### How games were made and loaded Games came on tape and on 3-inch disk. Disk protection is described on [CPCWiki: Copy protection](https://www.cpcwiki.eu/index.php/Copy_protection). ### Media and image formats | Format | What it holds | |---|---| | CDT | Tape in the TZX structure, with timings scaled by 4/3.5 ([TZX format](https://worldofspectrum.net/TZXformat.html)) | | Extended DSK | Disk with the size and status of each sector; keeps many protections (not confirmed) ([CPCWiki: DSK format](https://www.cpcwiki.eu/index.php/Format:DSK_disk_image_file_format)) | ### Tools - **WinAPE** ([winape.net](http://www.winape.net/)) and **Caprice32** ([github.com/ColinPitrat/caprice32](https://github.com/ColinPitrat/caprice32)) have debuggers. - Ghidra has the Z80 built in. Neither Unicorn nor Capstone supports the Z80. For ground truth use a small core such as iz80 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#tools-per-family)). CPCWiki ([cpcwiki.eu](https://www.cpcwiki.eu/)) is the community reference. ### Projects to learn from None listed yet. ### For an exact reimplementation - Count time in NOP units of 4 T-states. A Z80 cycle table from another machine gives the wrong times. - Record the CRTC type of the reference machine if the game uses split screens or overscan. - If the game times its frame from the gate array's six interrupts per frame, the port must do the same. - Keep the extended DSK or CDT image as the record of what shipped. --- # MSX, PC-8801 and Sharp X1 These machines use the Z80 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-8080-z80-and-sm83)). The MSX maps memory through slots and subslots. Its MegaROM cartridges switch banks with a mapper. The PC-8801 and the X1 were two of Japan's four major home computers, with the FM-7 and the MSX. ## MSX, MSX2, MSX2+ and turbo R ### Hardware | Fact | Value | |---|---| | Years | Announced by ASCII on 16 June 1983; first machine on sale 21 October 1983; MSX2 in 1985; MSX2+ in 1988 (Japan only); to 1990 | | CPU | Z80A at 3.58 MHz; R800 on the turbo R | | RAM | 8–64 KB; 64–512 KB on MSX2; up to 4 MB per slot through a memory mapper | | Video | TMS9918 family, V9938, V9958; European machines use the TMS9929A (not confirmed) | | Sound | AY-3-8910 or YM2149; Konami's SCC chip inside some cartridges adds five channels | Sources: [Wikipedia: MSX](https://en.wikipedia.org/wiki/MSX), [MSX Wiki](https://www.msx.org/wiki/). **Slots.** Slots and subslots are selected through the PPI at port `A8h` and the register at `FFFFh`. VRAM is reached only through VDP ports `98h` and `99h` (not confirmed) ([MSX Wiki: Slots](https://www.msx.org/wiki/Slots)). ### How games were made and loaded Cartridges were common on the MSX. MegaROM cartridges use bank-switching mappers: Konami, Konami SCC, ASCII 8K and ASCII 16K. An emulator needs the right mapper type to run a dump (not confirmed) ([MSX Wiki: MegaROM mappers](https://www.msx.org/wiki/MegaROM_Mappers)). ### Media and image formats Images are ROM dumps with a mapper type, DSK disks and CAS tapes (not confirmed) ([openMSX manual](https://openmsx.org/manual/)). ### Tools - **openMSX** has a Tcl console and a separate debugger ([openmsx.org](https://openmsx.org/)). An agent can drive captures through the Tcl console with a script ([re-observe](https://tanepiper.github.io/game-frame/skills/re-observe.md)). - Ghidra has the Z80 built in. Unicorn has no Z80. For ground truth use a small core such as iz80 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#tools-per-family)). The MSX Resource Center is at [msx.org](https://www.msx.org/). ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the mapper type of the cartridge. Name functions by bank and address. - Record the slot layout of the reference machine. - Record which VDP the reference machine has. European machines use the TMS9929A. ## NEC PC-8801 ### Hardware | Fact | Value | |---|---| | Launch | November 1981 | | CPU | Z80 | | Sound | YM2203 from the mkII SR, later YM2608 | Its disk unit has a second Z80 (not confirmed). Source: [Wikipedia: PC-8800 series](https://en.wikipedia.org/wiki/PC-8800_series). ### How games were made and loaded The PC-8801 ran N88-BASIC. Ys, Thexder, Snatcher and the Dragon Slayer series first appeared on it ([Wikipedia: PC-8800 series](https://en.wikipedia.org/wiki/PC-8800_series)). ### Media and image formats D88 is the usual disk image (not confirmed). ### Tools MAME covers the PC-8801, with a shared debugger and Lua scripting ([MAME debugger](https://docs.mamedev.org/debugger/index.html)). Ghidra has the Z80 built in. ### Projects to learn from None listed yet. ### For an exact reimplementation - The disk unit's second Z80 runs its own code (not confirmed). Treat it as a separate program. - Record the sound chip of the reference model. ## Sharp X1 ### Hardware | Fact | Value | |---|---| | Years | 1982; X1 turbo in 1984 (not confirmed) | | CPU | Z80A at 4 MHz (not confirmed) | | Video | HD46505 CRTC, PCG (not confirmed) | | Sound | AY-3-8910 (not confirmed) | Source: [Wikipedia: Sharp X1](https://en.wikipedia.org/wiki/Sharp_X1). ### How games were made and loaded None listed yet. ### Media and image formats None listed yet. ### Tools MAME covers the X1, with a shared debugger and Lua scripting ([MAME debugger](https://docs.mamedev.org/debugger/index.html)). Ghidra has the Z80 built in. ### Projects to learn from None listed yet. ### For an exact reimplementation - Confirm the CPU clock and the chips from a second source before you model timing. --- # Atari 400/800, XL and XE The Atari 8-bit computers use the 6502 family ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-6502-line)). ANTIC runs a display list and takes DMA cycles from the CPU on each line. Display list interrupts change the picture part way down the screen. Game logic on these machines often depends on exact cycle timing. ## Atari 400, 800, XL and XE ### Hardware | Model | Launch | |---|---| | 400 and 800 | November 1979 | | XL range | From 1983 | | XE range | From 1985, to 1987 | | Fact | Value | |---|---| | CPU | 6502B or SALLY 6502C, 1.79 MHz (NTSC) or 1.77 MHz (PAL) | | RAM | Up to 128 KB, banked on the 130XE | | Video | ANTIC runs a display list with one instruction per mode line; CTIA or GTIA does colour and player/missile graphics | | Sound and I/O | POKEY does sound, the keyboard and serial I/O | Source: [Wikipedia: Atari 8-bit](https://en.wikipedia.org/wiki/Atari_8-bit_computers). The details below are (not confirmed) ([Altirra Hardware Reference Manual](https://www.virtualdub.org/altirra.html)). - Banking on the 130XE uses PIA PORTB at `$D301`, with a 16 KB window at `$4000`. - Display list interrupts are enabled through NMIEN. - ANTIC DMA steals cycles on each line. ### How games were made and loaded Cartridges were common on the Atari. Games also came on tape and disk. ### Media and image formats The formats below are (not confirmed). | Format | What it holds | |---|---| | ATR | A sector image | | XEX | A DOS executable | | CAS | A tape | | ATX | A protected-disk image with timing and weak sectors | ### Tools - **Altirra** has a debugger and ships its own hardware reference ([virtualdub.org/altirra.html](https://www.virtualdub.org/altirra.html)). - Ghidra has the 6502 built in. Its `ADC` p-code is not exact ([arithmetic traps](https://tanepiper.github.io/game-frame/computers/cpu-families.md#ghidra-s-6502-adc-is-not-exact)). Unicorn has no 6502. Use py65, mos6502 or floooh/chips for ground truth. AtariAge is the main community forum ([atariage.com](https://atariage.com/)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the display list and every display list interrupt. Together they set the mode and colours of each screen line. - Model the cycles ANTIC DMA takes on each line if game logic depends on CPU time. - Record the PORTB bank state for each code region on a 130XE. - Record whether the build is PAL or NTSC. PAL and NTSC versions are often separate builds. - Keep the ATX image as the record of a protected disk. --- # Apple II The Apple II uses the 6502, later the 65C02, and the 65C816 in the IIgs ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-6502-line)). Soft switches select the video modes and the memory banks. The Disk II's encoding is controlled by software, so publishers wrote their own disk formats as copy protection. ## Apple II, IIe and IIgs ### Hardware | Model | Launch | CPU | |---|---|---| | Apple II | 10 June 1977 | 6502 at 1.023 MHz | | Later models, IIe | | 65C02 | | IIgs | 1986 | 65C816 at 2.8 MHz (not confirmed) | Sources: [Wikipedia: Apple II (original)](https://en.wikipedia.org/wiki/Apple_II_(original)), [Wikipedia: Apple II](https://en.wikipedia.org/wiki/Apple_II), [Wikipedia: Apple IIGS](https://en.wikipedia.org/wiki/Apple_IIGS). About six million Apple IIs were sold. - **RAM.** 4–48 KB, more on the IIe and IIgs. - **Video.** Lo-res and hi-res modes. Hi-res is 280x192 with six visible colours. - **Sound.** A 1-bit speaker, toggled at `$C030`. The IIgs has an Ensoniq 5503 DOC (not confirmed). - **Memory map.** Soft switches fill `$C000`–`$C0FF`. The language card banks RAM over `$D000`–`$FFFF` ([AppleWin](https://github.com/AppleWin/AppleWin)). ### How games were made and loaded Disk II and Apple DOS arrived in late 1978, with 140 KB per side ([Wikipedia: Apple II (original)](https://en.wikipedia.org/wiki/Apple_II_(original))). Disk II encoding is GCR controlled by software. Publishers wrote custom sector formats, nibble counts and spiral tracks as copy protection. ### Media and image formats | Format | What it holds | |---|---| | WOZ | Bit-level tracks and timing; protected disks run from it (not confirmed) | | DSK, DO, PO | Only the 140 KB of sectors (not confirmed) | Source: [WOZ reference](https://applesaucefdc.com/woz/reference2/). ### Tools - **AppleWin** ([github.com/AppleWin/AppleWin](https://github.com/AppleWin/AppleWin)) is an Apple II emulator. - Ghidra has the 6502 and 65C02 built in. Its `ADC` p-code is not exact ([arithmetic traps](https://tanepiper.github.io/game-frame/computers/cpu-families.md#ghidra-s-6502-adc-is-not-exact)). The IIgs's 65C816 needs a community extension. Unicorn has neither. Use py65, mos6502 or floooh/chips for 6502 ground truth. - For IIgs code, track the m and x flags. Capstone's `MOS65XX_65816` mode needs the width set for each function. ### Projects to learn from - Jordan Mechner released the Apple II Prince of Persia source in 2012 (not confirmed) ([github.com/jmechner/Prince-of-Persia-Apple-II](https://github.com/jmechner/Prince-of-Persia-Apple-II)). - [Elite on the 6502](https://www.bbcelite.com/) documents the Apple II version of Elite. ### For an exact reimplementation - Record the soft switch and language card state for each code region. - Keep the WOZ image as the record of a protected disk. A DSK image loses the protection. - On the IIgs, record the m and x flag state at the entry of each function. --- # Other 8-bit computers This page covers three smaller families. The Dragon and the CoCo use the 6809E, the Oric uses the 6502, and the TI-99/4A uses the 16-bit TMS9900 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md)). On the Oric some games depend on the BASIC ROM version. On the TI-99/4A the general registers are held in RAM. ## Dragon 32/64 and TRS-80 Color Computer ### Hardware | Machine | Years | CPU | Video | |---|---|---|---| | TRS-80 CoCo | Announced July 1980 at US$399 | 6809E | MC6847 VDG | | CoCo 3 | 1986–1991 | 6809E, double speed | GIME, 64-colour palette | | Dragon 32 | August 1982 | 6809E at 0.89 MHz | MC6847 VDG | RAM across the range runs from 4 KB to 512 KB. Dragon Data went out of business in 1984. Sources: [Wikipedia: TRS-80 Color Computer](https://en.wikipedia.org/wiki/TRS-80_Color_Computer), [Wikipedia: Dragon 32/64](https://en.wikipedia.org/wiki/Dragon_32/64). The details below are (not confirmed) ([Dragon Wiki](https://archive.worldofdragon.org/)). - Both machines use the MC6883 SAM for memory and timing. - Sound comes from a 6-bit DAC. - A "speed-up poke" doubles the clock on some machines. ### How games were made and loaded OS-9 ran on the CoCo ([Wikipedia: TRS-80 Color Computer](https://en.wikipedia.org/wiki/TRS-80_Color_Computer)). ### Media and image formats None listed yet. ### Tools - **XRoar** emulates both machines ([6809.org.uk/xroar](https://www.6809.org.uk/xroar/)). - Ghidra has the 6809 built in under `MC6800`. Its H6309 language is "missing many instructions". Unicorn has no 6809. MAME's m6809 core is BSD-3-Clause ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#small-cores-and-licences)). - The 6809 is big-endian. Decode data files in that byte order. World of Dragon is the community site ([worldofdragon.org](https://worldofdragon.org/)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Record whether the game sets the speed-up poke. The clock rate changes with it. - Record the model. The CoCo 3's GIME differs from the VDG of the earlier machines. ## Oric-1 and Atmos ### Hardware | Fact | Value | |---|---| | Years | Oric-1 on 1 September 1982; Atmos on 1 February 1984 | | CPU | 6502A at 1 MHz | | RAM | 16 or 48 KB | | Video | ULA HCS 10017; serial attributes take one character cell each | | Sound | AY-3-8912 | Source: [Wikipedia: Oric](https://en.wikipedia.org/wiki/Oric_(computer)). A 6522 VIA drives the AY, the keyboard, the printer and the tape (not confirmed) ([Defence Force Oric wiki](https://wiki.defence-force.org/)). ### How games were made and loaded Some games depend on BASIC ROM v1.0 ([Wikipedia: Oric](https://en.wikipedia.org/wiki/Oric_(computer))). ### Media and image formats None listed yet. ### Tools - **Oricutron** is the emulator ([github.com/pete-gordon/oricutron](https://github.com/pete-gordon/oricutron)). - Ghidra has the 6502 built in. Its `ADC` p-code is not exact ([arithmetic traps](https://tanepiper.github.io/game-frame/computers/cpu-families.md#ghidra-s-6502-adc-is-not-exact)). Defence Force is the community site ([defence-force.org](https://www.defence-force.org/)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the BASIC ROM version with the ground truth. Some games depend on v1.0. ## Texas Instruments TI-99/4A ### Hardware The TI-99/4A uses the TMS9900, a 16-bit big-endian CPU with only PC, WP and ST on the chip. Its sixteen general registers are held in RAM at the workspace pointer. BLWP saves WP, PC and ST into workspace registers 13 to 15 ([Wikipedia: TMS9900](https://en.wikipedia.org/wiki/Texas_Instruments_TMS9900)). ### How games were made and loaded None listed yet. ### Media and image formats None listed yet. ### Tools - Ghidra needs a community extension, [gnulnulf/Ghidra-TMS9900](https://github.com/gnulnulf/Ghidra-TMS9900). - Unicorn and Capstone have no TMS9900. MAME's tms9900 core is BSD-3-Clause ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#small-cores-and-licences)). ### Projects to learn from None listed yet. ### For an exact reimplementation - A register read in the disassembly is a memory read. Trace the workspace RAM along with the function. - Record the workspace pointer at the entry of each function you emulate. --- # Commodore Amiga The Amiga uses the big-endian 68000 line ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-68000)). Many games skip the OS and drive the custom chips directly. A game that uses the AmigaOS libraries can be traced at the OS call boundary. Trace a takeover game at its hardware register writes. Its port must reproduce the effect of each chip operation. ## The chip sets | Models | Years | CPU | Chip RAM | Chip set | Kickstart | |---|---|---|---|---|---| | A1000, A500, A2000 | 1985–1992 | 68000 | 256 KB to 1 MB | OCS | 1.0–1.3 | | A500+, A600, A3000 | 1990–1992 | 68000; 68030 in the A3000 | 1 MB, plus fast RAM on the A3000 | ECS | 2.04–2.05 | | A1200, A4000, CD32 | 1992–1996 | 68EC020; 68EC030 or 68040 in the A4000 | 2 MB | AGA | 3.0–3.1 | Floppies hold 880 KB. Sources: [Wikipedia: Amiga models and variants](https://en.wikipedia.org/wiki/Amiga_models_and_variants), [Wikipedia: Original Chip Set](https://en.wikipedia.org/wiki/Original_Chip_Set). ## Amiga with OCS ### Hardware The facts below come from [Wikipedia: Original Chip Set](https://en.wikipedia.org/wiki/Original_Chip_Set). That article asks for more citations. The Amiga Hardware Reference Manual should confirm the Copper, sprite and HAM details. The exact PAL and NTSC CPU clocks also need that manual. - **Agnus** is the chip RAM DMA controller. It arbitrates between the CPU and the custom chips. It holds the Blitter and the Copper. The original Agnus addresses 512 KB of chip RAM. Later "Fat Agnus" revisions raised this to 1 MB and then 2 MB. - **Timing.** The chips count time in colour clocks of 280 ns. - The **Blitter** combines up to three sources into a destination, over blocks up to 1,024x1,024 pixels. - The **Copper** runs a program of three instructions (MOVE, WAIT, SKIP) synchronised to the beam. It restarts every frame, so raster effects come from a Copper list for each frame. - **Denise** gives 1 to 5 bitplanes for 2 to 32 colours from 4,096. It adds Extra-Halfbrite and HAM with a sixth plane, and eight sprites 16 pixels wide. - **Paula** handles interrupts, four 8-bit DMA audio channels, the floppy and the serial port. Audio DMA peaks at about 28,867 samples/s per channel on NTSC and 28,837 on PAL. - Displays are 200 lines on NTSC and 256 on PAL. ### How games were made and loaded Many games take over the machine and drive the chips directly. A takeover game times itself from the vertical blank, 50 Hz on PAL and 60 Hz on NTSC. Rob Northen's **Copylock** protected disks on the Amiga, the ST and the PC. Its trace vector decoder decrypts each instruction, runs it, then encrypts it again ([Wikipedia: Rob Northen copylock](https://en.wikipedia.org/wiki/Rob_Northen_copylock); [Wikipedia: Trace vector decoder](https://en.wikipedia.org/wiki/Trace_vector_decoder)). CAPS reverse engineered the Amiga Copylock in June 2001 and counted over 500 titles using it ([SPS Copylock Unlocked](https://softpres.org/wip:2001-06-07); [Part 2](https://softpres.org/wip:2001-06-08)). Later keydisks carried a unique serial number in the disk format ([Codetapper interview with Rob Northen](https://codetapper.com/amiga/interviews/rob-northen)). Read a self-decrypting game from emulator memory after it has decrypted itself. ### Media and image formats | Format | What it holds | |---|---| | ADF | Plain sector images; cannot hold most copy protection | | IPF | Low-level track data; begins with "CAPS"; often made from KryoFlux captures; has an open-source library | Sources: [Wikipedia: Amiga Disk File](https://en.wikipedia.org/wiki/Amiga_Disk_File), [IANA: IPF](https://www.iana.org/assignments/media-types/application/vnd.softpres-ipf-disk-image). IPF comes from the Software Preservation Society. SPS notes that automatic format scripting cannot handle the Amiga's free-form track format ([SPS news 2005-08-05](https://softpres.org/news:2005-08-05)). ### Tools - **BartmanAbyss/ghidra-amiga** loads hunk executables, Kickstart ROMs and WinUAE state files, with NDK 3.9 symbols. It is forked from lab313ru's ghidra_amiga_ldr and builds on work by Vladimir Kononovich and Martin Atkins ([BartmanAbyss on GitHub](https://www.github.com/BartmanAbyss); [amiga-news 2022](https://amiga-news.de/en/news/AN-2022-09-00023-EN.html)). A 2023 update targeted Ghidra 10.3.1 ([amiga-news 2023](https://amiga-news.de/en/news/AN-2023-06-00122-EN.html)). One user reports that the Ghidra 12.0.1 build loaded in 12.1.3 only after editing its version line ([Sebastian Bergmann](https://amiga.sebastian-bergmann.de/2026/08/29/disassembling-my-1994-self/)). - amitools' Python hunk library also loads hunk binaries ([lab313ru/amitools_dbg](https://github.com/lab313ru/amitools_dbg)). - **WinUAE** has a debugger, opened with Shift+F12. `fp "name"` runs until a named process starts ([markwrobel.dk](https://markwrobel.dk/post/amiga-machine-code-part1-debugger)). It supports memory watchpoints. - The VS Code Amiga extension can step Copper lists through FS-UAE ([vscode-amiga-assembly wiki](https://github-wiki-see.page/m/prb28/vscode-amiga-assembly/wiki/Debugging-emulator)). - MAME's debugger also has watchpoints ([MAME docs](https://docs.mamedev.org/debugger/watchpoint.html)). - vAmiga's debugger is not documented here. - Unicorn runs the 68000 code but none of the custom chips. ### Projects to learn from - **rawgl** by cyxx reimplements the Another World engine. It reads Amiga, Atari, DOS, Windows 3.1, 3DO and anniversary data. It was archived on 28 June 2025 ([cyxx/rawgl](https://github.com/cyxx/rawgl)). - **OpenFodder** is a GPL-3.0 engine for Cannon Fodder 1 and 2 on Amiga, CD32 and DOS CD. Its README does not say how it was made ([OpenFodder](https://github.com/OpenFodder/openfodder)). - **pyDM** is a Python port of Amiga Dungeon Master, built from the ReDMCSB Amiga V3.x branch ([pyDM](https://codeberg.org/CyCl0ne/pyDM)). ReDMCSB is on the [Atari ST](https://tanepiper.github.io/game-frame/computers/atari-st.md) page. ### For an exact reimplementation - Record the model and the Kickstart version with every capture. An OCS game built for 512 KB of chip RAM behaves differently from an ECS or AGA build. - Decide whether the game uses the OS or takes over the machine. Trace an OS game at the library calls. Trace a takeover game at its chip register writes. - Pick one TV standard. A takeover game times itself from the vertical blank, at 50 Hz or 60 Hz. - Reproduce the Copper list, the Blitter operations and the audio DMA at that frame rate. - Take ground truth for a function that touches only RAM from Unicorn's 68000 ([re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). Anything that reads chip registers needs a full trace from WinUAE or MAME. - Keep the IPF image as the record of what shipped. Read a Copylock game from memory after it decrypts itself. ## Amiga with ECS ### Hardware The ECS models are the A500+, A600 and A3000 (1990–1992), with 1 MB of chip RAM and Kickstart 2.04–2.05. The A3000 has a 68030 and fast RAM. ### How games were made and loaded No differences from OCS are listed yet. ### Media and image formats No differences from OCS are listed yet. ### Tools No differences from OCS are listed yet. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the model and the Kickstart version. An ECS build can differ from the OCS build of the same game. ## Amiga with AGA ### Hardware The AGA models are the A1200, A4000 and CD32 (1992–1996), with 2 MB of chip RAM and Kickstart 3.0–3.1. The A1200 has a 68EC020. The A4000 has a 68EC030 or a 68040. ### How games were made and loaded No differences from OCS are listed yet. ### Media and image formats No differences from OCS are listed yet. ### Tools No differences from OCS are listed yet. Ghidra's default 68000 language is a 68040 superset. Ghidra also has 68020 and 68030 languages. ### Projects to learn from OpenFodder covers the CD32 version of Cannon Fodder. ### For an exact reimplementation - Record the model and the Kickstart version. An AGA build can differ from the OCS build of the same game. --- # Atari ST family The Atari ST uses the big-endian 68000 line ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-68000)). Like the Amiga, many ST games skip the OS and drive the hardware directly. A game that uses TOS and GEM can be traced at its GEMDOS, BIOS, XBIOS, AES and VDI calls. Hatari, an ST emulator, has the best documented debugger of these machines. ## Atari 520ST and 1040ST ### Hardware | Fact | Value | |---|---| | Years | 520ST shipped in June 1985; 1040ST from 1986 | | CPU | 68000 at 8 MHz | | RAM | 512 KB or 1 MB | | Video | Shifter: 320x200 in 16 colours and 640x200 in 4, from 512; 640x400 mono at 71.2 Hz | | Sound | YM2149 | | Other chips | MC68901 MFP, WD-1772 floppy controller | | OS and media | TOS with GEM; 360 or 720 KB floppies | Source: [Wikipedia: Atari ST](https://en.wikipedia.org/wiki/Atari_ST). ### How games were made and loaded Games either use TOS and GEM or take over the hardware. Rob Northen's Copylock protected disks on the ST as on the Amiga ([Wikipedia: Rob Northen copylock](https://en.wikipedia.org/wiki/Rob_Northen_copylock)). SPS supports Copylock ST and Speedlock timing tracks ([SPS 2002-10-28](https://softpres.org/wip:2002-10-28); [SPS Speedlock](https://softpres.org/wip:2003-06-01)). ### Media and image formats | Format | What it holds | |---|---| | ST | Raw data with no metadata and no protection | | MSA | Adds a geometry header and RLE compression | | STX (Pasti) | Non-standard sectors, fake track addresses and sector timing | Sources: [fplanque.com](https://www.fplanque.com/tech/retro/atari/atari-st-fd-image-file-formats/), [8bitchip forum](https://forum.8bitchip.info/atari-faq/disk-imaging-popular-formats/). Its author never documented STX. Hatari 1.8.0 (2014) added the first open-source decoder ([IANA: Pasti STX](https://www.iana.org/assignments/media-types/application/vnd.pasti-stx-disk-image); [st-computer.org](https://en.st-computer.org/news/2014/07/06/hatari-1-8-0-supports-pasti/)). ### Tools **Hatari** has these debugger features ([Hatari debugger manual](https://hatari.tuxfamily.org/doc/debugger.html)): - conditional breakpoints with up to four conditions; - CPU and DSP disassembly; - symbol loading; - profiling with Callgrind output; - tracing of GEMDOS, BIOS, XBIOS, AES and VDI calls; - command scripts that run at startup and on breakpoints. The scripts let an agent drive captures ([re-observe](https://tanepiper.github.io/game-frame/skills/re-observe.md)). No Ghidra loader for ST PRG files is known. Steem SSE's debugger is not documented here. Unicorn runs 68000 code but none of the ST's chips. ### Projects to learn from - **glFrontier.** Tom Morton started from the ST version of Frontier: Elite II. He stripped the hardware access from the assembly and changed a 68k assembler to emit C or i386 assembly ([virtuallyfun.com](https://virtuallyfun.com/?p=59); [Aminet readme](https://se.aminet.net/game/actio/frontier-elite2-gl.aros.readme)). - **fe2-intro** reproduces the Frontier intro ([watsonmw/fe2-intro](https://github.com/watsonmw/fe2-intro)). - **ReDMCSB** is reconstructed Dungeon Master and Chaos Strikes Back source by forum user ChristopheF. Its build scripts make ST and Amiga executables, with an option to drop copy protection ([dungeon-master.com forum](https://www.dungeon-master.com/forum/viewtopic.php?p=163896)). DM/CSB version 3 was built with Think C on Apple hardware ([dungeon-master.com forum](https://www.dungeon-master.com/forum/viewtopic.php?p=162234)). ReDMCSB says its builds match the original binaries. No second source confirms this. ### For an exact reimplementation - Decide whether the game uses TOS or takes over the machine. Trace a TOS game at its system calls with Hatari's call tracing. - Keep the STX image as the record of a protected disk. A plain ST image loses the protection. - Read a self-decrypting game from emulator memory after it decrypts itself. - Take ground truth for a function that touches only RAM from Unicorn's 68000 ([re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). Anything that reads chip registers needs a full trace from Hatari. ## Atari STE, Mega STE, TT030 and Falcon030 ### Hardware | Fact | Value | |---|---| | Years | STE in late 1989; to 1993 | | CPU | 68000, at 16 MHz on the Mega STE; 68030 on the TT and the Falcon | | RAM | To 4 MB on the STE | | STE additions | Blitter, fine scrolling, split screen, 8-bit stereo DMA PCM, 4096 colours | | Falcon030 (1992) | Adds a Motorola 56001 DSP | | OS | TOS 1.06 to 2.00 | Source: [Wikipedia: Atari ST](https://en.wikipedia.org/wiki/Atari_ST). ### How games were made and loaded No differences from the ST are listed yet. ### Media and image formats As for the ST: ST, MSA and STX. ### Tools Hatari disassembles DSP code as well as CPU code. It can disassemble code for the Falcon's 56001 ([Hatari debugger manual](https://hatari.tuxfamily.org/doc/debugger.html)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the model. An STE game can use the Blitter and DMA sound that the ST lacks. - On the Falcon, export the DSP code apart from the 68030 code. --- # Acorn Archimedes and Risc PC Acorn's 32-bit machines use ARM ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#arm)). The ARM2 and ARM3 run a 26-bit address mode with the flags in R15. Old Archimedes games depend on it. Ghidra's ARM languages start at v4, so this code has no exact built-in match. A game that uses RISC OS can be traced at its SWI calls. ## Acorn Archimedes ### Hardware | Model | Year | CPU | OS | |---|---|---|---| | A305, A310, A440 | July 1987 | ARM2 at 8 MHz | Arthur | | A3000 | 1989 | | RISC OS 2 | | A5000 | 1991 | ARM3 at 25 or 33 MHz | | | A3010 range | 1992 | ARM250 at 12 MHz | | The range ran Arthur, RISC OS 2 and RISC OS 3. Source: [Wikipedia: Acorn Archimedes](https://en.wikipedia.org/wiki/Acorn_Archimedes). - The chips are MEMC, VIDC and IOC. The ARM250 merges ARM2, IOC, VIDC and MEMC on one chip. - VIDC1 gives eight channels of 8-bit logarithmic stereo sound. - ARM2 and ARM3 use the 26-bit address mode with the flags in R15. ### How games were made and loaded Games ran under Arthur and RISC OS, which use SWI calls. Disks are 800 KB ADFS. The A5000 was the first model with an HD drive and reads MS-DOS and ST disks. ### Media and image formats Archimedes disk image formats still need sources. ### Tools - Ghidra's ARM languages start at v4. ARMv2 and ARMv3 code has no exact built-in match. - Unicorn is based on QEMU and likely lacks the 26-bit mode. - Archimedes code may need an emulator that knows the 26-bit mode, such as Arculator. - RISC OS module loaders for Ghidra and the debuggers of Arculator and RPCEmu still need sources. ### Projects to learn from None listed yet. ### For an exact reimplementation - Treat every instruction that reads or writes R15 as touching the flags as well as the PC. - Load the code into Ghidra as ARM v4 with care. Mark each place where 26-bit behaviour changes the result. - Take ground truth from a 26-bit-aware emulator. Unicorn's ARM models may give wrong results for this code. ## Acorn Risc PC ### Hardware | Fact | Value | |---|---| | Years | Launched 15 April 1994; to 2003 | | CPU | ARM610, ARM710, StrongARM up to 300 MHz | | RAM | To 256 MiB | | Video | VIDC20 | | OS | RISC OS 3.50–3.71 | Source: [Wikipedia: Risc PC](https://en.wikipedia.org/wiki/Risc_PC). The Risc PC's CPUs added a 32-bit mode. ### How games were made and loaded None listed yet. ### Media and image formats None listed yet. ### Tools Ghidra's ARM languages start at v4. No mapping of the ARM610 and ARM710 to a Ghidra language or a Unicorn model is listed yet. The RPCEmu debugger still needs a source. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record whether the code runs in 26-bit or 32-bit mode. Older games run in 26-bit mode. --- # Japanese 16-bit and 32-bit computers The X68000 uses the big-endian 68000 line. The PC-9801 and the FM Towns use x86 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md)). Each machine has its own custom video and sound chips. The PC-98's chips differ from the IBM PC's, even though the CPU and the OS match. A game that uses Human68k or MS-DOS can be traced at its OS calls. A game that drives the chips must be traced at its hardware register writes. ## Sharp X68000 ### Hardware | Fact | Value | |---|---| | Years | Released in March 1987, in Japan only; to 1993 | | CPU | HD68HC000 at 10 MHz; 16 MHz on the XVI; 68EC030 at 25 MHz on the X68030 | | RAM | 1–4 MB, to 12 MB | | VRAM | 1056 KB, split into graphics, text and sprite areas | | Sprites | 128, 32 per line; raster interrupts can raise that to 512 | | Sound | YM2151 with eight FM channels; MSM6258 ADPCM channel at 15.6 kHz | Source: [Wikipedia: X68000](https://en.wikipedia.org/wiki/X68000). ### How games were made and loaded The OS is Human68k, an MS-DOS-like OS made by Hudson Soft. Floppies hold 77 tracks of 8 sectors of 1024 bytes at 360 rpm, 1232 KiB in all ([Wikipedia: X68000](https://en.wikipedia.org/wiki/X68000)). ### Media and image formats The XDF, DIM and HDM image formats still need sources. ### Tools - Use MAME's debugger when no other debugger is available. - The XM6, PX68k and Unz debuggers still need sources. - Ghidra has the 68000 built in. Unicorn runs 68000 code but none of the X68000's chips. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record whether the game uses raster interrupts to show more than 128 sprites. - Record the model. The XVI and X68030 run faster. ## NEC PC-9801 ### Hardware | Fact | Value | |---|---| | Years | Launched in October 1982; the line ended on 30 September 2003 | | Sales | 18.3 million units | | CPU | 8086 at 5 MHz; later V30, 80286, 80386 | | RAM | 128 KB to 640 KB at first | | Video | Two μPD7220 controllers, for text and graphics separately; 640x400 in 8 colours, later 16 from 4,096 | | Blitters | GRCG (1985) and EGC (1986) | | Sound | FM on the PC-9801-26 card (YM2203) and the PC-9801-86 card (YM2608) | Source: [Wikipedia: PC-98](https://en.wikipedia.org/wiki/PC-98). ### How games were made and loaded The PC-98 runs x86 code under MS-DOS and N88-BASIC(86). Its display and sound hardware differ from the IBM PC's. ### Media and image formats | Kind | Formats | |---|---| | Floppy | FDI, FDD, D88/D98, HDM, NFD | | Hard disk | HDI, NHD | Sources: [libretro: Neko Project II Kai](https://docs.libretro.com/library/neko_project_ii_kai/), [Batocera wiki](https://wiki.batocera.org/systems:pc98). ### Tools - **Neko Project II Kai** emulates the PC-98. It needs BIOS, font and sound ROMs. - GameFrame's DOS tools help with the CPU and DOS layer ([DOS games](https://tanepiper.github.io/game-frame/dos.md)). The hardware layer needs its own notes. - Ghidra has x86 real mode built in. Unicorn runs x86 16-bit code ([re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Use the DOS method for the program and its calls ([DOS games](https://tanepiper.github.io/game-frame/dos.md)). - Write separate notes for the μPD7220, GRCG and EGC. The IBM PC's VGA notes do not apply ([2D graphics](https://tanepiper.github.io/game-frame/hardware/graphics-2d.md)). - Record which FM sound card the reference recording used. ## Fujitsu FM Towns ### Hardware | Fact | Value | |---|---| | Years | Released on 28 February 1989; to 1997 | | Sales | About 500,000 | | CPU | 80386DX at 16 MHz | | Sound | RF5c68 PCM, YM2612 FM | | Sprites | 1024 | | Media | CD-ROM drive as standard | Source: [Wikipedia: FM Towns](https://en.wikipedia.org/wiki/FM_Towns). The FM Towns Marty console (1993) runs Towns games. ### How games were made and loaded Towns OS is MS-DOS with the Phar Lap DOS extender ([Wikipedia: FM Towns](https://en.wikipedia.org/wiki/FM_Towns)). ### Media and image formats Games came on CD-ROM. No image format details are listed yet. ### Tools - A game built on the Phar Lap extender needs a loader for extended DOS programs. That differs from a real-mode MZ load ([DOS games](https://tanepiper.github.io/game-frame/dos.md)). - Ghidra has 32-bit x86 built in. Unicorn runs 32-bit x86 code. ### Projects to learn from None listed yet. ### For an exact reimplementation - Identify the extender and its executable format before loading the program into Ghidra ([IBM PC](https://tanepiper.github.io/game-frame/computers/ibm-pc.md#dos-extenders)). - Write separate notes for the FM Towns sprite and sound hardware. --- # The IBM PC and compatibles On the PC, identify the executable format first. The tools you need depend on the format more than on the CPU. Each PC era has its own executable format and tool chain. DOS used COM and MZ, Win16 used NE, DOS extenders used LE and LX, Win32 used PE32 and x64 uses PE32+. Check the magic first: MZ, then the header at `e_lfanew` for NE, LE, LX or PE. GameFrame's `tools/triage` and `tools/dos` already cover MZ and NE ([re-inventory](https://tanepiper.github.io/game-frame/skills/re-inventory.md)). The x86 CPUs and the PC's hardware are on [CPUs and floating point](https://tanepiper.github.io/game-frame/hardware/cpus.md) and [Hardware of the era](https://tanepiper.github.io/game-frame/hardware/index.md). | Era | Years | Hardware | Executable | Running it today | |---|---|---|---|---| | IBM PC, XT, AT | 1981–1984 | 8088 at 4.77 MHz; 80286 in the AT; MDA or CGA; PC speaker on the 8253 PIT (not confirmed) | COM, MZ | DOSBox, DOSBox-X | | PCjr, Tandy 1000 | 1984 on | CGA modes plus 160x200x16 and 320x200x16; SN76496 sound at port C0h (not confirmed) | COM, MZ | DOSBox `machine=tandy` or `pcjr` | | DOS extenders | About 1990 to the late 1990s | 386 and later | LE (DOS/4GW), COFF behind a go32 stub (DJGPP) (not confirmed) | DOSBox | | Windows 3.x | 1990–1995 | | NE (not confirmed) | Wine with winevdm/otvdm, or a 32-bit Windows VM | | Windows 95 to ME | 1995–2001 | DirectX from 1995 (not confirmed) | PE32 | Windows, Wine | | Windows XP to 11, x64 | 2001 on | AMD64 from 2003 (not confirmed) | PE32+ (magic `0x20B`) (not confirmed) | Windows, Wine, Proton | Sources: [IBM Personal Computer](https://en.wikipedia.org/wiki/IBM_Personal_Computer), [IBM PC XT](https://en.wikipedia.org/wiki/IBM_Personal_Computer_XT), [IBM PC AT](https://en.wikipedia.org/wiki/IBM_Personal_Computer_AT), [Tandy Graphics Adapter](https://en.wikipedia.org/wiki/Tandy_Graphics_Adapter), [Texas Instruments SN76489](https://en.wikipedia.org/wiki/Texas_Instruments_SN76489), [IBM PCjr](https://en.wikipedia.org/wiki/IBM_PCjr), [COM file](https://en.wikipedia.org/wiki/COM_file), [DOS MZ executable](https://en.wikipedia.org/wiki/DOS_MZ_executable), [Linear Executable](https://en.wikipedia.org/wiki/Linear_Executable), [DJGPP](https://www.delorie.com/djgpp/), [Windows 3.0](https://en.wikipedia.org/wiki/Windows_3.0), [New Executable](https://en.wikipedia.org/wiki/New_Executable), [Windows 95](https://en.wikipedia.org/wiki/Windows_95), [Portable Executable](https://en.wikipedia.org/wiki/Portable_Executable), [DirectX](https://en.wikipedia.org/wiki/DirectX), [Windows XP](https://en.wikipedia.org/wiki/Windows_XP), [x86-64](https://en.wikipedia.org/wiki/X86-64), [Windows XP Professional x64 Edition](https://en.wikipedia.org/wiki/Windows_XP_Professional_x64_Edition), [PE Format, Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format). Ghidra has built-in loaders for MZ, NE and PE. LE and LX need a community extension (not confirmed) ([Ghidra file formats overview](https://www.mintlify.com/NationalSecurityAgency/ghidra/processors/file-formats)). ## IBM PC, XT and AT ### Hardware 1981–1984. An 8088 at 4.77 MHz, or an 80286 in the AT. MDA or CGA video. The PC speaker is driven by the 8253 PIT (not confirmed). The x86 generations are on [CPUs and floating point](https://tanepiper.github.io/game-frame/hardware/cpus.md). The video modes are on [2D graphics](https://tanepiper.github.io/game-frame/hardware/graphics-2d.md), and the speaker on [Sound](https://tanepiper.github.io/game-frame/hardware/sound.md). ### How games were made and loaded Games are COM and MZ programs under DOS. The compiler leaves fingerprints in the binary ([compilers](#compilers-and-their-fingerprints)). ### Media and image formats COM and MZ executables. The 16-bit details are on [DOS games](https://tanepiper.github.io/game-frame/dos.md). ### Tools DOSBox and DOSBox-X run these programs. GameFrame's `tools/dos` and its DOS emulators cover MZ programs ([DOS games](https://tanepiper.github.io/game-frame/dos.md), [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Follow the [DOS games](https://tanepiper.github.io/game-frame/dos.md) method. - Find timing loops calibrated to the CPU's speed ([CPUs and floating point](https://tanepiper.github.io/game-frame/hardware/cpus.md#timing)). ## PCjr and Tandy 1000 ### Hardware 1984 on. CGA modes plus 160x200 in 16 colours and 320x200 in 16 colours. SN76496 sound at port C0h (not confirmed). ### How games were made and loaded Some games detect Tandy hardware and draw Tandy graphics even when CGA is chosen ([Tandy Graphics Adapter](https://en.wikipedia.org/wiki/Tandy_Graphics_Adapter)). Sierra's King's Quest (1984) was commissioned by IBM for the PCjr (not confirmed) ([King's Quest I](https://en.wikipedia.org/wiki/King%27s_Quest_I)). ### Media and image formats COM and MZ executables. ### Tools DOSBox with `machine=tandy` or `machine=pcjr`. ### Projects to learn from None listed yet. ### For an exact reimplementation - A Tandy path is a separate rendering and sound path in the same binary. Choose which path is ground truth. Write the choice in the project's `AGENTS.md`. - Find the hardware test that picks the path. Run the reference capture with the machine type that matches your choice. ## DOS extenders ### Hardware About 1990 to the late 1990s, on a 386 or later. Memory and extenders are on [Memory, storage, input and networks](https://tanepiper.github.io/game-frame/hardware/other.md). ### How games were made and loaded Watcom bundled DOS/4GW, which prints a start-up banner. Watcom's default 32-bit convention passes arguments in EAX, EDX, EBX and ECX. Doom was built with Watcom. Quake was built with DJGPP. These fingerprints are (not confirmed) ([Watcom C/C++](https://en.wikipedia.org/wiki/Watcom_C/C%2B%2B); [DOS/4G](https://en.wikipedia.org/wiki/DOS/4G); [DJGPP](https://en.wikipedia.org/wiki/DJGPP)). ### Media and image formats LE for DOS/4GW. COFF behind a go32 stub for DJGPP (not confirmed). ### Tools DOSBox runs these programs. Ghidra needs a community extension for LE and LX (not confirmed). GameFrame's DPMI support is described on [DOS games](https://tanepiper.github.io/game-frame/dos.md). ### Projects to learn from None listed yet. ### For an exact reimplementation - Identify the extender from its banner and its stub. - Copy the compiler's calling convention ([Calling conventions](https://tanepiper.github.io/game-frame/abi.md)). ## Windows 3.x ### Hardware 1990–1995. No hardware details beyond the PC's are listed yet. ### How games were made and loaded Programs are NE executables (not confirmed). WinG shipped on 21 September 1994. It let Windows 3.x programs write bitmap memory directly. Developers used it to port DOS games. DirectX and CreateDIBSection later replaced it. Doom95, SimCity 2000, SimTower and Civilization II are among the games listed as using it ([WinG](https://en.wikipedia.org/wiki/WinG)). ### Media and image formats NE executables. GameFrame's `tools/dos` covers NE ([DOS games](https://tanepiper.github.io/game-frame/dos.md)). ### Tools - Microsoft states that "64-bit Windows does not support running 16-bit Windows-based applications". Such attempts fail with ERROR_BAD_EXE_FORMAT ([Running 32-bit Applications, Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/winprog64/running-32-bit-applications)). - Windows 11 has no 32-bit edition, so no current Windows runs 16-bit code (not confirmed) ([Virtual DOS machine](https://en.wikipedia.org/wiki/Virtual_DOS_machine); [Windows 11](https://en.wikipedia.org/wiki/Windows_11)). - Run these programs with Wine and winevdm/otvdm, or in a 32-bit Windows VM. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record whether the game draws through WinG. - Capture the reference run in Wine or a 32-bit VM. A 64-bit Windows cannot run it. ## Windows 95 to ME ### Hardware 1995–2001. DirectX from 1995 (not confirmed). The 3D cards and APIs are on [3D accelerators](https://tanepiper.github.io/game-frame/hardware/graphics-3d.md). ### How games were made and loaded Programs are PE32 executables. Engine-era games are on [Engine-era PC games](https://tanepiper.github.io/game-frame/computers/modern-pc-engines.md). ### Media and image formats PE32 executables. Disc protection is described [below](#copy-protection-over-the-years). ### Tools Windows and Wine run these programs. GameFrame's PE tools and emulator cover them ([re-inventory](https://tanepiper.github.io/game-frame/skills/re-inventory.md), [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). ### Projects to learn from See [case studies](https://tanepiper.github.io/game-frame/case-studies/index.md) and [Software engines and middleware](https://tanepiper.github.io/game-frame/engines/index.md). ### For an exact reimplementation - Read the FPU control word the game runs with ([CPUs and floating point](https://tanepiper.github.io/game-frame/hardware/cpus.md#the-x87-fpu)). - Identify the compiler and its runtime ([compilers](#compilers-and-their-fingerprints)). ## Windows XP to 11 on x64 ### Hardware 2001 on. AMD64 from 2003 (not confirmed). ### How games were made and loaded Programs are PE32 or PE32+ executables. WOW64 runs 32-bit code on x64 and ARM64. A 32-bit process cannot load a 64-bit DLL for execution ([Running 32-bit Applications, Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/winprog64/running-32-bit-applications)). Steam launched in September 2003 and GOG in 2008. GOG often repackages old games with DOSBox or ScummVM (not confirmed) ([Steam (service)](https://en.wikipedia.org/wiki/Steam_(service)); [GOG.com](https://en.wikipedia.org/wiki/GOG.com)). ### Media and image formats PE32+ executables have the optional-header magic `0x20B` (not confirmed). ### Tools Windows, Wine and Proton run these programs. Unicorn runs x86-64 code. ### Projects to learn from None listed yet. ### For an exact reimplementation - A GOG release may wrap the original DOS program in DOSBox. Study the original program inside. - A later DRM-free release is often the easiest owned copy to study, because it has no DRM code. ## Compilers and their fingerprints Each compiler leaves recognisable code and strings in the binary. Once you know the compiler, you know which library code a port can skip and which arithmetic rules it must copy. These fingerprints are widely documented. Each one below is (not confirmed). | Compiler | Fingerprint | |---|---| | Turbo Pascal | Programs fail with "Runtime error 200" on fast CPUs, because the CRT unit's delay calibration divides by zero ([Runtime error 200](https://en.wikipedia.org/wiki/Turbo_Pascal#CRT_Delay_problem)). Real numbers use a 6-byte Real ([Turbo Pascal](https://en.wikipedia.org/wiki/Turbo_Pascal)) | | Borland C++ | A copyright string, its `c0.asm` startup code and sometimes appended TDInfo debug data ([Borland C++](https://en.wikipedia.org/wiki/Borland_C%2B%2B)) | | Watcom | Bundled DOS/4GW with its start-up banner; arguments in EAX, EDX, EBX and ECX ([Watcom C/C++](https://en.wikipedia.org/wiki/Watcom_C/C%2B%2B)) | | DJGPP | COFF behind a go32 stub ([DJGPP](https://en.wikipedia.org/wiki/DJGPP)) | | MSVC | The undocumented "Rich" header; imports from CRTs such as MSVCRT.DLL or VCRUNTIME140.DLL ([Microsoft Visual C++](https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B)) | | Delphi | VCL class names, RTTI and DFM form resources ([Delphi](https://en.wikipedia.org/wiki/Delphi_(software))) | The Borland debug symbols are on [DOS games](https://tanepiper.github.io/game-frame/dos.md). .NET and Unity are on [Engine-era PC games](https://tanepiper.github.io/game-frame/computers/modern-pc-engines.md). ## Copy protection over the years Protection checks moved over the years toward the disc, and later to online activation. - Manual checks and code wheels came first, such as Monkey Island's "Dial-a-Pirate", with malformed floppy tracks alongside (not confirmed) ([Copy protection](https://en.wikipedia.org/wiki/Copy_protection)). - Rob Northen's Copylock protected disks on the PC as on the Amiga and the ST ([Amiga](https://tanepiper.github.io/game-frame/computers/amiga.md)). - SafeDisc appeared in 1998 and was discontinued in March 2009. Its `secdrv.sys` driver had a privilege flaw patched in December 2007. Microsoft said in 2015 that SafeDisc games would not run on Windows 10. SecuROM games would not run there either ([SafeDisc](https://en.wikipedia.org/wiki/SafeDisc)). - StarForce 3.0 installed a driver that stayed after uninstall. In 2006 Heroes of Might and Magic V and GTR2 replaced it with SecuROM ([StarForce](https://en.wikipedia.org/wiki/StarForce)). - Valve says the Steam DRM wrapper "by itself is not an anti-piracy solution" and is "easily removed by a motivated attacker" ([Steam DRM, Steamworks documentation](https://partner.steamgames.com/doc/features/drm)). - Denuvo's first game was FIFA 15 in September 2014. It needs online activation, binds a token to the hardware and is integrated with the game's code. Publishers have removed it by patch ([Denuvo](https://en.wikipedia.org/wiki/Denuvo)). Protected or virtualised code is hard to decompile. Unicorn emulation is most useful on the game's own logic. A later DRM-free release is often the easiest owned copy to study. The law on working around protection varies by country ([Legal](https://tanepiper.github.io/game-frame/legal.md), [the law worldwide](https://tanepiper.github.io/game-frame/preservation/law/index.md)). You decide which route to take. --- # The Macintosh The Mac has had four CPU families, each with its own code format. 68k code is stored in resource forks, PowerPC code in PEF fragments, and Intel and Apple silicon code in Mach-O files. macOS since Catalina cannot run 32-bit apps, so most older Mac games now run only in an emulator or on old hardware. A fat or universal file holds two compiled versions of the same game. You can compare the two versions of each function. | Era | Years | CPU | Code format | Emulator | |---|---|---|---|---| | 68k Classic Mac OS | 1984–1998 (Mac OS 8.1 is the last 68k release) | 68000, 68020, 68030, 68040 | CODE resources in the resource fork, A5 world, A-line traps | Mini vMac, MAME, Basilisk II | | PowerPC Classic and early OS X | 1994–2006 | 601 to G5 | PEF loaded by the Code Fragment Manager; fat apps carry both | SheepShaver, QEMU | | Intel | 2006–2020 | x86, x86-64 | Mach-O, universal binaries | Native, VMs | | Apple silicon | 2020 on | ARM64 | Mach-O | Native; Rosetta 2 for x86-64 | ## 68k Macintosh ### Hardware The Macintosh 128K arrived on 24 January 1984, the Mac II in March 1987 and the Quadra 700 and 900 in October 1991 (not confirmed) ([Macintosh 128K](https://en.wikipedia.org/wiki/Macintosh_128K); [Macintosh II](https://en.wikipedia.org/wiki/Macintosh_II); [Macintosh Quadra](https://en.wikipedia.org/wiki/Macintosh_Quadra)). The CPUs run from the 68000 to the 68040 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-68000)). Mac OS 8.1 (January 1998) was the last release for 68k Macs ([Basilisk II](https://en.wikipedia.org/wiki/Basilisk_II)). ### How games were made and loaded 68k application code is stored as CODE resources in the resource fork. The details below are (not confirmed) ([Inside Macintosh: Mac OS Runtime Architectures](https://developer.apple.com/library/archive/documentation/mac/runtimehtml/RTArch-110.html)). - CODE 0 holds the jump table. - The globals and the jump table sit around register A5, the "A5 world". - The Segment Loader loads segments on demand. - Toolbox calls are A-line traps. Myst (1993) was built with HyperCard (not confirmed) ([Myst](https://en.wikipedia.org/wiki/Myst)). ### Media and image formats Forks are lost on file systems other than the Mac's. Archives therefore use MacBinary, BinHex, AppleSingle/AppleDouble or DiskCopy images (not confirmed) ([Resource fork](https://en.wikipedia.org/wiki/Resource_fork)). ### Tools - Ghidra does not understand resource forks. On macOS, open a fork by adding `..namedfork/rsrc` to the path. Import the CODE resources as raw binaries. Add the A-trap names to the 68000 spec by hand. RetroGhidra has a basic Classic Mac loader ([A-Traps in Ghidra, 68kMLA](https://68kmla.org/bb/goto/post?id=466137); [68kmla.org thread](https://68kmla.org/bb/goto/post?id=510430)). - resource_dasm decodes many classic resource types and disassembles 68k and PPC code resources (not confirmed) ([resource_dasm](https://github.com/fuzziqersoftware/resource_dasm)). - The emulators below are (not confirmed). Basilisk II (GPL, 1999) emulates 68k Macs up to Mac OS 8.1 ([Basilisk II](https://en.wikipedia.org/wiki/Basilisk_II)). Mini vMac covers the Mac Plus era ([Mini vMac](https://www.gryphel.com/c/minivmac/)). MAME has Mac drivers ([MAME](https://www.mamedev.org/)). - Executor and Advanced Mac Substitute reimplement the 68k Mac OS without Apple ROMs (not confirmed) ([Executor](https://en.wikipedia.org/wiki/Executor_(software)); [Advanced Mac Substitute](https://www.v68k.org/advanced-mac-substitute/)). - Unicorn runs 68k code (not confirmed) ([Unicorn Engine](https://www.unicorn-engine.org/)). A function from a single CODE segment could run under Unicorn with the A5 world and the needed traps stubbed. That is how `tools/emu/dos_env` stubs DOS ([re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). ### Projects to learn from Bungie's Marathon (1994) (not confirmed) ([Marathon](https://en.wikipedia.org/wiki/Marathon_(video_game))). Bungie released the Marathon 2 source in January 2000, and Aleph One was developed from it. The trilogy's data became freeware in 2005. "Classic Marathon 2" was re-released on Steam on Aleph One ([Marathon 2: Durandal](https://en.wikipedia.org/wiki/Marathon_2:_Durandal); [Aleph One on GitHub](https://github.com/Aleph-One-Marathon/alephone)). Aleph One is a fan engine. For an exact match, study the shipped binary. ### For an exact reimplementation A 68k Mac binary needs three steps before analysis. No mainstream loader does all three. 1. Extract the resource fork. 2. Split the CODE resources. 3. Rebuild the A5 jump table. Then name the A-line traps, so that each Toolbox call shows as a named call. ## PowerPC Macintosh ### Hardware The first Power Macs launched on 14 March 1994 with a 68k emulator in ROM (not confirmed) ([Power Macintosh](https://en.wikipedia.org/wiki/Power_Macintosh)). The CPUs run from the 601 to the G5 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#powerpc)). ### How games were made and loaded PEF is Apple's RISC executable format for Classic Mac OS. The Code Fragment Manager loads it. PowerPC Mac OS X kept it for Carbon apps ([Preferred Executable Format](https://en.wikipedia.org/wiki/Preferred_Executable_Format)). Fat Classic apps held 68k CODE resources and a PPC fragment together (not confirmed) ([Fat binary](https://en.wikipedia.org/wiki/Fat_binary)). The Classic environment ran Mac OS 9 apps up to 10.4 and never on Intel (not confirmed) ([Classic environment](https://en.wikipedia.org/wiki/Classic_Environment)). ### Media and image formats As for 68k Macs: MacBinary, BinHex, AppleSingle/AppleDouble or DiskCopy images (not confirmed). ### Tools - Ghidra ships a PEF loader ([Ghidra API: PefLoader 11.4](https://ghidradocs.com/11.4_PUBLIC/docs/GhidraAPI_javadoc/api/ghidra/app/util/opinion/PefLoader.html); [PefLoader 12.1.3](https://ghidradocs.com/12.1.3_PUBLIC/docs/GhidraAPI_javadoc/api/ghidra/app/util/opinion/PefLoader.html)). - The emulators below are (not confirmed). SheepShaver (GPL, final release 2.5 in September 2022) runs Mac OS 7.5.2 to 9.0.4 and has no MMU emulation ([SheepShaver](https://en.wikipedia.org/wiki/SheepShaver)). QEMU's g3beige and mac99 machines run Mac OS 9 and OS X up to 10.5 ([QEMU PowerPC Mac documentation](https://www.qemu.org/docs/master/system/ppc/powermac.html)). - Unicorn runs PowerPC code (not confirmed). ### Projects to learn from The Marathon games and Aleph One, as above. ### For an exact reimplementation - In a fat app, compare the 68k and PPC versions of each function. Both were compiled from the same source. ## Intel Macintosh ### Hardware Intel Macs arrived in January 2006 (not confirmed) ([Mac transition to Intel processors](https://en.wikipedia.org/wiki/Mac_transition_to_Intel_processors)). ### How games were made and loaded Rosetta translated PowerPC code from Mac OS X 10.4.4 until Lion removed it in 2011 ([Rosetta](https://en.wikipedia.org/wiki/Rosetta_(software))). macOS Catalina (7 October 2019) runs only 64-bit apps ([macOS Catalina](https://en.wikipedia.org/wiki/MacOS_Catalina)). ### Media and image formats Mach-O magic is `0xfeedface` or `0xfeedfacf`. Universal files use `0xcafebabe` ([Mach-O](https://en.wikipedia.org/wiki/Mach-O)). ### Tools Ghidra has a built-in Mach-O loader (not confirmed) ([Ghidra file formats overview](https://www.mintlify.com/NationalSecurityAgency/ghidra/processors/file-formats)). Unicorn runs x86 code. ### Projects to learn from None listed yet. ### For an exact reimplementation - A universal file carries two compiled versions of the same game. Use the second one as a check on each function. - A 32-bit Intel build needs a Mac older than Catalina or a VM. ## Apple silicon Macintosh ### Hardware ARM64 Macs, from 2020 ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#arm)). ### How games were made and loaded Rosetta 2 shipped with Big Sur. Apple said at WWDC 2025 that most of it will go in macOS 28 (2027), keeping support for older unmaintained games. The article gives both 27 and 28 as the version ([Rosetta](https://en.wikipedia.org/wiki/Rosetta_(software))). Rosetta 2 does not translate kernel extensions or AVX instructions (not confirmed) ([About the Rosetta translation environment, Apple](https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment)). ### Media and image formats Mach-O, as for Intel Macs. ### Tools Ghidra has AARCH64 built in. Unicorn runs ARM64 code. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record whether the reference run used native ARM64 code or x86-64 code under Rosetta 2. --- # Engine-era PC games In engine-era PC games much of the logic is outside the native executable. It is in managed assemblies, in C++ generated from them, or in packed asset files. This page has one section per engine or runtime. For these games the tools depend on the engine more than on the hardware. Each section uses the headings of the machine pages that apply. [Software engines and middleware](https://tanepiper.github.io/game-frame/engines/index.md) describes the engines and how to recognise each one. ## .NET and Unity with Mono ### How games were made and loaded .NET assemblies decompile to near-source C# ([.NET Framework](https://en.wikipedia.org/wiki/.NET_Framework); [Mono](https://en.wikipedia.org/wiki/Mono_(software))). ### Tools The REA tools inspect managed assemblies ([re-rea](https://tanepiper.github.io/game-frame/skills/re-rea.md)). ### For an exact reimplementation - Read the decompiled C# for the logic. Check its floating-point and integer rules against the runtime the game shipped with. ## Unity with IL2CPP ### How games were made and loaded Unity's IL2CPP "converts all managed assemblies into standard C++ code". That code is compiled to an executable or a DLL ([IL2CPP overview, Unity Manual](https://docs.unity3d.com/Manual/il2cpp-introduction.html)). ### Media and image formats On Windows the output is `GameAssembly.dll` plus `global-metadata.dat` (not confirmed). ### Tools Il2CppDumper rebuilds names from the metadata (not confirmed) ([Il2CppDumper](https://github.com/Perfare/Il2CppDumper)). ### For an exact reimplementation - Rebuild the names first. Then trace the native code as for any PE program ([re-trace](https://tanepiper.github.io/game-frame/skills/re-trace.md)). ## Unreal Engine 4 and 5 ### How games were made and loaded Unreal Engine 4 and 5 pack assets in `.pak` files, which may be AES-encrypted. UObject reflection keeps class names in the binary (not confirmed) ([Unreal Engine](https://en.wikipedia.org/wiki/Unreal_Engine)). ### Media and image formats `.pak` files, which may be AES-encrypted (not confirmed). ### Tools More on Unreal is in [Unreal](https://tanepiper.github.io/game-frame/engines/unreal.md). ### For an exact reimplementation - Use the class names that reflection keeps to name the functions. ## Godot ### How games were made and loaded Godot packs `.pck` files (not confirmed) ([Godot](https://godotengine.org/)). ### Media and image formats `.pck` files (not confirmed). ### Tools gdsdecomp recovers projects (not confirmed) ([gdsdecomp](https://github.com/bruvzg/gdsdecomp)). ### For an exact reimplementation - Recover the project, then read the game's scripts for the logic. ## Anti-cheat Kernel-level anti-cheat can block debuggers (not confirmed) ([Anti-cheat software](https://en.wikipedia.org/wiki/Anti-cheat_software)). Protected or virtualised code is hard to decompile. Unicorn emulation is most useful on the game's own logic ([re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)). ## Linux builds as a second binary Linux ports came from Loki Software between 1998 and 2002. Most Linux players now use Proton (2018) and the Steam Deck (2022). These facts are (not confirmed) ([Loki Entertainment Software](https://en.wikipedia.org/wiki/Loki_Entertainment_Software); [Proton](https://en.wikipedia.org/wiki/Proton_(software)); [Steam Deck](https://en.wikipedia.org/wiki/Steam_Deck)). Linux builds sometimes keep more symbols than Windows builds. Use a Linux build as a second binary. Compare its functions with the Windows build to name them. --- # Console games A home console game runs on fixed hardware with little or no operating system. The game controls the hardware directly. It writes to the video chip's registers, starts DMA transfers, reads the controllers from memory-mapped ports and times everything to the display's vertical blank. So reverse engineering and porting a console game use different methods from PC work. This section covers how to study home console games and how to bring them to other systems. The work follows GameFrame's playbook. The tools and the hardware details differ from PC work. The handhelds, from the Game & Watch to the Switch, are in [Handhelds](https://tanepiper.github.io/game-frame/handhelds/index.md). The arcade boards are in [Arcade](https://tanepiper.github.io/game-frame/arcade/index.md). ## Ask your agent Start on a console game. Copy this prompt into your coding agent: ```text In my GameFrame project, start work on a game for . 1. Read docs/consoles/README.md, docs/consoles/tools.md and the platform page for . 2. Record the region, the revision and the checksum of the ROM or disc image. 3. Load the code into Ghidra with the right processor and memory map, and label the hardware registers. 4. Find the reset vector, the vertical blank handler and the controller read. 5. Map the main variables in RAM with the emulator's RAM search. Write them into labels/. ``` ## Platforms by generation - [Early consoles](https://tanepiper.github.io/game-frame/consoles/early-consoles.md): the Odyssey, Atari 2600, Intellivision, ColecoVision, Atari 5200 and 7800, and Vectrex. - [Console platforms](https://tanepiper.github.io/game-frame/consoles/platforms.md): the home consoles from the NES to the sixth generation, with their CPUs, video and sound chips, and what each means for the work. - [CD add-ons](https://tanepiper.github.io/game-frame/consoles/cd-add-ons.md): the Sega CD, 32X, PC Engine CD and Philips CD-i. - [Jaguar and 3DO](https://tanepiper.github.io/game-frame/consoles/32-bit-oddities.md): the Atari Jaguar and the 3DO, with their RISC co-processors and the ARMv3 CPU. - [Seventh generation onward](https://tanepiper.github.io/game-frame/consoles/modern.md): from the Xbox 360 to the PS5, with their signed executables, loaders and emulator status. ## Doing the work - [Tools for console work](https://tanepiper.github.io/game-frame/consoles/tools.md): Ghidra processors and loaders, emulators with debuggers, Unicorn's architectures, and splitting tools. - [Routes to a port](https://tanepiper.github.io/game-frame/consoles/porting.md): reimplementation, matching decompilation, static recompilation and emulation, and how to replace each part of the hardware. - [Matching decompilation](https://tanepiper.github.io/game-frame/consoles/matching-decomp.md): how the decompilation projects work, and when that route is worth its cost. ## How console work differs from PC work | Topic | PC games | Console games | |---|---|---| | The program | An EXE with imports, sometimes symbols | A ROM or a disc image; code at fixed addresses, often banked; no imports | | The CPU | x86 | 6502, 65816, Z80, 68000, MIPS, SH-2, SH-4, PowerPC, ARM; often two or more CPUs | | Hardware access | Through drivers and APIs | Direct writes to memory-mapped registers | | Time | A timer interrupt or the frame | The vertical blank, at 60 Hz (NTSC) or 50 Hz (PAL) | | Graphics | A frame buffer | Often tiles, sprites and layers built by a video chip; frame buffers from the 32-bit generation | | Sound | A sound card or an API | A sound chip or a separate sound CPU with its own program | | Saves | Files | Battery-backed RAM in the cartridge, memory cards, passwords | | Ground truth | Run the EXE in an emulator or on a PC | Run the ROM in an accurate emulator with a debugger | | Source and symbols | Sometimes symbols, rarely source | Sometimes symbol maps on discs and in prototypes; leaked source exists for some games; GameFrame's method does not use it ([Legal](https://tanepiper.github.io/game-frame/legal.md)) | ## Where to start 1. Identify the console, the region and the exact revision of the game. Regions differ in timing (50 or 60 Hz), text and sometimes code. Record the checksum of the ROM or disc. 2. Get an accurate emulator with a debugger running the game ([Tools](https://tanepiper.github.io/game-frame/consoles/tools.md)). 3. Load the code into Ghidra with the right processor and memory map. For banked cartridges, load each bank at the address it runs at. 4. Find the reset vector and the vertical blank interrupt handler. From them you can find the main loop and the frame timing. 5. Find the controller read. It is usually one function called once a frame, storing the buttons in RAM. Log those buttons each frame as the input record ([Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md)). 6. Map the RAM: which addresses hold the player, the enemies, the level, the random state. Use the emulator's RAM watch and RAM search to find these addresses. Write them into `labels/`. Then follow the usual loop: trace, emulate, port, verify. --- # Early consoles The first and second generation consoles have tiny ROMs, no operating system and well-known CPUs. Most of them use a 6502 family CPU, a Z80, a 6809 or the CP1600. Ghidra builds in all four. On several of these machines the game logic depends on exact CPU timing. Take ground truth from cycle-exact emulator traces. ## The consoles | Console | Year | CPU | Media | Ghidra module or loader | Emulator with a debugger | |---|---|---|---|---|---| | Magnavox Odyssey | September 1972 | None | Cards that rewire jumpers | Not applicable | None | | Atari 2600 | September 1977 | 6507 at 1.19 MHz | 4 KB cartridge space, with bank switching (F8, F6, E0, 3F) | 6502 | Stella | | Intellivision | 1979 test, 1980 national | CP1610, about 894.886 kHz (not confirmed) | Cartridge with 10-bit ROM | CP1600 | jzIntv | | ColecoVision | August 1982 | Z80A at 3.58 MHz | Cartridge; 8 KB BIOS | Z80 | MAME | | Atari 5200 | November 1982 | SALLY 6502C at 1.79 MHz | Cartridge | 6502 | MAME, Altirra (not confirmed) | | Atari 7800 | 1984 test, May 1986 | SALLY at 1.79 MHz, 1.19 MHz on TIA and RIOT access | Cartridge; NTSC cartridges are signed | 6502 | MAME | | Vectrex | November 1982 | 68A09 at 1.5 MHz | Cartridge | 6809 under MC6800, plus [ghidra-vectrex-loader](https://github.com/nevesnunes/ghidra-vectrex-loader) | MAME | MAME's debugger works across all these CPUs ([MAME docs: debugger](https://docs.mamedev.org/debugger/index.html)). ## Magnavox Odyssey ### Hardware The Odyssey has no CPU. It is built from discrete transistor and diode logic, and game cards rewire jumpers ([Magnavox Odyssey](https://en.wikipedia.org/wiki/Magnavox_Odyssey)). ### For an exact reimplementation The Odyssey has no software to reverse engineer. A port has to simulate the circuit, as for the discrete-logic arcade games. ## Atari 2600 ### Hardware The 6507 is a 6502 with 8 KB of address space and no interrupts ([Wikipedia: MOS Technology 6502](https://en.wikipedia.org/wiki/MOS_Technology_6502)). The TIA makes the video and the sound. The RIOT holds 128 bytes of RAM. A cartridge has 4 KB of address space. Larger games use bank-switching schemes such as F8, F6, E0 and 3F ([Atari 2600](https://en.wikipedia.org/wiki/Atari_2600); [Atari 2600 hardware](https://en.wikipedia.org/wiki/Atari_2600_hardware)). There is no frame buffer. The program writes TIA registers in time with each scanline. This is called "racing the beam". ### Tools Stella has a debugger built on DiStella, with breakpoints, TIA and RIOT views and scanline stepping (not confirmed) ([Stella](https://stella-emu.github.io/)). Load the code with Ghidra's 6502 language. Ghidra's 6502 `ADC` p-code is not exact, so take ground truth from a tested core ([Tools](https://tanepiper.github.io/game-frame/consoles/tools.md#disassembly-and-decompilation)). ### Projects to learn from Commented 2600 disassemblies appear on AtariAge (not confirmed) ([AtariAge](https://atariage.com/)). ### For an exact reimplementation A port must model the CPU cycles in each scanline. Take ground truth from cycle-exact emulator traces. Record the cartridge's bank-switching scheme, because a function's address identifies it only together with its bank. ## Intellivision ### Hardware The CP1610 addresses only 16-bit words and uses 10 bits of each opcode. R7 is the program counter and R6 the stack pointer. SDBD builds a 16-bit operand from two 10-bit ROM reads ([Wikipedia: CP1600](https://en.wikipedia.org/wiki/General_Instrument_CP1600)). The STIC makes the video. An AY-3-8914 makes the sound. Game code calls into the Executive ROM ([Intellivision](https://en.wikipedia.org/wiki/Intellivision)). Sources give different clock speeds. One gives about 894.886 kHz, the NTSC colour clock divided by 4. The CPU article gives about 2 MHz for the CP1610. ### Tools Ghidra builds in the CP1600 as `CP1600:BE:16:default` ([CP1600.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/CP1600/data/languages/CP1600.ldefs)). jzIntv has a command-line debugger (not confirmed) ([jzIntv](http://spatula-city.org/~im14u2c/intv/)). Unicorn has no CP1600. MAME's cp1610 core file carries a BSD-3-Clause header. ### For an exact reimplementation Many games call into the Executive ROM, so a port must reproduce what that ROM does. Trace the calls into it as well as the cartridge code. ## ColecoVision ### Hardware The Z80A runs at 3.58 MHz. A TMS9928A with 16 KB of VRAM makes the video. An SN76489A makes the sound. The console has 1 KB of RAM and an 8 KB BIOS ([ColecoVision](https://en.wikipedia.org/wiki/ColecoVision)). ### Tools Ghidra builds in the Z80. Unicorn and Capstone have no Z80, so take ground truth from a small core such as [iz80](https://github.com/ivanizag/iz80) (BSD-3, passes ZEXALL) or from MAME. ## Atari 5200 ### Hardware The SALLY 6502C runs at 1.79 MHz. ANTIC, GTIA and POKEY make the video and sound. The console has 16 KB of RAM ([Atari 5200](https://en.wikipedia.org/wiki/Atari_5200)). ## Atari 7800 ### Hardware SALLY runs at 1.79 MHz and drops to 1.19 MHz when it accesses the TIA or the RIOT. MARIA makes the video. The TIA makes the sound. The console has 4 KB of RAM ([Atari 7800](https://en.wikipedia.org/wiki/Atari_7800)). ### For an exact reimplementation NTSC cartridges are signed. The console checks the signature. PAL 7800s skip the signature check. ## Vectrex ### Hardware The 68A09 runs at 1.5 MHz. An AY-3-8912 makes the sound. The console has 1 KB of RAM. The CPU drives the vector beam through a DAC ([Vectrex](https://en.wikipedia.org/wiki/Vectrex)). The 6809 joins A and B into a 16-bit D register. It has an 8x8 hardware multiply ([Wikipedia: Motorola 6809](https://en.wikipedia.org/wiki/Motorola_6809)). ### Tools Ghidra builds in the 6809 under `MC6800`, and describes it as big-endian ([6800.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/MC6800/data/languages/6800.ldefs)). [ghidra-vectrex-loader](https://github.com/nevesnunes/ghidra-vectrex-loader) loads cartridges. Capstone has an `M680X_6809` mode. Unicorn has no 6809. MAME's m6809 core file carries a BSD-3-Clause header. ### For an exact reimplementation The CPU drives the beam, so game code does the drawing. Trace the drawing code like the game logic. --- # Console platforms Each console has its own CPU, video chip, sound hardware and conventions. This page lists the home consoles most often studied, with what each means for reverse engineering and porting. The details here are summaries. Each section names the community documents for that console, which are far more complete. Other consoles have their own pages: - [Early consoles](https://tanepiper.github.io/game-frame/consoles/early-consoles.md): the Odyssey to the Vectrex. - [Jaguar and 3DO](https://tanepiper.github.io/game-frame/consoles/32-bit-oddities.md): the Atari Jaguar and the 3DO. - [CD add-ons](https://tanepiper.github.io/game-frame/consoles/cd-add-ons.md): the Sega CD, 32X, PC Engine CD and CD-i. - [Seventh generation onward](https://tanepiper.github.io/game-frame/consoles/modern.md): the Xbox 360, PS3, Wii, Wii U, PS4, Xbox One, PS5 and Series consoles. The Game Boy, Game Boy Advance and the other handhelds are in the [Handhelds](https://tanepiper.github.io/game-frame/handhelds/index.md) section. ## 8-bit and 16-bit consoles | Console | Year | CPU | Video | Sound | |---|---|---|---|---| | NES / Famicom | 1983 | Ricoh 2A03 (6502 core without decimal mode), 1.79 MHz | PPU: tiles, 64 sprites, one background layer | Built into the 2A03 (2 pulse, triangle, noise, sample channel) | | Sega Master System | 1985 | Zilog Z80 | VDP: tiles and sprites | SN76489 | | PC Engine / TurboGrafx-16 | 1987 | HuC6280 (65C02 variant) | HuC6270 VDC | Six wave channels | | Sega Mega Drive / Genesis | 1988 | Motorola 68000, plus a Z80 for sound | VDP: two scrolling layers, sprites | YM2612 FM and SN76489 | | Super Nintendo / Super Famicom | 1990 | Ricoh 5A22 (65C816), plus the SPC700 sound CPU | PPU: up to four layers, Mode 7 rotation and scaling | S-DSP with eight sample channels | **What to know.** - **Banking.** Cartridges hold more than the CPU can address, so a mapper chip switches banks. On the NES, mappers such as MMC1 and MMC3 decide which banks are visible. A function's address alone does not identify it; you also need the bank. Name functions as `bank:address`. - **Hand-written assembly.** Most games of this generation were written in assembly. Expect no calling convention, values passed in registers and flags, and code that jumps into the middle of other code. - **The frame.** Almost every game runs its logic once per vertical blank (60 Hz in NTSC regions, 50 Hz in PAL regions). A frame that takes too long causes a "lag frame", where the logic skips a frame. Speedrunners study lag frames closely. - **Coprocessors in cartridges.** Some SNES cartridges carry extra chips: the Super FX (a RISC CPU for 3D, as in Star Fox), DSP-1 (maths), SA-1 (a faster 65C816). Trace their code as well. - **PC Engine.** HuC6280 code disassembles mostly correctly with Ghidra's 65C02 language. The block-transfer and MMU instructions need the [Ghidra_HuC6280](https://github.com/TiCoKH/Ghidra_HuC6280) extension. The CD unit is on the [CD add-ons](https://tanepiper.github.io/game-frame/consoles/cd-add-ons.md) page. - **Community documents.** NESdev Wiki (NES), SNESdev Wiki and fullsnes (SNES), Plutiedev and Sega Retro (Mega Drive). Pan Docs and GBATEK cover the [Game Boy family](https://tanepiper.github.io/game-frame/handhelds/game-boy.md). ## 32-bit and 64-bit consoles | Console | Year | CPU | Graphics | Notes | |---|---|---|---|---| | Sega Saturn | 1994 | Two Hitachi SH-2, plus a 68000 for sound | VDP1 (sprites and quads) and VDP2 (backgrounds) | Hard to program; games use both SH-2s unevenly | | Sony PlayStation | 1994 | MIPS R3000A, little-endian, 33.9 MHz, no floating-point unit | GPU drawing triangles and quads into a frame buffer; the GTE coprocessor for 3D maths | Fixed-point 3D; textures without perspective correction | | Nintendo 64 | 1996 | NEC VR4300 (MIPS III, 64-bit capable, big-endian) | RCP: the RSP (vector processor running microcode) and the RDP (rasteriser) | Graphics and audio are microcode programs on the RSP | **What to know.** - **PlayStation GTE.** The Geometry Transformation Engine is coprocessor 2. It does matrix multiplications, perspective projection and lighting in fixed point, with its own rounding and its own overflow flags. A port of a PlayStation game's 3D must reproduce the GTE's results exactly where the game uses them for logic (collision, positions). The psx-spx document describes every GTE operation. - **No floating point on the PlayStation.** Everything is integer or fixed point. Integer maths has no rounding mode, so exact ports are easier. - **N64 microcode.** The game sends display lists to the RSP, which runs microcode (Fast3D, F3DEX and others). The microcode transforms and lights vertices and passes triangles to the RDP. A port interprets the display lists. The SM64 PC port is one project that does this. - **N64 floating point.** The VR4300 has an FPU with single and double precision. Games are mostly single precision. MIPS has no 80-bit intermediates, so the C code's float maths maps well to modern 32-bit floats. Check that the port's compiler does not use fused multiply-add. - **Saturn.** The two SH-2s share the work. Each has its own cache. Find which code runs on which CPU and how they synchronise. Few games of this platform have been studied in depth. - **Community documents.** psx-spx (PlayStation), N64brew Wiki (Nintendo 64), SegaXtreme and the Saturn hardware manuals. ## The sixth generation | Console | Year | CPU | Graphics | Notes | |---|---|---|---|---| | Sega Dreamcast | 1998 | Hitachi SH-4 with a fast vector FPU | PowerVR CLX2 (tile-based) | Some games use Windows CE | | Sony PlayStation 2 | 2000 | Emotion Engine: MIPS R5900 core with 128-bit integer instructions, and two vector units (VU0, VU1) | Graphics Synthesizer | The IOP (a PlayStation CPU) handles input, sound and discs | | Nintendo GameCube | 2001 | IBM Gekko (PowerPC 750 with paired-single floats) | Flipper | Wii (2006) uses the similar Broadway and Hollywood | | Microsoft Xbox | 2001 | Intel Pentium III-class x86 | NVIDIA NV2A (close to a GeForce 3) | Programs are XBE files; code is close to a PC game's | **What to know.** - **PS2 floating point is not IEEE 754.** The R5900's FPU and the vector units have no infinities or NaNs, clamp large values, and round differently. A port that uses normal floats gets different results at the edges. PCSX2's code and documents describe the differences. - **Vector unit microprograms.** PS2 games run their own programs on VU1 for geometry. Trace those programs like the rest of the game's code. - **GameCube paired singles.** The Gekko can do two single-precision operations in one instruction, with its own quantisation instructions for loading and storing compressed values. Ghidra needs an extension to read them. The Wii shares the paired singles and the DOL and REL formats. It is covered on the [Seventh generation onward](https://tanepiper.github.io/game-frame/consoles/modern.md) page. - **Xbox.** The code is x86 and uses a version of DirectX 8. Much of the PC advice applies. - **Compilers.** Games for these consoles were mostly written in C and C++ with known compilers (GCC versions for PS2, Metrowerks CodeWarrior for GameCube, MSVC for Xbox). If you know the compiler, you can recognise library code. Matching decompilation also needs the compiler ([Matching decompilation](https://tanepiper.github.io/game-frame/consoles/matching-decomp.md)). ## Arcade boards Arcade games ran on boards built for one game or one family of games. They have their own section, [Arcade](https://tanepiper.github.io/game-frame/arcade/index.md). MAME's driver for each board is the main record of that board. --- # CD add-ons These add-ons give the base console a second CPU or a BIOS. Game code runs on that second CPU. An emulator needs the add-on's BIOS. The Philips CD-i is a standalone player, but its programs are OS-9 modules on a 68000-family CPU. ## The platforms | Platform | Year | CPU | Media | Ghidra module or loader | Emulator with a debugger | |---|---|---|---|---|---| | Mega-CD / Sega CD | December 1991 (JP) | Second 68000 at 12.5 MHz | CD; BIOS on the add-on | 68000 | Genesis Plus GX, PicoDrive, ares, MAME (not confirmed) | | Sega 32X | November 1994 | Two SH-2 at 23 MHz | Cartridge | SuperH SH-2 | PicoDrive, MAME (not confirmed) | | PC Engine CD-ROM² | December 1988 (JP) | HuC6280 at 7.16 MHz | CD; needs a System Card BIOS | 65C02 is close; [Ghidra_HuC6280](https://github.com/TiCoKH/Ghidra_HuC6280) | Mednafen, Mesen 2 | | Philips CD-i | December 1991 | SCC68070 at 15.5 MHz (not confirmed) | CD; programs are OS-9 modules | 68000 | MAME cdimono1, CeDImu | Sources for the table: [Sega CD](https://en.wikipedia.org/wiki/Sega_CD), [32X](https://en.wikipedia.org/wiki/32X), [TurboGrafx-CD](https://en.wikipedia.org/wiki/TurboGrafx-CD), [CD-i](https://en.wikipedia.org/wiki/CD-i). The Sega CD and 32X split game code across two or more CPUs. Find and export each CPU's code separately. The second CPU's code is often taken from a RAM dump after the main CPU uploads it. ## Mega-CD / Sega CD ### Hardware The add-on has a second 68000 at 12.5 MHz. It holds 512 KB of program RAM and 256 KB of word RAM shared with the Mega Drive. An RF5C164 makes PCM sound. The BIOS is in the add-on. The size of the backup RAM is not confirmed. ### Tools Ghidra builds in the 68000. Its default 68000 language is a 68040 superset, which accepts instructions a real 68000 would fault on. Unicorn emulates the 68000, so a function that runs only on the CPU can be run headless with stubs for the custom chips ([Tools](https://tanepiper.github.io/game-frame/consoles/tools.md#gameframe-s-unicorn-emulator)). Genesis Plus GX, PicoDrive, ares and MAME run the add-on (not confirmed). ### For an exact reimplementation Trace both 68000s. Map which code runs on which CPU and how they share word RAM. ## Sega 32X ### Hardware The 32X adds two SH-2 CPUs at 23 MHz, 256 KB of SDRAM, a frame buffer and PWM sound. The Mega Drive's own 68000 and Z80 keep running beside them. ### Tools Ghidra builds in the SH-2 under SuperH. Unicorn has no SuperH, so take ground truth from a full emulator such as PicoDrive or MAME (not confirmed). MAME's sh core is another source of ground truth. ### For an exact reimplementation A 32X game can run code on four CPUs: two SH-2s, the 68000 and the Z80. Export each CPU's code separately. ## PC Engine CD-ROM² ### Hardware The HuC6280 runs at 7.16 MHz. It is a 65C02-style core with extra instructions, an MMU that maps 64 KB onto 2 MB, a timer and a PSG ([Wikipedia: Hudson Soft HuC6280](https://en.wikipedia.org/wiki/Hudson_Soft_HuC6280)). The CD unit needs a System Card BIOS. The Super System Card adds RAM, and the Arcade Card adds 2 MB. ### Tools HuC6280 code disassembles mostly correctly with Ghidra's 65C02 language. The block-transfer and MMU instructions come out wrong without the [Ghidra_HuC6280](https://github.com/TiCoKH/Ghidra_HuC6280) extension. Capstone has only the 6502 modes. Mednafen and Mesen 2 run the games. Mednafen has a debugger in its SDL front end ([Mednafen docs](https://mednafen.github.io/documentation/)). MAME's h6280 core is a source of ground truth. ### For an exact reimplementation Record which System Card the game needs. The card sets which BIOS the game runs on and how much RAM it has. ## Philips CD-i ### Hardware The CPU is an SCC68070 at 15.5 MHz (not confirmed). The system runs CD-RTOS, which is built on Microware OS-9. Programs are OS-9 modules. ### Tools Load the modules with Ghidra's 68000 language. MAME's cdimono1 driver and CeDImu run the programs. --- # Jaguar and 3DO The Atari Jaguar and the 3DO do not belong to the main console families. Jaguar games run much of their code on two RISC co-processors. Ghidra has no module for them. The 3DO uses an ARMv3 CPU, older than any ARM language Ghidra builds in. The handhelds are in the [Handhelds](https://tanepiper.github.io/game-frame/handhelds/index.md) section. ## The platforms | Platform | Year | CPU | Media | Ghidra module or loader | Emulator with a debugger | |---|---|---|---|---|---| | Atari Jaguar | November 1993 | 68000 at 13.295 MHz; Tom and Jerry RISC at 26.59 MHz | Cartridge | 68000; no Tom or Jerry module | BigPEmu (closed), Virtual Jaguar | | 3DO | October 1993 | ARM60 (ARMv3) at 12.5 MHz | CD; Portfolio OS | ARM v4 and up only | Opera (not confirmed) | ## Atari Jaguar ### Hardware The 68000 runs at 13.295 MHz. Tom and Jerry are 32-bit RISC cores at 26.591 MHz, with 4 KB and 8 KB of local RAM. The console has 2 MB of RAM. A memory controller flaw makes it awkward to run RISC code from main RAM, so developers ran more of their code on the 68000 ([Wikipedia: Atari Jaguar](https://en.wikipedia.org/wiki/Atari_Jaguar)). The main loop runs on the 68000 and uploads RISC code ([Game Tech Wiki: Jaguar](https://emulation.gametechwiki.com/index.php/Jaguar)). Tom has two banks of thirty-two 32-bit registers (not confirmed) ([Jagdox general](https://www.mulle-kybernetik.com/jagdox/general.html)). ### Tools Ghidra builds in the 68000. No Ghidra module is known for Tom and Jerry ([GitHub search](https://github.com/search?q=ghidra+jaguar&type=repositories)). MAME's jaguar core file carries a BSD-3-Clause header. BigPEmu is Rich Whitehouse's closed-source Jaguar emulator, written for Digital Eclipse's Atari 50. Whether it has a debugger is unknown. Virtual Jaguar is the open alternative, now maintained by Joseph Mattiello, with GPU, DSP and blitter timing models ([Game Tech Wiki: Jaguar](https://emulation.gametechwiki.com/index.php/Jaguar)). ### For an exact reimplementation Game code is split across the 68000 and the RISC cores. Find and export each CPU's code separately. The RISC code is often taken from a RAM dump after the 68000 uploads it. ## 3DO ### Hardware The ARM60 is an ARMv3 core at 12.5 MHz. The Madam and Clio chips hold the cel engine. Games run on the Portfolio OS ([3DO Interactive Multiplayer](https://en.wikipedia.org/wiki/3DO_Interactive_Multiplayer)). ### Tools Ghidra's ARM languages start at v4, so the ARM60 has no exact built-in match. Opera runs the games (not confirmed). ### Projects to learn from rawgl by cyxx reimplements the Another World engine and reads the 3DO data among others. It was archived on 28 June 2025 ([cyxx/rawgl](https://github.com/cyxx/rawgl)). --- # Seventh generation onward From the Xbox 360 on, every platform ships signed executables in a vendor format such as XEX, SELF or RPX. Most of them are encrypted. Before any analysis, you need a decrypted dump and the right Ghidra loader. This page covers the home consoles. The DS, 3DS, PSP, Vita and Switch are in the [Handhelds](https://tanepiper.github.io/game-frame/handhelds/index.md) section. ## The platforms | Platform | Year | CPU | Executable | Ghidra module or loader | Emulator with a debugger | |---|---|---|---|---|---| | Xbox 360 | 22 November 2005 | Xenon, 3 PowerPC cores at 3.2 GHz, VMX128, big-endian | XEX2, signed, usually encrypted, wraps PE | [XEXLoaderWV](https://github.com/zeroKilo/XEXLoaderWV) | Xenia Canary | | PlayStation 3 | November 2006 | Cell: PPE plus six SPEs for games | SELF and SPRX (EBOOT.BIN) | [GhidraSPU](https://github.com/aerosoul94/GhidraSPU) for SPU; no SELF loader confirmed | RPCS3 | | Wii | 19 November 2006 | Broadway PowerPC at 729 MHz; Starlet ARM9 runs IOS | DOL and REL; discs AES-encrypted | [Ghidra-GameCube-Loader](https://github.com/Cuyler36/Ghidra-GameCube-Loader), paired-singles module | Dolphin | | Wii U | 18 November 2012 | Espresso, 3 PowerPC cores at 1.24 GHz | RPX and RPL ELF variants | [GhidraRPXLoader](https://github.com/cemu-project/GhidraRPXLoader) | Cemu | | PlayStation 4 | 15 November 2013 | 8 Jaguar x86-64 cores at 1.6 GHz | SELF with x86-64 ELF | [GhidraPS4Loader, GhidraOrbis](https://p.codekk.com/detail/64e02e470c2d5a3fbe2aa984) | shadPS4 | | Xbox One | 22 November 2013 | 8 Jaguar cores at 1.75 GHz | Encrypted XVC containers | None known | None known | | PlayStation 5 | 12 November 2020 | Zen 2, 8 cores to 3.5 GHz | | None known | None known | | Xbox Series X and S | 10 November 2020 | Zen 2, 8 cores to 3.8 GHz | | None known | None known | No emulator is known to run commercial Xbox One, PS5 or Series games. The PS4, Xbox One, PS5 and Series are x86-64. Many of their games also have PC releases, which may be easier to study. ## Keys and encrypted executables - PS3 SELF files are signed and encrypted. RPCS3 needs the official firmware and a disc key (not confirmed) ([psdevwiki: SELF](https://www.psdevwiki.com/ps3/SELF_-_SPRX); [RPCS3 quickstart](https://rpcs3.net/quickstart)). - XEX2 files are decrypted with the console-family key ([XEXLoaderWV](https://github.com/zeroKilo/XEXLoaderWV)). - PS4 loaders need a decrypted ELF without the Sony header. - Wii discs are AES-encrypted. Recompilation projects such as Unleashed Recompiled and the GoldenEye 360 recompilation ship no game files and need the user's own copy ([tbreak](https://tbreak.com/goldeneye-007-xbox-360-pc-recompilation/)). GameFrame follows the same rule and keeps all game material out of the repository. The Switch keys and the yuzu settlement are on the [Switch family](https://tanepiper.github.io/game-frame/handhelds/switch-family.md#keys-and-the-yuzu-settlement) page. The 3DS keys are on the [Nintendo DS and 3DS](https://tanepiper.github.io/game-frame/handhelds/ds-and-3ds.md) page. The user decides how to obtain and use their own keys and dumps. ## Emulator status Repositories can disappear, so pin an exact emulator build and keep a local copy. - Dolphin has a full debugger, shared with the GameCube ([Dolphin](https://dolphin-emu.org/)). - RPCS3 has a PPU and SPU debugger (not confirmed) ([RPCS3](https://rpcs3.net/)). - Cemu has been open source since 2022. Its organisation maintains the RPX loader. - Xenia Canary is the active Xbox 360 branch. Secondary coverage reports near-daily commits into July 2026. The original repository gets fewer commits ([tech-insider](https://tech-insider.org/?p=20173); [Snapcraft: xenia-canary](https://snapcraft.io/xenia-canary)). - shadPS4 runs Bloodborne. Reported versions are v0.16.0 in June 2026 and v0.19.0 in October 2026 ([Wikipedia: ShadPS4](https://en.wikipedia.org/wiki/ShadPS4); [itechguides](https://www.itechguides.com/what-the-bloodborne-pc-port-is-and-how-it-works/)). - The handheld emulators (PPSSPP, Vita3K, Azahar and the Switch forks) are on the [Handhelds](https://tanepiper.github.io/game-frame/handhelds/index.md) pages. ## Xbox 360 ### Hardware Xenon has three in-order big-endian PowerPC cores at 3.2 GHz with VMX128 ([Wikipedia: Xenon](https://en.wikipedia.org/wiki/Xenon_(processor))). Executables are XEX2 files that wrap a PE. They are signed and usually encrypted ([Xbox 360 technical specifications](https://en.wikipedia.org/wiki/Xbox_360_technical_specifications)). ### Tools [XEXLoaderWV](https://github.com/zeroKilo/XEXLoaderWV) loads XEX2 files. Neither Ghidra's PowerPC module nor Unicorn supports VMX128. ### Projects to learn from hedge-dev's XenonRecomp (PowerPC to C++) and XenosRecomp (shaders to HLSL) produced Unleashed Recompiled, a native PC port of Sonic Unleashed revealed in March 2025. It ships without game files and was inspired by N64: Recompiled ([Unleashed Recompiled README](https://cdn.jsdelivr.net/gh/hedge-dev/unleashedrecomp@main/README.md); [Time Extension, March 2025](https://www.timeextension.com/news/2025/03/this-new-unofficial-pc-port-of-sonic-unleashed-could-be-the-best-version-of-the-game-yet)). ReXGlue (2026) is an open SDK built on XenonRecomp and Xenia, with early ports such as Blue Dragon and Ninja Gaiden 2. It is "in early development" ([Time Extension, March 2026](https://www.timeextension.com/news/2026/03/i-hold-my-work-to-a-strict-standard-the-driving-force-behind-xbox-360-recomp-tool-rexglue-speaks-out); [Notebookcheck](https://www.notebookcheck.net/ReXGlue-SDK-debut-sparks-Xbox-360-to-PC-ports-for-Blue-Dragon-more.1231442.0.html)). ## PlayStation 3 ### Hardware The Cell pairs one PPE with SPUs. Games get six SPEs. Each SPU has its own 128-bit instruction set, 256 KiB of local store and DMA-only access to main memory ([Wikipedia: Cell](https://en.wikipedia.org/wiki/Cell_(processor)); [psdevwiki](https://www.psdevwiki.com/ps3/)). Executables are SELF and SPRX files. ### Tools [GhidraSPU](https://github.com/aerosoul94/GhidraSPU) adds the SPU. No SELF loader is confirmed. RPCS3 runs the games. ### Projects to learn from PS3Recomp is a PS3 static recompiler. One guide says its first playable games appeared in October 2026 without public downloads. No primary source confirms the claim of "more than three thousand games" ([Generation Amiga, June 2026](https://www.generationamiga.com/2026/06/15/ps3recomp-the-new-tool-bringing-native-playstation-3-ports-closer-to-pc/); [Held Games](https://heldgames.com/tags/ps3recomp)). ### For an exact reimplementation Cell SPU single precision rounds toward zero, treats denormals as zero and has no INF or NaN (not confirmed) ([IBM Cell SDK 3.0](https://arcb.csc.ncsu.edu/~mueller/cluster/ps3/SDK3.0/docs/accessibility/sdkpt/cbet_3codemapeqv.html)). Host IEEE floats give different results from SPU floats. A port of SPU float code needs a software float model. Export the PPE and SPU code separately. ## Wii and Wii U ### Hardware The Wii's Broadway is a PowerPC at 729 MHz. A Starlet ARM9 runs IOS. Executables are DOL and REL files. Discs are AES-encrypted ([Wii](https://en.wikipedia.org/wiki/Wii)). The Wii U's Espresso has three PowerPC cores at 1.24 GHz. Its executables are RPX and RPL ELF variants ([Wii U](https://en.wikipedia.org/wiki/Wii_U)). Broadway and Espresso descend from the GameCube's Gekko ([Wikipedia: Gekko](https://en.wikipedia.org/wiki/Gekko_(processor))). ### Tools The Wii uses the GameCube's tools: [Ghidra-GameCube-Loader](https://github.com/Cuyler36/Ghidra-GameCube-Loader) for DOL and REL, and [ghidra-gekko-broadway-lang](https://github.com/aldelaro5/ghidra-gekko-broadway-lang) for paired singles ([WiiBrew: Ghidra](https://wiibrew.org/wiki/Ghidra)). Dolphin runs the games. [GhidraRPXLoader](https://github.com/cemu-project/GhidraRPXLoader) loads Wii U files. Cemu runs them. ### Projects to learn from Super Mario Galaxy was reported 100% decompiled in late September 2026. It is the first Wii game to be fully decompiled. A PC port called AstroCore is in progress. The project uses decomp-toolkit ([Android Authority](https://androidauthority.com/super-mario-galaxy-decompiled-pc-port-3717193); [GitHub: Petari](https://github.com/Gota7/Petari); [decomp.dev](https://decomp.dev/)). ### For an exact reimplementation Gekko paired-single results depend on the quantised load and store (GQR) setup. Confirm it against Dolphin's code. The PowerPC consoles share tooling, so you can treat big-endian PowerPC as one family. ## PlayStation 4 ### Tools GhidraPS4Loader and GhidraOrbis load PS4 files ([codekk](https://p.codekk.com/detail/64e02e470c2d5a3fbe2aa984)). They need a decrypted ELF without the Sony header. shadPS4 is the emulator ([PlayStation 4 technical specifications](https://en.wikipedia.org/wiki/PlayStation_4_technical_specifications)). ## Matching decompilations not yet done No completed PS4 matching decompilation is known. --- # Tools for console work GameFrame's playbook works for consoles, but some tools change. The tables on this page cover both home consoles and handhelds. The [Handhelds](https://tanepiper.github.io/game-frame/handhelds/index.md) pages add the handheld CPUs and tools. Ghidra needs the right processor module and a loader for the console's file format. Take most ground truth from emulators with debuggers. GameFrame's Unicorn emulator runs some console CPUs. ## Disassembly and decompilation | CPU | Consoles | In Ghidra | |---|---|---| | 6502 and 65C02 | Atari 2600, 5200 and 7800, NES, Lynx, PC Engine (HuC6280 is close) | Built in (6502, 65C02). Extensions add NES ROM loaders and mappers. The `ADC` p-code is not exact (see below) | | HuC6280 | PC Engine, PC Engine CD | 65C02 is close; [Ghidra_HuC6280](https://github.com/TiCoKH/Ghidra_HuC6280) fixes the block-transfer and MMU instructions | | CP1600 | Intellivision | Built in | | 6809 | Vectrex | Built in under MC6800 | | 65C816 | SNES | Through community extensions; IDA and dedicated disassemblers are alternatives | | Z80 and SM83 | Master System, Game Boy, sound CPUs | Z80 built in; the Game Boy's SM83 through extensions | | 68000 | Mega Drive, Neo Geo, arcade boards | Built in (68000). Extensions add Mega Drive ROM loaders | | MIPS (R3000, R4300, R5900) | PlayStation, Nintendo 64, PlayStation 2 | MIPS built in; extensions add PS-X EXE and N64 ROM loaders, and the R5900's extra instructions | | SuperH (SH-2, SH-4) | Saturn, Dreamcast | Built in (SuperH with SH-1, SH-2 and SH-2A; SuperH4) | | PowerPC (Gekko, Broadway) | GameCube, Wii | PowerPC built in; extensions add paired-single instructions and DOL and REL loaders | | ARM7 (ARM and Thumb) | Game Boy Advance, Nintendo DS | Built in | | x86 | Xbox, PS4 onward | Built in; an extension adds the XBE loader | | V30MZ | WonderSwan | x86 real mode, plus [ghidra-wonderswan](https://github.com/JidouGhost-TL/ghidra-wonderswan) | | V810 | Virtual Boy | The built-in V850 differs; [Ghidra_v810_v830](https://github.com/20Enderdude20/Ghidra_v810_v830) adds it | | TLCS-900/H | Neo Geo Pocket | None; [NGDis](https://se.aminet.net/dev/asm/NGDis.readme) disassembles | | Allegrex | PSP | [ghidra-allegrex](https://github.com/kotcrab/ghidra-allegrex) | | Cell SPU | PlayStation 3 | [GhidraSPU](https://github.com/aerosoul94/GhidraSPU) | | Jaguar RISC (Tom and Jerry) | Atari Jaguar | None known | | ARMv3 (ARM60) | 3DO | ARM languages start at v4, so no exact match | Set the memory map before analysis: ROM, RAM and hardware registers at their addresses, and name the hardware registers. Most console communities publish register lists; load them as labels so the decompiled code reads `PPUCTRL` instead of `0x2000`. For 8-bit and 16-bit games written in assembly, the decompiler's C output is of limited use. Work from the disassembly and write your findings as tables and prose. **Ghidra's 6502 `ADC` p-code is not exact.** It has no decimal path. It leaves the incoming carry out of the carry-out test. It copies V from C instead of computing signed overflow ([6502.slaspec lines 127–138](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/6502/data/languages/6502.slaspec#L127-L138)). Do not use p-code emulation as ground truth for 6502 games. Take 6502 ground truth from a tested core: [py65](https://github.com/mnaberez/py65), [mre/mos6502](https://github.com/mre/mos6502), [floooh/chips](https://github.com/floooh/chips) or MAME. Capstone's `next` branch has MOS65XX modes (6502, 65C02, W65C02, 65816) and M680X modes (6809, 6309 and others). It has no Z80, R5900 or Allegrex mode ([capstone.h](https://github.com/capstone-engine/capstone/blob/next/include/capstone/capstone.h)). For 65816 code, the Capstone width mode must follow the M and X flag state in each function. ## Ghidra loaders | Loader | Platform | Format | |---|---|---| | [ghidra-vectrex-loader](https://github.com/nevesnunes/ghidra-vectrex-loader) | Vectrex | Cartridge | | [NTRGhidra](https://github.com/pedro-javierf/NTRGhidra) | Nintendo DS | ARM9 and ARM7 binaries | | [Ghidra-GameCube-Loader](https://github.com/Cuyler36/Ghidra-GameCube-Loader) | GameCube, Wii | DOL and REL | | [XEXLoaderWV](https://github.com/zeroKilo/XEXLoaderWV) | Xbox 360 | XEX2 | | [ghidra-3ds](https://github.com/Stary2001/ghidra-3ds) | Nintendo 3DS | 3DS files; analysis described as "pretty broken" | | [ghidra-3dsx-loader](https://github.com/danylokos/ghidra-3dsx-loader) | Nintendo 3DS | 3DSX homebrew | | [GhidraVitaLoader](https://github.com/xerpi/GhidraVitaLoader), [VitaLoaderRedux](https://github.com/CreepNT/VitaLoaderRedux) | PS Vita | SELF wrapping ARM ELF | | [GhidraRPXLoader](https://github.com/cemu-project/GhidraRPXLoader) | Wii U | RPX and RPL | | [GhidraPS4Loader, GhidraOrbis](https://p.codekk.com/detail/64e02e470c2d5a3fbe2aa984) | PlayStation 4 | Decrypted ELF without the Sony header | | [Ghidra-Switch-Loader](https://github.com/Adubbz/Ghidra-Switch-Loader) | Nintendo Switch | NSO | Check each extension's recent activity before relying on it. Archived modules need a pinned version that matches the Ghidra release, or an active fork. Test an extension with few users on a known binary. ## Emulators with debuggers | Console | Emulators with good debuggers | |---|---| | NES, SNES, Game Boy, PC Engine | Mesen (NES; Mesen 2 adds SNES, Game Boy, PC Engine), bsnes-plus (SNES), BGB and SameBoy (Game Boy) | | Mega Drive | BlastEm, Exodus, Gens KMod | | Game Boy Advance | mGBA, no$gba | | PlayStation | PCSX-Redux, DuckStation, no$psx | | Nintendo 64 | ares, Project64 with its debugger | | Saturn | Mednafen, Kronos | | Dreamcast | Flycast, Redream (limited debugging) | | PlayStation 2 | PCSX2 with its debugger | | GameCube and Wii | Dolphin | | Xbox | xemu | | Atari 2600 | Stella | | Intellivision | jzIntv | | Lynx | Handy, Mednafen | | Jaguar | BigPEmu (closed; debugger unknown), Virtual Jaguar | | 3DO | Opera (not confirmed) | | Virtual Boy, WonderSwan, Neo Geo Pocket, PC Engine CD | Mednafen | | PlayStation 3 | RPCS3 | | Xbox 360 | Xenia Canary | | Nintendo DS | melonDS, DeSmuME, no$gba | | Nintendo 3DS | Azahar | | PSP | PPSSPP | | PS Vita | Vita3K | | Wii U | Cemu | | PlayStation 4 | shadPS4 | | Nintendo Switch | Ryubing, Eden, Citron | | Arcade | MAME | | Several systems, scripted | BizHawk (Lua scripting, input movies, RAM watch, many cores) | Debugger support in the newer emulators varies. Some entries above are not confirmed. The [Seventh generation onward](https://tanepiper.github.io/game-frame/consoles/modern.md#emulator-status) page gives the dated status of the home console emulators. The [Handhelds](https://tanepiper.github.io/game-frame/handhelds/index.md) pages give the handheld emulators, from SameBoy and PokeMini to the Switch forks. The family pages give the rest ([Early consoles](https://tanepiper.github.io/game-frame/consoles/early-consoles.md), [Jaguar and 3DO](https://tanepiper.github.io/game-frame/consoles/32-bit-oddities.md), [CD add-ons](https://tanepiper.github.io/game-frame/consoles/cd-add-ons.md)). What to use them for: - **RAM search and watch** to find variables: search for a value that changes when the player's lives change, then name the address. - **Breakpoints** on reads and writes to find the code that changes a variable. - **Trace logs** of every instruction for a frame, to compare with your port's behaviour. - **Input movies** (BizHawk, FCEUX, Mesen) as recorded per-frame input. They are the console equivalent of demos. Tool-assisted speedrun movies cover whole games. - **Scripting** (Lua in Mesen, BizHawk, FCEUX) to dump RAM every frame and to drive the game. This is the console equivalent of GameFrame's headless runs. ## GameFrame's Unicorn emulator Unicorn 2 emulates ARM, ARM64, M68K, MIPS (32 and 64-bit), PowerPC, RISC-V, s390x, SPARC, TriCore and x86. It has no 6502, 65816, Z80 or SuperH. It has no R5900, no paired singles and no VMX128. Its MIPS likely does not model the R3000A load delay slot; this is untested. For the CPUs it supports, you can run one original function headless, as `pe_emu.py` does for Windows code: 1. Load the ROM or the executable's code segments at their addresses. 2. Map RAM, and fill it from a RAM dump taken in an emulator at the right moment. 3. Set the registers, call the function, read the results. 4. Stub hardware registers the function touches. This gives bit-exact ground truth in tests, as for PC games. Unicorn does not emulate coprocessors such as the PlayStation's GTE or the PS2's vector units; take ground truth for those from a full emulator. For CPUs Unicorn lacks, MAME's m6809, g65816, tms9900, cp1610, jaguar and m68000 core files carry `license:BSD-3-Clause` headers, though MAME as a whole is GPL-2.0. For the Z80, [iz80](https://github.com/ivanizag/iz80) (BSD-3) passes ZEXALL. Otherwise use a small emulator core in Python or Rust for that CPU (6502 and Z80 cores are short), or script a full emulator to run the function and dump the result. ## Splitting and building tools Projects that decompile console games built tools that also help a reimplementation: | Tool | What it does | |---|---| | splat | Splits a ROM or executable (N64, PlayStation, PS2) into code, data and assets by a configuration file, and disassembles the code | | asm-differ | Compares the assembly of two builds function by function | | decomp-toolkit (dtk) and objdiff | Split and compare GameCube and Wii programs | | decomp.me | A website for comparing the output of the original compilers with candidate C code | | m2c | Turns MIPS and PowerPC assembly into first-draft C | See [Matching decompilation](https://tanepiper.github.io/game-frame/consoles/matching-decomp.md) for how they fit together. --- # Routes to a port There are four ways to bring a console game to other systems. They differ in effort, in how close the result is to the original, and in what the project may publish. Choose one at the start and write it in the project's `AGENTS.md`. | Route | What it is | Examples | Effort | What can be published | |---|---|---|---|---| | Emulation | Run the original code on an emulated console | every emulator | Low for one game, once an emulator exists | The emulator; the player brings the game | | Static recompilation | Translate the original machine code into C or another language automatically, and link it with a reimplementation of the hardware | N64Recomp (Zelda 64: Recompiled) | Medium; most of the work is in the hardware layer | The recompiler and the hardware layer; the player's ROM is translated on their machine | | Matching decompilation, then a port | Write C that compiles to the identical original binary, then build it for a new system | the SM64 and Ocarina of Time decompilations and their PC ports | Very high | Contested: the C is derived from the original program ([Legal](https://tanepiper.github.io/game-frame/legal.md)) | | Reimplementation | Write new code from documented behaviour, checked against the original | most projects in GameFrame's case studies | High | The new code; the player brings the game's data | GameFrame is designed for reimplementation. The other routes share most of its steps (finding functions, capturing ground truth, replacing hardware). GameFrame's toolkit helps with them too. ## Ask your agent Choose a route to a port. Copy this prompt into your coding agent: ```text In my GameFrame project, help me choose how to port this console game. 1. Read docs/consoles/porting.md and docs/consoles/matching-decomp.md. 2. Tell me what each route would take for this game: reimplementation, matching decompilation, static recompilation or emulation. 3. List for each route what we could publish and what the player must provide. 4. Wait for my choice. Then write it into the project's AGENTS.md. ``` ## Replacing the hardware Every route needs code on the new system that replaces the console's hardware. Split the work by device. ### The frame and the clock - The game's logic runs once per vertical blank. Keep one logic update per frame, at the original's rate (60.0988 Hz for an NTSC NES, 59.94 Hz or 50 Hz for most others). Find the exact rate for your console and region. - If the port runs at a different display rate, run the logic at the original rate and show the latest frame, or interpolate only the picture. Never change how often the logic runs. - Lag frames happen when the original took more than one frame for its work. They change the game's timing. Decide whether the reimplementation reproduces them. A port that must match speedrun behaviour needs them. Most ports do not. Record the decision. - PAL versions often run slower, or change speeds and timers to compensate. Pick one region as the reference. ### Input - The controller is read once a frame into RAM, often with a "pressed this frame" mask computed from the previous frame. Log that RAM as the input record. Port the read, and feed it from the new system's controllers. - Some games read the controller more than once a frame, or in an interrupt. Find every read. - Input movies from emulators are per-frame input. Use them to drive both the original and the port for comparisons. ### Video | Console video | How to port it | |---|---| | Tiles, sprites and layers (NES to GBA, Mega Drive, Saturn VDP2) | Reimplement the video chip's rendering: read the game's tile, map, sprite and palette memory each frame and draw it. Exact output is possible, including sprite limits and priorities | | Frame buffer with triangles (PlayStation, Saturn VDP1) | Render the game's draw commands with a modern API. Decide whether to keep the original's look (no perspective correction, wobbling vertices from integer coordinates) or improve it | | Display lists for a programmable GPU (N64, later consoles) | Interpret the display lists the game builds, and translate them to a modern API | - Raster effects, where the game changes video registers during the frame, need the scanline timing. Find the interrupts that fire on a line (the SNES's H-IRQ, the Game Boy's LYC) and apply their register changes at the same line in the port. - Keep the original's resolution and aspect ratio as the reference. Widescreen and higher resolutions are deviations; record them. ### Sound - Sound often runs on a separate CPU with its own program (the SNES's SPC700, the Mega Drive's Z80, the PS2's IOP). The game includes that program. Either emulate the sound CPU and its chip with the original program, or trace and port the sound program. - Emulating the sound CPU and chip is usually less work and gives exact sound. The program and samples still come from the player's copy. - Sound chips (YM2612, the NES APU, the S-DSP) have accurate emulators. Use one rather than writing your own. ### Saves - Battery-backed RAM (SRAM) in cartridges: map it to a file. Keep the same layout, so saves move between the original (through an emulator) and the port. - Memory cards (PlayStation, PS2, GameCube): the game uses a card API in its libraries. Port the API calls to files. Keep the save data's format. - Passwords: the password encodes the game's state. Port the encoding exactly; players still have old passwords. ### Libraries Games from the 32-bit generation on were linked with the console maker's libraries (Sony's libraries for the PlayStation, Nintendo's SDK for the N64 and GameCube). Identify them by signatures and label them. Port the library functions the game calls. Do not copy the libraries into the port. ## Checking the port Use the same checks as for PC games ([Running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md)): 1. Play an input movie in the original's emulator, and dump RAM every frame. 2. Play the same movie in the port, and dump the same variables. 3. Compare frame by frame. Stop at the first difference. 4. Compare pictures of the same frames. Tile-based video can be compared pixel by pixel. --- # Matching decompilation A matching decompilation is C or C++ source code that, compiled with the original compiler and settings, produces a program identical byte for byte to the original. Community projects have done this for many console games, among them Super Mario 64, The Legend of Zelda: Ocarina of Time and Majora's Mask, and Paper Mario. For games written in assembly, the same idea gives a matching disassembly that assembles to the identical ROM, as pret's Pokémon Red does. This page describes how these projects work and when to choose this route. ## How it works 1. **Identify the compiler.** Find the exact compiler and version the game was built with: SGI IDO 5.3 or 7.1 for many N64 games, GCC 2.x versions from the PsyQ SDK for the PlayStation, Metrowerks CodeWarrior versions for the GameCube, Microsoft's for the Xbox. Compiler signatures in the code, library versions and leftover strings help to identify it. 2. **Split the program.** Tools (splat, decomp-toolkit) split the ROM or executable into files of code, data and assets, following a configuration of addresses. The project builds them back into an identical program from disassembly at first. 3. **Decompile one function at a time.** For each function, write C that compiles to the same instructions. Tools give a first draft (m2c), and decomp.me lets many people try candidate C against the original compiler in a browser. asm-differ and objdiff show where the output still differs. 4. **Replace the assembly.** When a function matches, its C replaces its assembly in the build. The build still produces the identical program, which proves the match. 5. **Name and document.** Matching code starts with generic names. The project then names variables, structures and functions, as in any reverse engineering. ## What it gives - A proof of behaviour. The match shows that the C behaves exactly like the original, including every bug. - A base for ports. Once the code is C, it can be built for other systems with a new hardware layer (as the SM64 PC port did). - A complete map of the program. Later projects on the same game can use it. ## What it costs - Matching is slow. Compilers of the time are sensitive to the order of statements, the types of temporary variables and the use of registers. Some functions take days to match. - It needs the original compiler, which often runs only in an emulator or an old system. - The resulting source is derived from the original program. Projects have different views of what may be published. The law differs by country. The user decides whether to publish such code ([Legal](https://tanepiper.github.io/game-frame/legal.md)). ## When to choose it | Situation | Route | |---|---| | The game is in C, the compiler is known and available, and a community already started a decompilation | Contribute to or use the decompilation's documents | | You need a port that is exactly the original, bugs included, and can accept the legal position | Matching decompilation | | The game is mostly hand-written assembly (most 8-bit and 16-bit games) | Matching proves less when the source was assembly; disassemble, document and reimplement | | You want new code you can publish freely | Reimplementation, using decompilation projects' documents as references | ## How GameFrame fits in GameFrame's playbook applies to every route. Its toolkit helps a matching project with tasks the matching tools do not cover: - `decomp_query.py` and the Ghidra export for searching the whole program as text. - Emulated ground truth (`re-emulate`) to check a ported function's results on many inputs before it matches byte for byte. - `seqdiff.py` to compare random draws or events between the original and a build. - The evidence labels and documents, for knowledge that the C code alone does not record. --- # Handhelds This section covers the handheld game machines, from the mask-ROM LCD games of 1979 to the PC handhelds of today. Each page covers one family. The home consoles are in [Console games](https://tanepiper.github.io/game-frame/consoles/index.md). The arcade boards are in [Arcade](https://tanepiper.github.io/game-frame/arcade/index.md). The handhelds fall into four groups. In the first, a 4-bit microcontroller with a segment LCD holds the program inside the CPU chip. The second uses 8-bit cartridge systems. The third runs ARM code on an operating system. The fourth uses modern ARM64 and x86-64 machines. In every group after the first, games come on removable media. ## Across all handhelds - [Handheld practices](https://tanepiper.github.io/game-frame/handhelds/practices.md): battery saves, real-time clocks in save files, link cables and infrared, sensors, LCD behaviour and the outside inputs a test records. ## Handhelds by family - [LCD games and Game & Watch](https://tanepiper.github.io/game-frame/handhelds/lcd-games.md): Game & Watch, Tiger LCD games and the R-Zone, Elektronika, and the Microvision. Mask ROM in the MCU, MAME sets and lit segments. - [Game Boy family](https://tanepiper.github.io/game-frame/handhelds/game-boy.md): the Game Boy, Pocket, Light, Color, Super Game Boy and Game Boy Advance. CGB modes, SGB timing, GhidraBoy, SameBoy and pret's matching builds. - [Sega, Atari and SNK handhelds](https://tanepiper.github.io/game-frame/handhelds/sega-atari-snk.md): the Game Gear, Lynx and Neo Geo Pocket. - [WonderSwan](https://tanepiper.github.io/game-frame/handhelds/wonderswan.md): the WonderSwan, Color and SwanCrystal on the V30MZ. - [Virtual Boy and Pokémon mini](https://tanepiper.github.io/game-frame/handhelds/nintendo-oddities.md): the V810 extension, the S1C88 and PokeMini traces. - [Game.com](https://tanepiper.github.io/game-frame/handhelds/tiger-game-com.md): Tiger's Game.com and Pocket Pro on the SM8521. - [Nintendo DS and 3DS](https://tanepiper.github.io/game-frame/handhelds/ds-and-3ds.md): the ARM9 and ARM7 split, NTRGhidra, encrypted 3DS containers and Azahar. - [PSP and PS Vita](https://tanepiper.github.io/game-frame/handhelds/psp-and-vita.md): the Allegrex extension, encrypted executables, PPSSPP and the Vita loaders. - [Switch family](https://tanepiper.github.io/game-frame/handhelds/switch-family.md): the Switch, Switch Lite and Switch 2. NSO in NCA, keys, Game-Key Cards and emulator status. - [ARM-OS handhelds](https://tanepiper.github.io/game-frame/handhelds/arm-os-handhelds.md): the N-Gage, Gizmondo, GP32 and GP2X, with OS stubs for Unicorn. - [PC handhelds](https://tanepiper.github.io/game-frame/handhelds/pc-handhelds.md): the Steam Deck and other PC handhelds on the PE path. - [Phones](https://tanepiper.github.io/game-frame/handhelds/phones.md): J2ME and BREW games and the Zeebo. ## How handheld work differs from console work | Topic | Home consoles | Handhelds | |---|---|---| | The program | A ROM or a disc image | The same for cartridge handhelds; mask ROM inside the CPU on LCD games; files on an OS from the N-Gage onward | | The CPU | Mostly CPUs with a Ghidra module | Several with no built-in Ghidra module and no Unicorn backend, such as the SM5xx, TMS1100, SM83, TLCS-900/H, S1C88 and SM8521 | | The output | A frame on a TV | Lit LCD segments on LCD games; small passive or TFT screens; up to 120 Hz with VRR on the Switch 2 | | Saves | Battery RAM, memory cards | Battery RAM, often with a real-time clock whose state is stored in the save | | Outside inputs | Controllers | Controllers plus link cables, infrared, shock and touch sensors, GPS and cameras | --- # Handheld practices Handhelds keep state in battery-backed RAM and real-time clocks. They connect to other units over link cables and infrared. Some read sensors. **A reimplementation must reproduce these parts as well as the CPU.** This page covers what every handheld project records beside the CPU state. Each family page covers its own hardware. ## Saves and real-time clocks The Game Boy's MBC3 mapper holds up to 2 MB of ROM, RAM and a real-time clock. The clock needs a 32.768 kHz crystal and a battery ([Pan Docs: MBC3](https://gbdev.io/pandocs/MBC3.html)). | Action | Address | Effect | |---|---|---| | Write `$08` to `$0C` | 4000 to 5FFF | Selects the RTC registers: seconds, minutes, hours, the low byte of the day counter, and DH | | Write `$00` then `$01` | 6000 to 7FFF | Latches the time | DH holds day bit 8, a halt flag (bit 6) and a carry flag (bit 7). The day counter is 9 bits (0 to 511). Carry stays set until software clears it. Separate RTC accesses need about 4 µs between them. MBC30 (4 MiB ROM, 64 KiB RAM, with a timer) shipped only in the Japanese *Pocket Monsters: Crystal Version*. Other regions used MBC3 ([Pan Docs: MBC3](https://gbdev.io/pandocs/MBC3.html)). **A save file with an RTC needs the clock state alongside the RAM.** The clock state is the latched values, the halt flag and the carry flag. Tests can then reproduce time-based events, such as day and night or the daily events in the Pokémon Gold, Silver and Crystal games. The Pokémon mini also has an RTC. It adds a shock sensor, IR for up to six players and rumble ([Wikipedia: Pokémon mini](https://en.wikipedia.org/wiki/Pok%C3%A9mon_Mini)). ## Link cables and infrared Link cables and infrared are more outside inputs. - GBC infrared runs up to 9.5 kbit/s ([Wikipedia: Game Boy Color](https://en.wikipedia.org/wiki/Game_Boy_Color)). - SGB games can read four joypads and run code on the SNES. Their timing differs between NTSC, PAL and the SGB2. Games that detect the SGB can retune their sound ([Pan Docs: SGB Functions](https://gbdev.io/pandocs/SGB_Functions.html)). - The Game.com has a modem cartridge ([Wikipedia: Game.com](https://en.wikipedia.org/wiki/Game.com)). - BREW signs each downloaded game for one handset, so the game cannot be backed up ([GameTechWiki: BREW](https://emulation.gametechwiki.com/index.php/BREW)). Link-cable protocols are not covered here. Two examples are GBC serial at double speed and the Pokémon trade protocol. ## Sensors and other inputs - The Game.com has a touchscreen ([Wikipedia: Game.com](https://en.wikipedia.org/wiki/Game.com)). - The Gizmondo has GPS, GPRS and a camera ([Wikipedia: Gizmondo](https://en.wikipedia.org/wiki/Gizmondo)). - Switch 2 Game-Key Cards must stay inserted to launch. The Joy-Con 2 adds mouse input ([Wikipedia: Nintendo Switch 2](https://en.wikipedia.org/wiki/Nintendo_Switch_2)). - Accelerometer cartridges such as Kirby Tilt 'n' Tumble on the GBC are commonly cited but not verified. Add each of these to the list of outside inputs that a test records and replays ([Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md)). ## LCD behaviour What a picture test compares depends on the LCD. Game & Watch graphics are fixed LCD segments. MAME draws them from SVG artwork ([Wikipedia: Game & Watch](https://en.wikipedia.org/wiki/Game_%26_Watch); [MAME hh_sm510.cpp](https://raw.githubusercontent.com/mamedev/mame/master/src/mame/handheld/hh_sm510.cpp)). For segment games the parity test compares lit segments per frame ([LCD games](https://tanepiper.github.io/game-frame/handhelds/lcd-games.md)). Some handheld games are reported to flicker sprites. They rely on slow LCD response (ghosting) to blend frames. Which games do this is not confirmed. For such a game, compare the raw output of each frame. Do any blending only in the presentation layer. --- # LCD games and Game & Watch On Game & Watch, Tiger LCD games and the Microvision, the program is mask ROM inside a 4-bit microcontroller. It cannot be read through a cartridge connector the usual way. It is read from a decap (a die photo) or with a special electronic dump. In practice, projects take it from the MAME set. A test compares the set of lit LCD segments. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | Game & Watch | 28 April 1980 to 1991 | Sharp SM5A, SM510 to SM512, SM530; 32.768 kHz crystal | Mask ROM in the MCU | None for the SM5xx | MAME (hh_sm510.cpp) | | Tiger LCD games, R-Zone | Not covered here | Sharp SM5xx family, by MAME's hh_sm510.cpp driver | Mask ROM in the MCU | None for the SM5xx | MAME (hh_sm510.cpp, rzone.cpp) | | Elektronika LCD games | Not covered here | Clones of the Sharp chips | Mask ROM in the MCU | None | MAME (hh_sm510.cpp) | | Microvision | November 1979 to 1981 | Intel 8021 or TI TMS1100 in each cartridge, at 100 kHz | Cartridge holding its own CPU | 8048 may decode the 8021 (not confirmed); none for the TMS1100 | Not covered here | Unicorn supports none of these CPUs. ## Game & Watch ### Hardware Nintendo's Game & Watch first went on sale on 28 April 1980. Original production ran until 1991. It has about 60 games in 11 series. The Multi Screen series uses two LCD panels. The CPU is a 4-bit Sharp SM5xx-family microcontroller with ROM, RAM and the LCD driver on the chip. The SM5A and SM510 to SM512 are rated for a 61 µs cycle time and use a 32.768 kHz crystal. Wikipedia equates that to about 16.384 kHz (not confirmed). RAM is 80 to 128 nibbles. ROM runs from 1,792 bytes on the SM5A to 4,032 bytes on the SM511 and SM512. Those two also add a melody ROM. Wikipedia's figures for that melody ROM disagree with each other. The text gives 256 6-bit words. The chip table gives 192 bytes. The display is a segmented LCD. Every possible image is a fixed printed segment ([Wikipedia: Game & Watch](https://en.wikipedia.org/wiki/Game_%26_Watch)). The 2020 *Game & Watch: Super Mario Bros.* is a different machine. It has a colour backlit LCD, USB-C and a Cortex-M7 (STMicroelectronics STM32H7B0VB) with over 1 MB of RAM and flash ([Wikipedia: Game & Watch](https://en.wikipedia.org/wiki/Game_%26_Watch)). ### Tools MAME's driver covers games on the SM5A, SM510, SM511, SM512 and SM530. The SM5A and SM510 are the most common ([MAME hh_sm510.cpp](https://raw.githubusercontent.com/mamedev/mame/master/src/mame/handheld/hh_sm510.cpp)). MAME notes that an accurate electronic method now dumps the SM511 and SM512 melody ROM. Some melody ROMs are still marked BAD_DUMP pending decap checks. Many program entries are marked "(no decap)". In MAME, the LCD screen graphics come from an SVG file inside the driver. The driver credits hap and Henrik Algestam, with thanks to Sean Riddle, Igor, Lee Robson and Milan Galcik ([MAME hh_sm510.cpp](https://raw.githubusercontent.com/mamedev/mame/master/src/mame/handheld/hh_sm510.cpp)). MAME release notes credit Sean Riddle on Game & Watch drivers such as Donkey Kong Jr. (new wide screen), Mario Bros. and Ball ([MAME forum](https://forum.mamedev.org/viewtopic.php?p=732); [MAME changelog via Softpedia](https://games.softpedia.com/progChangelog/MAME-Changelog-40.html)). A MiSTer FPGA Game & Watch core can use the MAME ROMs and artwork. So far only SM510 games convert ([MiSTer forum](https://misterfpga.org/viewtopic.php?p=53100)). No Ghidra module exists for the SM5xx. MAME's own disassemblers cover it, through its debugger or its `unidasm` tool. ### Projects to learn from Sean Riddle's decap index has die-shot pages for the Sharp SM510, SM511, SM512 and SM530. His site documents his decapping method, with a microscope camera, an automated X-Y table and stitched die images ([Sean Riddle: Chip Decap Info](https://www.seanriddle.com/decap.html)). How mask-ROM bits are read from a die photo is not described here. ### For an exact reimplementation **Compare the lit segments in each frame.** The MCU's LCD RAM holds them. A pixel comparison against MAME's SVG gives no extra information. MAME's debugger with hap's SM5xx cores is the only known way to get ground truth. ## Tiger LCD games and Elektronika ### Tools MAME's hh_sm510.cpp also covers Tiger's LCD games. The Tiger R-Zone is a child driver (rzone.cpp). The same file covers the Soviet Elektronika games, whose MCUs cloned the Sharp chips ([MAME hh_sm510.cpp](https://raw.githubusercontent.com/mamedev/mame/master/src/mame/handheld/hh_sm510.cpp)). A MAME changelog notes Konami, Tiger and other hh_sm510 games being added to the Experimental category ([MAME forum](https://forum.mamedev.org/viewtopic.php?p=1346)). Mattel's LCD games of 1976 to 1977 are not covered here. Their CPUs are not confirmed. ## Microvision ### Hardware Milton Bradley's Microvision came out in November 1979. It was discontinued in 1981. **The base unit has no CPU.** Each cartridge holds its own processor with the program masked into it. That processor is an Intel 8021 with 1K of ROM or a TI TMS1100 with 2K, clocked at 100 kHz. The display is a 16 by 16 pixel LCD. Input is a 12-button keypad and one paddle ([Wikipedia: Microvision](https://en.wikipedia.org/wiki/Microvision)). Many surviving units have LCD "screen rot". Early cartridges had no ESD protection, so a static discharge can destroy the CPU and its program. Keypad overlays tear ([Wikipedia: Microvision](https://en.wikipedia.org/wiki/Microvision)). ### Tools No Ghidra module exists for the TMS1100. The Intel 8021 is a reduced member of the MCS-48 family, so Ghidra's 8048 module may decode most of it (not confirmed). MAME's disassemblers cover the rest. ### Projects to learn from Sean Riddle's decap index has die-shot pages for the Microvision LCD controller (SCUS0488), the TMS1000 and the TMS1100 ([Sean Riddle: Chip Decap Info](https://www.seanriddle.com/decap.html)). ### For an exact reimplementation Take the program from a decap or a MAME set. The cartridge connector does not give a normal ROM dump, because the program is inside the CPU chip ([Handheld practices](https://tanepiper.github.io/game-frame/handhelds/practices.md#lcd-behaviour)). --- # Game Boy family The Game Boy family has the most mature tool chain of any handheld. pret's builds produce ROMs with the same hashes as the originals. Few other platforms have projects like them. The SM83 has no built-in Ghidra module and no Unicorn backend, so take ground truth from SameBoy or another emulator. Super Game Boy games can also contain SNES code. Their timing depends on the region. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | Game Boy (DMG) | 1989 | Sharp SM83 (similar to Z80 and 8080) | Cartridge, banked by a mapper | [GhidraBoy](https://github.com/Gekkio/GhidraBoy) (archived), [ghidra-sm83](https://github.com/minorum/ghidra-sm83) fork | SameBoy, Emulicious, BGB, Mesen 2 | | Game Boy Pocket and Light (MGB) | 1996 and 1998 | Sharp SM83 | Cartridge | As the DMG | SameBoy | | Game Boy Color (CGB) | 21 October 1998 (JP) | Sharp SM83 at 4.194304 MHz; 8.388608 MHz in double speed | Cartridge; grey, black "dual mode" and clear CGB-only carts | As the DMG | SameBoy, Emulicious | | Super Game Boy, SGB2 | Not covered here | SM83 in a SNES cartridge, with SNES-side 65C816 code | SNES cartridge running Game Boy carts | As the DMG; SNES side through 65C816 extensions | SameBoy (high level) | | Game Boy Advance | 2001 | ARM7TDMI plus SM83 | Cartridge | ARM built in; [gba-ghidra-loader](https://github.com/pudii/gba-ghidra-loader) | mGBA, no$gba | Community documents are Pan Docs for the Game Boy and GBATEK for the GBA and DS. ## Game Boy ### Hardware The Sharp SM83 is similar to the Z80 and the 8080. The video has tiles, sprites, one background and a window. Sound has four channels. Cartridges hold more than the CPU can address, so a mapper chip switches banks. Name functions as `bank:address` ([Console platforms](https://tanepiper.github.io/game-frame/consoles/platforms.md)). SameBoy treats the **Game Boy Pocket (1996)** and **Game Boy Light (1998)** as one model, MGB, separate from the DMG ([SameBoy features](https://sameboy.github.io/features/)). Release dates and hardware differences for the Pocket and Light are not covered here. ### Tools GhidraBoy (Gekkio, Apache-2.0) adds SM83 SLEIGH support and a Game Boy ROM loader. The loader handles banked and unbanked ROMs and boot ROMs. It sets up memory blocks, hardware register symbols and header data types. It is marked "very experimental" with no compatibility guarantees. It supports Ghidra 11.1 to 11.4.2. Its decompiler output is hard to read around rotates and `JP HL` jump tables ([GhidraBoy README](https://raw.githubusercontent.com/Gekkio/GhidraBoy/main/README.markdown)). The repository is archived. [minorum/ghidra-sm83](https://github.com/minorum/ghidra-sm83) is a fork ([Gekkio/GhidraBoy](https://github.com/Gekkio/GhidraBoy)). [yaxpeax-sm83](https://github.com/iximeow/yaxpeax-sm83) is a Rust SM83 disassembler. Unicorn has no SM83 backend, so take ground truth from SameBoy or another emulator ([Unicorn Engine](https://www.unicorn-engine.org/)). SameBoy emulates the DMG, MGB, CGB, the AGB in GBC mode, and the SGB and SGB2 at high level. It passes all of mooneye-gb's test suite plus Wilbert Pol's and blargg's tests. Its LCD timing is T-cycle accurate. It claims over 99.9% success across about 2,800 games. Its debugger has conditional breakpoints and watchpoints, call stack tracing, a disassembler with symbol support, an expression evaluator and back-stepping. It emulates the RTC. Link cable and infrared work only in its macOS (Cocoa) front end ([SameBoy features](https://sameboy.github.io/features/)). Emulicious covers the Game Boy, Game Boy Color, Master System, Game Gear and MSX. It has a profiler, tracer, memory tracer, source-level debugging and a VS Code debug adapter. Its accuracy is checked against the gbdev test ROM corpus (GBEmulatorShootout) ([Emulicious](https://emulicious.net/)). BGB also has a good debugger. Mesen 2 adds the Game Boy to Mesen. Gambatte and mGBA debugger features are not covered here. ## Game Boy Color ### Hardware The Game Boy Color came out in Japan on 21 October 1998. It reached North America on 18 November 1998 and Europe on 23 November 1998. Its Sharp SM83 runs at 4.194304 MHz. A double-speed mode runs it at 8.388608 MHz. It has 32 KiB of WRAM and 16 KiB of VRAM. The 160 by 144 reflective TFT has no backlight. It shows up to 56 of 32,768 colours at once. The infrared port runs up to 9.5 kbit/s at under 2 m within 45°. Few games used it. The GBA has no infrared port. The GBC is slightly thicker and taller than the Pocket ([Wikipedia: Game Boy Color](https://en.wikipedia.org/wiki/Game_Boy_Color)). Pan Docs gives the speeds in M-cycles as about 1.05 MHz and 2.10 MHz ([Pan Docs: CGB Registers](https://gbdev.io/pandocs/CGB_Registers.html)). Grey cartridges are DMG games shown with a limited palette. Black "dual mode" carts run on both systems. Clear carts are CGB-only ([Wikipedia: Game Boy Color](https://en.wikipedia.org/wiki/Game_Boy_Color)). ### The CGB registers These registers control the CGB features a game uses ([Pan Docs: CGB Registers](https://gbdev.io/pandocs/CGB_Registers.html)). | Register | Address | What it does | |---|---|---| | Header byte | $0143 | `$80`: supports both systems. `$C0`: CGB only. Any other value runs the cart in the monochrome "Non CGB" mode | | KEY1 | $FF4D | Speed switch. The game sets bit 0 and executes `stop`. Bit 7 reads the current speed. The switch pauses the CPU for 2050 M-cycles | | VBK | $FF4F | Selects VRAM bank 0 or 1. Bank 1 holds extra tiles and tile-map attributes | | SVBK | $FF70 | Maps WRAM banks 1 to 7 at D000 to DFFF. Writing 0 selects bank 1 | | HDMA1 to HDMA5 | $FF51 to $FF55 | VRAM DMA of $10 to $800 bytes. With bit 7 of HDMA5 clear, general-purpose DMA runs at once and halts the CPU. With bit 7 set, HBlank DMA copies $10 bytes per HBlank, at about 8 µs per block | | KEY0 | $FF4C | Selects DMG compatibility mode. Only the boot ROM writes it before it locks | | OPRI | $FF6C | Selects CGB or DMG object priority | | RP | $FF56 | Infrared. Bit 0 drives the LED. Bit 1 reads the receiver. Bits 6 and 7 must be set to enable reading | **Double speed speeds up the CPU, timers, serial port and OAM DMA.** The LCD, VRAM DMA and sound keep normal timing. ### Projects to learn from pret/pokecrystal builds Pokémon Crystal (UE) v1.0 and v1.1 and the Australian release. Each matches a listed SHA-1. It also builds two later rebuild files and a patch. It pins its RGBDS version in `.rgbds-version`. Other pret projects are listed at pret.github.io ([pret/pokecrystal](https://github.com/pret/pokecrystal)). A rebuild with the same hash proves that the disassembly is complete. A GBC reimplementation can use such a project as ground truth. ### For an exact reimplementation A dual-mode game has a DMG code path and a CGB code path. A port needs both. Treat the mode the boot ROM picks as an input. For a cartridge with an MBC3 clock, the save must include the clock state ([Handheld practices](https://tanepiper.github.io/game-frame/handhelds/practices.md)). ## Super Game Boy ### Hardware The Super Game Boy is a SNES cartridge that holds a Game Boy chip, an ICD2 bridge and SNES-side system software. It shows the 160 by 144 screen inside a 256 by 224 SNES picture. The border uses 4 bpp tiles in 16 colours per tile. Palettes can be set per cell of a 20 by 18 grid. A game can add up to 24 SNES sprites, read four joypads separately and play SNES sound effects. Games can also upload code or data into SNES memory and run it on the SNES CPU ([Pan Docs: SGB Functions](https://gbdev.io/pandocs/SGB_Functions.html)). **The SGB clock comes from the SNES, so Game Boy timing runs fast.** It runs about 2.41% fast on NTSC (4.2955 MHz) and 1.48% fast on PAL (4.2563 MHz). The SGB2 has its own 20.972 MHz crystal and runs at the correct speed ([Pan Docs: SGB Functions](https://gbdev.io/pandocs/SGB_Functions.html)). ### For an exact reimplementation An SGB-enhanced game contains 65C816 code as well as SM83 code. Its timing depends on which SGB and which region it ran on. Trace both CPUs' code. ## Game Boy Advance ### Hardware The GBA has an ARM7TDMI plus an SM83. The video has tile modes and bitmap modes. Sound has the four Game Boy channels and two sample channels ([Copetti: Game Boy Advance](https://www.copetti.org/writings/consoles/game-boy-advance/)). ### Tools Ghidra builds in the ARM7 (ARM and Thumb). [gba-ghidra-loader](https://github.com/pudii/gba-ghidra-loader) loads GBA ROMs. Unicorn's ARM support can run single functions ([Tools for console work](https://tanepiper.github.io/game-frame/consoles/tools.md)). mGBA and no$gba run the games. mGBA's debugger features are not covered here. --- # Sega, Atari and SNK handhelds The Game Gear, Lynx and Neo Geo Pocket are 8-bit and 16-bit cartridge handhelds. The Game Gear and Lynx use CPUs that Ghidra builds in. The Neo Geo Pocket's TLCS-900/H has no Ghidra module. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | Sega Game Gear | 6 October 1990 (JP) | Z80 at 3.58 MHz | Cartridge | Z80 built in | MAME, Emulicious | | Atari Lynx | September 1989 | 65SC02 at 4 MHz in Mikey | Cartridge with an encrypted loader | 65C02 built in | Handy, Mednafen (lineage not confirmed) | | Neo Geo Pocket and Color | October 1998 and March 1999 (JP) | TLCS-900/H at 6.144 MHz; Z80 for sound | Cartridge | No TLCS-900 module; [NGDis](https://se.aminet.net/dev/asm/NGDis.readme) disassembles | MAME, Mednafen | Mednafen covers the Neo Geo Pocket with a debugger in its SDL front end ([Mednafen docs](https://mednafen.github.io/documentation/)). ## Sega Game Gear ### Hardware The Z80 runs at 3.58 MHz. The video chip is the Master System VDP, with 160x144 pixels visible. Many Game Gear games are Master System ports ([Game Gear](https://en.wikipedia.org/wiki/Game_Gear)). ### Tools Ghidra builds in the Z80. Unicorn and Capstone have no Z80, so take ground truth from a small core such as [iz80](https://github.com/ivanizag/iz80) or from MAME. Emulicious also covers the Game Gear, with a profiler, tracer and source-level debugging ([Emulicious](https://emulicious.net/)). ## Atari Lynx ### Hardware The 65SC02 runs at 4 MHz inside the Mikey chip. The Suzy blitter scales sprites and has a maths unit ([Atari Lynx](https://en.wikipedia.org/wiki/Atari_Lynx)). ### Tools Load the code with Ghidra's 65C02 language. Ghidra's 6502 `ADC` p-code is not exact, so take ground truth from a tested core ([Tools for console work](https://tanepiper.github.io/game-frame/consoles/tools.md#disassembly-and-decompilation)). ### For an exact reimplementation The cartridge loader is encrypted. An emulator needs the Lynx boot ROM to run it. ## Neo Geo Pocket and Neo Geo Pocket Color ### Hardware The main CPU is a TLCS-900/H at 6.144 MHz, with a Z80 for sound. The screen is 160x152 ([Neo Geo Pocket Color](https://en.wikipedia.org/wiki/Neo_Geo_Pocket_Color)). ### Tools Ghidra has no TLCS-900 module. [NGDis](https://se.aminet.net/dev/asm/NGDis.readme) disassembles the code. MAME and Mednafen run the games. --- # WonderSwan Bandai's WonderSwan family runs a CPU compatible with 16-bit x86. Ghidra's x86 real-mode language reads it. Unicorn runs x86 16-bit code, so GameFrame's Unicorn emulator can run its functions. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | WonderSwan, Color, SwanCrystal | March 1999 to 2002 (JP) | NEC V30MZ at 3.072 MHz | Cartridge | x86 real mode; [ghidra-wonderswan](https://github.com/JidouGhost-TL/ghidra-wonderswan) | Mednafen, Mesen 2 | Mednafen covers the WonderSwan with a debugger in its SDL front end. Mesen 2 covers the WonderSwan (system list not confirmed) ([Mednafen docs](https://mednafen.github.io/documentation/); [Mesen](https://www.mesen.ca/)). ## WonderSwan, WonderSwan Color and SwanCrystal ### Hardware The NEC V30MZ runs at 3.072 MHz and is compatible with 16-bit x86. The screen is 224x144 ([WonderSwan](https://en.wikipedia.org/wiki/WonderSwan)). ### Tools Load the code with Ghidra's x86 real-mode language. [ghidra-wonderswan](https://github.com/JidouGhost-TL/ghidra-wonderswan) adds a loader. The extension has few users, so test it on a known binary before trusting it. ### For an exact reimplementation Unicorn runs x86 16-bit code. Run single functions headless under GameFrame's emulator, as for DOS code ([DOS games](https://tanepiper.github.io/game-frame/dos.md)). --- # Virtual Boy and Pokémon mini The Virtual Boy and the Pokémon mini are Nintendo handhelds outside the Game Boy line. The Virtual Boy's V810 needs a Ghidra extension. The Pokémon mini's S1C88 has no Ghidra module and no Unicorn backend, so take ground truth from PokeMini's trace. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | Virtual Boy | July 1995 (JP) | NEC V810 at 20 MHz | Cartridge | Built-in V850 differs; [Ghidra_v810_v830](https://github.com/20Enderdude20/Ghidra_v810_v830) | Mednafen | | Pokémon mini | 16 November 2001 (NA) | Epson S1C88 at 4 MHz | Cartridge up to 2 MB | None | PokeMini (development package) | ## Virtual Boy ### Hardware The NEC V810 runs at 20 MHz. It drives red LED displays with 384x224 pixels for each eye ([Virtual Boy](https://en.wikipedia.org/wiki/Virtual_Boy)). ### Tools Ghidra's built-in V850 differs from the V810. [Ghidra_v810_v830](https://github.com/20Enderdude20/Ghidra_v810_v830) adds the V810. The extension has few users, so test it on a known binary before trusting it. Mednafen has a debugger in its SDL front end ([Mednafen docs](https://mednafen.github.io/documentation/)). ## Pokémon mini ### Hardware The Pokémon mini came out on 16 November 2001 in North America. It reached Japan on 14 December 2001 and Europe on 15 March 2002. Nintendo R&D3 designed it. Epson made it. The CPU is an Epson S1C88, an 8-bit core with 16-bit instructions, at 4 MHz. It has 4 KB of SRAM shared with video. A 4 KB BIOS handles start-up and power. Cartridges hold up to 2 MB. Every commercial game is under 500 KB. **The monochrome 96 by 64 LCD runs at 30 fps.** Built in are a reed-switch shock sensor, an IR port (about 1 m, up to six players), a rumble motor and a battery-backed real-time clock ([Wikipedia: Pokémon mini](https://en.wikipedia.org/wiki/Pok%C3%A9mon_Mini)). ### Tools PokeMini by JustBurn is GPLv3. Its last version is 0.60 from 16 June 2015. Its debugger ships only in the development package. It keeps a 10,000-instruction trace history ([PokeMini on SourceForge](https://www.sourceforge.net/projects/pokemini); [libretro docs: PokeMini](https://docs.libretro.com/library/pokemini/)). No Ghidra module or Unicorn backend exists for the S1C88. ### Projects to learn from An official emulator inside the GameCube game *Pokémon Channel* (2003) ran demo versions of some games. Homebrew games appeared after people reverse engineered the system. The demo *SHizZLE* was shown at Breakpoint 2005 ([Wikipedia: Pokémon mini](https://en.wikipedia.org/wiki/Pok%C3%A9mon_Mini)). ### For an exact reimplementation Take ground truth from PokeMini's trace. The shock sensor, IR, rumble and RTC are outside inputs and state that a test records ([Handheld practices](https://tanepiper.github.io/game-frame/handhelds/practices.md)). The S1C88 programs are small, so writing a SLEIGH module or a purpose-built core would take modest effort. --- # Game.com Tiger's Game.com is a touchscreen handheld on a Sharp SM8521. No Ghidra module exists for the SM8521. Unicorn has no backend for it either. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | Game.com | 12 September 1997 (US) | Sharp SM8521 at 10 MHz | Two cartridge slots | None | MAME driver commonly cited (not confirmed) | | Game.com Pocket Pro | Mid-1999 | Sharp SM8521 at 10 MHz | One cartridge slot | None | As above | ## Game.com and Pocket Pro ### Hardware Tiger's Game.com came out in the US on 12 September 1997. The Pocket Pro followed in mid-1999. It uses a Sharp SM8521 8-bit CPU at 10 MHz. The 200 by 160 screen has 4 grey levels and a 12 by 10 grid touchscreen. The original had no backlight. One Pocket Pro model has one. The original has two cartridge slots. The Pocket Pro has one. Internet use needed a separate cartridge and a 14.4 kbit/s modem for email and text web. The Pocket Pro dropped internet use except for high-score submission ([Wikipedia: Game.com](https://en.wikipedia.org/wiki/Game.com)). The RAM size is not confirmed. ### Tools No Ghidra module exists for the SM8521. A MAME Game.com driver is commonly cited but not confirmed. ### Projects to learn from A prototype of an unreleased Game.com port of *Castlevania: Symphony of the Night* was found and released in 2022 ([Wikipedia: Game.com](https://en.wikipedia.org/wiki/Game.com)). ### For an exact reimplementation The touchscreen and the modem cartridge are outside inputs that a test records ([Handheld practices](https://tanepiper.github.io/game-frame/handhelds/practices.md)). The SM8521 programs are small, so writing a SLEIGH module or a purpose-built core would take modest effort. --- # Nintendo DS and 3DS The DS and 3DS run ARM code from cartridges. The DS splits a game between an ARM9 and an ARM7 binary. 3DS games store their code in AES-encrypted containers. Before any analysis, you need a decrypted dump and the right Ghidra loader. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | Nintendo DS, DSi | 21 November 2004 | ARM946E-S at 67 MHz and ARM7TDMI at 33 MHz | ARM9 and ARM7 binaries, overlays, NitroFS | [NTRGhidra](https://github.com/pedro-javierf/NTRGhidra) | melonDS, DeSmuME, no$gba | | Nintendo 3DS | 26 February 2011 (JP) | ARM11 MPCore, 2 cores at 268 MHz; ARM9 for security | NCSD/CCI, CIA, NCCH with ExeFS; 3DSX homebrew | [ghidra-3ds](https://github.com/Stary2001/ghidra-3ds), [ghidra-3dsx-loader](https://github.com/danylokos/ghidra-3dsx-loader) | Azahar | ## Nintendo DS and DSi ### Hardware The DS has an ARM946E-S at 67 MHz and an ARM7TDMI at 33 MHz. A game ships ARM9 and ARM7 binaries, overlays and a NitroFS file system ([Nintendo DS](https://en.wikipedia.org/wiki/Nintendo_DS); [GBATEK](https://problemkaputt.de/gbatek.htm)). ### Tools [NTRGhidra](https://github.com/pedro-javierf/NTRGhidra) loads DS binaries. Load the ARM9 and the ARM7 code as separate programs. Ghidra builds in the ARM7 (ARM and Thumb). melonDS, DeSmuME and no$gba run the games. GBATEK is the community document for the GBA and DS. ### Projects to learn from pret's pokeplatinum and pokeheartgold are active C decompilations. An SDL3 port fork is in progress ([gittrend: pokeplatinum](https://gittrend.io/repo/pret/pokeplatinum); [pokeplatinum-portable](https://github.com/arcanite24/pokeplatinum-portable)). ## Nintendo 3DS ### Hardware The 3DS has an ARM11 MPCore with 2 cores at 268 MHz. An ARM9 handles security. Formats are NCSD/CCI, CIA and NCCH with ExeFS. Homebrew uses 3DSX ([Nintendo 3DS](https://en.wikipedia.org/wiki/Nintendo_3DS); [3dbrew](https://www.3dbrew.org/)). ### Keys and encrypted executables 3DS content is AES-encrypted, with keys in the ARM9 boot ROM ([3dbrew](https://www.3dbrew.org/)). The user decides how to obtain and use their own keys and dumps. ### Tools [ghidra-3ds](https://github.com/Stary2001/ghidra-3ds) loads 3DS files. Its analysis is described as "pretty broken". [ghidra-3dsx-loader](https://github.com/danylokos/ghidra-3dsx-loader) loads 3DSX homebrew. Azahar runs the games. Citra was taken down in March 2024, at the same time as the yuzu settlement ([Wikipedia: Citra](https://en.wikipedia.org/wiki/Citra_(emulator)); [Switch family](https://tanepiper.github.io/game-frame/handhelds/switch-family.md#keys-and-the-yuzu-settlement)). Lime3DS and PabloMK7's fork merged into Azahar in late 2024, with first releases in early 2025 ([Azahar](https://azahar-emu.org/); [tech-insider](https://tech-insider.org/?p=19264)). Repositories can disappear, so pin an exact emulator build and keep a local copy. ### Projects to learn from No completed 3DS matching decompilation is known. --- # PSP and PS Vita Games for Sony's handhelds come as signed and encrypted executables. The PSP runs MIPS code with a vector unit. The Vita runs ARM code. Both need a Ghidra extension or loader. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | PSP | 12 December 2004 (JP) | Allegrex, MIPS32 with VFPU, 222 MHz default | EBOOT.BIN and PRX, encrypted "~PSP" ELF | [ghidra-allegrex](https://github.com/kotcrab/ghidra-allegrex) | PPSSPP | | PS Vita | 17 December 2011 (JP) | Quad Cortex-A9 | SELF wrapping ARM ELF; SUPRX modules | [GhidraVitaLoader](https://github.com/xerpi/GhidraVitaLoader), [VitaLoaderRedux](https://github.com/CreepNT/VitaLoaderRedux) | Vita3K | ## PSP ### Hardware The Allegrex is a MIPS32 core with a VFPU, at 222 MHz by default. Games ship as EBOOT.BIN and PRX files, which are encrypted "~PSP" ELFs ([PlayStation Portable](https://en.wikipedia.org/wiki/PlayStation_Portable)). ### Tools Ghidra has no Allegrex language built in. [ghidra-allegrex](https://github.com/kotcrab/ghidra-allegrex) adds it. Capstone has no Allegrex mode. splat supports PSP binaries ([GitHub: splat](https://github.com/ethteck/splat)). PPSSPP runs the games. It has a built-in debugger and a WebSocket remote debugger (not confirmed) ([PPSSPP](https://www.ppsspp.org/)). ### Projects to learn from The Castlevania: Symphony of the Night decompilation includes a PSP build. No completed PSP matching decompilation is known. ## PS Vita ### Hardware The Vita has a quad Cortex-A9. Executables are SELF files wrapping an ARM ELF, with SUPRX modules ([PlayStation Vita](https://en.wikipedia.org/wiki/PlayStation_Vita)). ### Tools [GhidraVitaLoader](https://github.com/xerpi/GhidraVitaLoader) and [VitaLoaderRedux](https://github.com/CreepNT/VitaLoaderRedux) load Vita files. Vita3K runs the games. Vita3K's debugger status is not confirmed ([Vita3K](https://vita3k.org/)). ### Projects to learn from No completed Vita matching decompilation is known. --- # Switch family The Switch, Switch Lite and Switch 2 run AArch64 code. Games ship as NSO files inside encrypted NCA containers. Before any analysis, you need a decrypted dump and the right Ghidra loader. The Switch 2 adds Game-Key Cards and a display that runs up to 120 Hz with VRR. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | Nintendo Switch | 3 March 2017 | 4 Cortex-A57 at 1.02 GHz | NSO in encrypted NCA; NRO homebrew | [Ghidra-Switch-Loader](https://github.com/Adubbz/Ghidra-Switch-Loader) | Ryubing, Eden, Citron | | Nintendo Switch Lite | 20 September 2019 | Tegra X1+ (16 nm) | As the Switch | As the Switch | As the Switch | | Nintendo Switch 2 | 5 June 2025 | Nvidia T239: 8 Cortex-A78C at 998 MHz docked, 1,101 MHz handheld | Game cards, eShop downloads, Game-Key Cards | AARCH64 built in | None named | ## Nintendo Switch ### Hardware The Switch has 4 Cortex-A57 cores at 1.02 GHz. Games ship as NSO files inside encrypted NCA containers. Homebrew uses NRO ([Nintendo Switch](https://en.wikipedia.org/wiki/Nintendo_Switch); [switchbrew NSO](https://switchbrew.org/wiki/NSO)). ### Tools [Ghidra-Switch-Loader](https://github.com/Adubbz/Ghidra-Switch-Loader) loads Switch executables. Ghidra's AARCH64 language covers v8.5-A, "missing some 8.5 vector" ([AARCH64.ldefs](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Processors/AARCH64/data/languages/AARCH64.ldefs)). ### Projects to learn from A Breath of the Wild v1.5.0 decompilation is in progress ([GitHub: botw](https://github.com/german77/botw)). ## Keys and the yuzu settlement Switch NCA contents are encrypted. Emulators need prod.keys, title keys and firmware dumped from the user's own console (not confirmed) ([switchbrew: NCA](https://switchbrew.org/wiki/NCA)). On 4 March 2024 Tropic Haze, yuzu's developer, settled with Nintendo for $2.4 million. A Rhode Island federal court entered a permanent injunction against offering yuzu or software that circumvents Nintendo's protections. The yuzu domain went to Nintendo ([Android Authority](https://androidauthority.com/nintendo-switch-yuzu-settlement-3422388); [Nintendo World Report](https://www.nintendoworldreport.com/news/66510/creators-of-yuzu-emulator-settle-lawsuit-with-nintendo)). Citra was taken down at the same time ([Wikipedia: Citra](https://en.wikipedia.org/wiki/Citra_(emulator))). The 2024 to 2026 cases ended in settlements. No court ruling on console emulators is known from that period. The user decides how to obtain and use their own keys and dumps. ## Emulator status Repositories can disappear, so pin an exact emulator build and keep a local copy. On the Switch, yuzu development stopped in March 2024. Ryubing, a Ryujinx fork at ryujinx.app, moved to maintenance in February 2026. Eden and Citron are described as the active forks. Suyu was an early fork. In February 2026, takedown notices reportedly targeted at least 13 Switch projects, including Citron, Eden and Ryubing. These Switch facts come from secondary 2026 coverage ([tech-insider: Eden vs Ryubing](https://tech-insider.org/eden-vs-ryubing-best-switch-emulator-2026/); [tech-insider](https://tech-insider.org/?p=23388)). ## Nintendo Switch Lite ### Hardware The Switch Lite came out on 20 September 2019 on the 16 nm Tegra X1+. It has a 5.5-inch 720p display and built-in controllers. A d-pad replaces four buttons. It plays games that support handheld mode ([Wikipedia: Nintendo Switch Lite](https://en.wikipedia.org/wiki/Nintendo_Switch_Lite)). ### Tools Its games are Switch games, so the Switch tools apply unchanged ([Ghidra-Switch-Loader](https://github.com/Adubbz/Ghidra-Switch-Loader); [switchbrew NSO](https://switchbrew.org/wiki/NSO)). ## Nintendo Switch 2 ### Hardware The Switch 2 came out on 5 June 2025 in most regions. Its Nvidia T239 SoC has eight ARM Cortex-A78C cores at 998 MHz docked and 1,101 MHz handheld. It has 12 GB of LPDDR5X on a 128-bit bus and 256 GB of UFS 3.1 storage. Only microSD Express cards can load games. The 7.9-inch 1080p display runs up to 120 Hz with HDR10 and VRR. Games come on game cards, as eShop downloads and as Game-Key Cards. **A Game-Key Card holds only a licence and must stay inserted while the game is downloaded and played.** The Switch 2 runs most Switch games through a mix of software and hardware emulation. The Joy-Con 2 can work as a mouse ([Wikipedia: Nintendo Switch 2](https://en.wikipedia.org/wiki/Nintendo_Switch_2)). ### Tools The code is AArch64, which Ghidra's AARCH64 module and Unicorn's ARM64 support cover. Its security and homebrew status are not confirmed. No Switch 2 emulator is known. ### For an exact reimplementation With VRR and up to 120 Hz, a Switch 2 game sets its own frame rate within what the display allows. Record that rate in each test. The Game-Key Card and the Joy-Con 2 mouse are outside inputs ([Handheld practices](https://tanepiper.github.io/game-frame/handhelds/practices.md#sensors-and-other-inputs)). --- # ARM-OS handhelds The N-Gage, Gizmondo, GP32 and GP2X run ARM code on an operating system. **Study a game's imports and OS calls as closely as its CPU code**, as for a Windows PE game. Ghidra's ARM module covers all four devices. Unicorn's ARM support can run their functions ([Ghidra Processors on GitHub](https://github.com/NationalSecurityAgency/ghidra/tree/master/Ghidra/Processors); [Unicorn Engine](https://www.unicorn-engine.org/)). ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | Nokia N-Gage, QD | 7 October 2003 to 26 November 2005 (West) | ARM920T at 104 MHz | MMC cards; Symbian OS 6.1, Series 60 | ARM built in | EKA2L1 (GDB stub not confirmed) | | Gizmondo | 19 March 2005 to 6 February 2006 | Samsung S3C2440 (ARM9) at 400 MHz | SD or MMC; Windows CE | ARM built in | None sourced | | GP32 | 23 November 2001 (KR) | Samsung S3C2400X01 (ARM920T), 20 to 133 MHz | SmartMedia, 2 to 128 MB | ARM built in | Not confirmed | | GP2X, F-200 | 10 November 2005 and 30 October 2007 (KR) | MMSP2 MP2520F: 200 MHz ARM920T host, 200 MHz ARM940T coprocessor | SD (SDHC on the F-200 with firmware 4.0 or later); Linux | ARM built in | Not confirmed | ## Nokia N-Gage ### Hardware Nokia's N-Gage came out on 7 October 2003. It was discontinued in Western markets on 26 November 2005. It uses an ARM920T at 104 MHz, a 2.1-inch 176 by 208 TFT with 4,096 colours, and Symbian OS 6.1 with Series 60 (1st Edition). Games came on MultiMediaCard (MMC) cards. The N-Gage QD shipped on 26 May 2004 in Europe, Africa and Asia-Pacific. It reached the Americas in June 2004. It has the same S60 software, without MP3, FM radio or USB. N-Gage 2.0 was a service on Nokia smartphones from April 2008 to October 2009. Pirated N-Gage games ran on other phones of the time such as the Nokia 3650 ([Wikipedia: N-Gage](https://en.wikipedia.org/wiki/N-Gage_(device))). ### Tools EKA2L1 is a C++17 Symbian OS and N-Gage emulator. It emulates the Symbian kernel. It reimplements the app servers and libraries. It supports nearly all official N-Gage and N-Gage 2.0 libraries. It also supports most game libraries from S60v1 to Symbian Belle. Builds exist for Windows, macOS, Linux and Android ([EKA2L1 GitHub](https://github.com/EKA2L1/EKA2L1)). Whether it has a GDB stub is not confirmed. ### For an exact reimplementation EKA2L1 reimplements the OS at high level. Code that depends on exact OS behaviour may differ from a real N-Gage. To run a single function under Unicorn, write stubs for the Symbian calls it makes. ## Gizmondo ### Hardware The Gizmondo came out in the UK on 19 March 2005 and in the US on 22 October 2005. It was discontinued on 6 February 2006. It uses a Samsung S3C2440 (ARM9) at 400 MHz and an Nvidia GoForce 3D 4500 GPU. It has 64 MB of SDRAM, 1.2 MB of graphics SRAM and 64 MB of ROM. Its 2.8-inch 320 by 240 TFT runs Windows CE. Games come on SD or MMC. It also has GPS, GPRS and a camera. Fourteen games were released. Only eight reached the US ([Wikipedia: Gizmondo](https://en.wikipedia.org/wiki/Gizmondo)). ### Tools No emulator is known for it. ### For an exact reimplementation To run a single function under Unicorn, write stubs for the Windows CE calls it makes. GPS, GPRS and the camera are outside inputs ([Handheld practices](https://tanepiper.github.io/game-frame/handhelds/practices.md#sensors-and-other-inputs)). ## GP32 ### Hardware Game Park's GP32 came out in South Korea on 23 November 2001. Its Samsung S3C2400X01 (ARM920T) runs from 20 MHz up to 133 MHz. Some units reach 166 MHz or more. It has 8 MB of 16-bit SDRAM, a 3.5-inch 320 by 240 TFT with 16-bit colour, and SmartMedia cards of 2 to 128 MB at 3.3 V. A free C SDK was available. Game Park's firmware launches programs. Alternative firmware removed the need for the "Free Launcher" ([Wikipedia: GP32](https://en.wikipedia.org/wiki/GP32)). ### Tools The GP32's executable formats are not confirmed. Neither is any emulator with a debugger. ## GP2X ### Hardware GamePark Holdings' GP2X came out in South Korea on 10 November 2005. The F-200 followed on 30 October 2007. Its successors were the Wiz (announced 26 August 2008) and the Caanoo. The MagicEyes MMSP2 MP2520F pairs a 200 MHz ARM920T host with a 200 MHz ARM940T coprocessor. It has 64 MB of SDRAM and 64 MB of NAND. It takes SD cards, with SDHC on the F-200 with firmware 4.0 or later. It runs Linux on a 3.5-inch 320 by 240 screen with 65,536 colours. The F-200 added a touchscreen and a d-pad ([Wikipedia: GP2X](https://en.wikipedia.org/wiki/GP2X)). ### Tools No emulator with a debugger is confirmed. --- # PC handhelds The Steam Deck and the other PC handhelds are x86-64 PCs with a built-in screen and controls. GameFrame's Windows PE path (Ghidra, Unicorn x86-64, Wine) applies to them directly. ## The devices | Device | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | Steam Deck (LCD) | 25 February 2022 | AMD Aerith (7 nm): Zen 2, 4 cores and 8 threads at 2.4 to 3.5 GHz | Downloads; SteamOS 3, Windows games through Proton | x86-64 built in | Not needed; the games are PC games | | Steam Deck OLED | 16 November 2023 | AMD Sephiroth (6 nm): Zen 2, as above | As above | x86-64 built in | As above | | Asus ROG Ally, Lenovo Legion Go | 2023 | Not covered here | Not covered here | x86-64 built in | As above | | MSI Claw A1M | 2024 | Not covered here | Not covered here | x86-64 built in | As above | | ROG Xbox Ally | Announced June 2025 | Not covered here | Windows | x86-64 built in | As above | ## Steam Deck ### Hardware Valve's Steam Deck came out on 25 February 2022 in North America and Europe. The OLED model followed on 16 November 2023. Its custom AMD APU is codenamed Aerith (7 nm) on the LCD model. The OLED model uses Sephiroth (6 nm). The Zen 2 CPU has 4 cores and 8 threads at 2.4 to 3.5 GHz. The RDNA 2 GPU has 8 CUs at 1.0 to 1.6 GHz. It has 16 GB of LPDDR5 on the LCD model and LPDDR5X on the OLED model. The 1280 by 800 screen is a 7-inch LCD at 60 Hz or a 7.4-inch OLED at 90 Hz ([Wikipedia: Steam Deck](https://en.wikipedia.org/wiki/Steam_Deck)). SteamOS 3 is built on Arch Linux with an immutable file system and a KDE Plasma desktop. Windows games run through the Proton compatibility layer ([Wikipedia: Steam Deck](https://en.wikipedia.org/wiki/Steam_Deck)). Proton is Wine-based (not confirmed). ### Tools Use GameFrame's Windows PE path: Ghidra, Unicorn x86-64 and Wine ([Home computers and the PC](https://tanepiper.github.io/game-frame/computers/index.md)). ### For an exact reimplementation The LCD model's panel runs at 60 Hz. The OLED model's runs at 90 Hz. Record which panel a test ran on. ## Other PC handhelds ### Hardware Other PC handhelds include the Asus ROG Ally and Lenovo Legion Go (2023) and the MSI Claw A1M (2024). The ROG Xbox Ally was announced in June 2025 and runs Windows ([Wikipedia: Steam Deck](https://en.wikipedia.org/wiki/Steam_Deck)). ### Tools These run PC games, so the Windows PE path applies as for the Steam Deck. --- # Phones Before smartphones, phone games ran as J2ME or BREW apps. J2ME games are JVM bytecode, which Ghidra reads. Downloaded BREW apps are signed for one handset, so most were never dumped. One game often shipped in many builds for different handsets. Treat each build as a separate target. ## The platforms | Platform | Years | CPU and clock | Media and format | Ghidra support | Emulator with a debugger | |---|---|---|---|---|---| | J2ME | Not covered here | A Java VM on the phone | .jar files | JVM built in | FreeJ2ME, J2ME Loader (debuggers not covered here) | | BREW | Developer site shut 23 July 2021 | Not covered here | .mif and .mod files, signed per handset | Not covered here | Melange (early development) | | Zeebo | Not covered here | Not covered here | BREW | Not covered here | @Tuxality's emulator work | ## J2ME ### Hardware J2ME games ship as .jar files and run on a Java VM on the phone. Community estimates put the total above 3,000 unique games ([techtactician roundup](https://techtactician.com/?p=58218); [GameTechWiki: J2ME](https://emulation.gametechwiki.com/index.php/J2ME)). ### Tools Ghidra's built-in JVM module reads the bytecode ([Ghidra Processors on GitHub](https://github.com/NationalSecurityAgency/ghidra/tree/master/Ghidra/Processors)). FreeJ2ME is a free J2ME emulator with AWT, SDL2 and libretro front ends. Its README states neither its licence nor its MIDP and CLDC coverage ([FreeJ2ME GitHub](https://github.com/hex007/freej2me)). J2ME Loader is an Android J2ME emulator under Apache 2.0. It runs most 2D and 3D games, including Mascot Capsule 3D ([J2ME Loader GitHub](https://github.com/nikita36078/J2ME-Loader)). ### Projects to learn from Kahvibreak is a J2ME launcher and collection. It has been inactive since its maintainer (Nokia64) disappeared. Sources disagree on the year. One gives 2022. Users report quality problems in its database ([GameTechWiki: Kahvibreak](https://emulation.gametechwiki.com/index.php/Kahvibreak); [techtactician roundup](https://techtactician.com/?p=58218)). Other efforts include a J2ME Preservation archive and the Cell Phone Game Preservation Wiki ([awesome-j2me list](https://www.context-awesome.com/list/hstsethi/awesome-j2me)). ### For an exact reimplementation One game often shipped in many builds for different handsets. Treat each build as a separate target. Record which build a test runs. ## BREW and the Zeebo ### Hardware BREW apps use .mif and .mod files. **Downloaded BREW apps are tied to one handset by a digital signature, so most were never dumped.** Qualcomm shut the Brew MP developer site on 23 July 2021 ([GameTechWiki: BREW](https://emulation.gametechwiki.com/index.php/BREW)). ### Tools Melange is an Android BREW emulator. It is open source but relies on proprietary Qualcomm components. It is in early development. Qualcomm's SDK BREW Simulator does not emulate handset hardware ([GameTechWiki: BREW](https://emulation.gametechwiki.com/index.php/BREW)). ### Projects to learn from The Zeebo console ran on BREW. @Tuxality's emulator work in 2024 made its games playable ([Time Extension, May 2024](https://www.timeextension.com/news/2024/05/zeebo-emulator-makes-lost-double-dragon-and-crash-bandicoot-games-playable-again)). Early iOS and Android games are not covered here. --- # Arcade games This section covers arcade boards from discrete logic to the PC-based systems. Each page covers one era or one family of boards. Many boards were built for one game or a few games. The MAME driver source for a board lists its chips, its crystals, its clock dividers and its screen timing. It usually gives more detail than any other public source. ## Across all boards - [Practices for arcade work](https://tanepiper.github.io/game-frame/arcade/practices.md): MAME driver source, machine flags, ROM sets and CHD, NVRAM and DIP settings as test inputs, attract mode, input recording, refresh rates and frame drift. - [Encryption and protection](https://tanepiper.github.io/game-frame/arcade/protection.md): suicide batteries, the Sega, Capcom, Konami and SNK ciphers, protection MCUs, Phoenix sets and desuicide work. ## 2D boards - [Discrete logic and the 8080](https://tanepiper.github.io/game-frame/arcade/discrete-and-8080.md): Pong, Breakout and Space Invaders. - [Z80 and 6502 boards](https://tanepiper.github.io/game-frame/arcade/z80-and-6502-era.md): Galaxian, Pac-Man, Galaga, Donkey Kong, Asteroids, Sega System 1, the VS. System and the PlayChoice-10. - [Williams and Midway](https://tanepiper.github.io/game-frame/arcade/williams-and-midway.md): the Williams 6809 boards, the Midway Y, T and W-units, and Midway Zeus. - [Atari coin-op boards](https://tanepiper.github.io/game-frame/arcade/atari-coin-op.md): Atari System 1, System 2, Hard Drivin' and GT. - [Sega 2D boards](https://tanepiper.github.io/game-frame/arcade/sega-2d.md): System 16B, System 18, the X Board and the Y Board. - [Capcom CPS](https://tanepiper.github.io/game-frame/arcade/capcom-cps.md): CPS-1, CPS-2 and CPS-3. - [Neo Geo](https://tanepiper.github.io/game-frame/arcade/neo-geo.md): the MVS and the AES. - [Other 2D makers](https://tanepiper.github.io/game-frame/arcade/other-2d-makers.md): Namco System 1 and 2, Konami, Taito F2 and F3, Irem M72 and M92, Data East, Toaplan and Cave. ## 3D, console-derived and PC boards - [Early 3D boards](https://tanepiper.github.io/game-frame/arcade/early-3d.md): Namco System 21 and 22, Sega Model 1, 2 and 3, Taito JC, Konami GTI Club and Hornet, and Gaelco 3D. - [Console-derived boards](https://tanepiper.github.io/game-frame/arcade/console-derived.md): ZN-1 and ZN-2, Namco System 11 and 12, ST-V, NAOMI, Atomiswave, Triforce, Chihiro, System 246 and the Bemani boards. - [PC-based boards](https://tanepiper.github.io/game-frame/arcade/pc-based.md): the Taito Type X family, Sega Lindbergh, RingEdge to ALLS and Namco ES1 and ES3. ## How arcade work differs from console work A console has one hardware design for every game. An arcade board was often built for one game or for a short run of games. Each board family has its own memory map, clocks and protection. Arcade refresh rates are rarely 60 Hz. They run from about 54.71 Hz on Midway's TMS34010 boards to about 61.5 Hz on Atari's Asteroids. A port must run at the board's own rate to stay in step with a recording of the original. The [practices page](https://tanepiper.github.io/game-frame/arcade/practices.md#refresh-rates-and-frame-drift) has the table. Later boards add three problems. 1. From the 1980s, many boards use battery-backed custom CPUs that decrypt the program. A disassembly of the raw ROM is unreadable until you decrypt it with the CPU's method. 2. From 1988, 3D boards split the game across a main CPU and one or more DSPs. Each DSP runs its own program. Ghidra has no built-in module for most of those DSPs. 3. From 2004, the boards are x86 PCs. The game code is decrypted only on a running machine with its keychip. ## Order of work 1. Read the MAME driver for the board. Note the clocks, the screen totals and the machine flags. 2. Decrypt the program wherever the board has a custom CPU, before any disassembly ([protection](https://tanepiper.github.io/game-frame/arcade/protection.md)). 3. On a 3D board, capture the DSP programs too. They hold part of the game. 4. Record the DIP settings, the BIOS, the region and the NVRAM image with every test case ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#nvram-and-dip-settings-as-test-inputs)). 5. Check MAME's flags before using it as ground truth. If MAME does not run a game, take ground truth from another emulator or from owned hardware. ## Page layout Each family page opens with one short paragraph and a table that compares its boards. Then it has one section per board. Each board section has the same headings in the same order. 1. **Hardware**: the years, the CPUs and clocks, the sound chips, the screen timing. 2. **Protection**: encryption, protection MCUs and key chips. 3. **Tools**: MAME status, Ghidra and Unicorn support, other emulators. 4. **Projects to learn from**: disassemblies, source releases and re-releases. 5. **For an exact reimplementation**: what to settle before porting. A refresh rate marked "(computed)" is the pixel clock divided by the horizontal and vertical totals in the MAME driver. It comes from MAME's model of the board. It is not a separate measurement. A fact marked "(not confirmed)" has one source and lacks a second. A fact marked "(secondary source)" comes from a forum, a wiki copy or a press report. MAME's flags and entry counts change with each release. Encryption and protection are described factually. The law on working around them differs by country ([Legal](https://tanepiper.github.io/game-frame/legal.md), [the law worldwide](https://tanepiper.github.io/game-frame/preservation/law/index.md)). You decide which route to take. ## Related pages - [CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md): byte order, Ghidra and Unicorn support, and arithmetic traps for the Z80, 6502, 6809, 68000, x86, SuperH, MIPS and PowerPC. - [Tools for console work](https://tanepiper.github.io/game-frame/consoles/tools.md) and [matching decompilation](https://tanepiper.github.io/game-frame/consoles/matching-decomp.md). - [Sound](https://tanepiper.github.io/game-frame/hardware/sound.md) and [running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md). - The skills for each step: [re-inventory](https://tanepiper.github.io/game-frame/skills/re-inventory.md), [re-observe](https://tanepiper.github.io/game-frame/skills/re-observe.md), [re-ghidra](https://tanepiper.github.io/game-frame/skills/re-ghidra.md), [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md), [re-port](https://tanepiper.github.io/game-frame/skills/re-port.md) and [re-verify](https://tanepiper.github.io/game-frame/skills/re-verify.md). --- # Practices for arcade work This page covers the practices that apply to every arcade board. MAME's driver source describes the hardware. MAME marks each game with flags that show how complete its emulation is. The game reads the settings stored on the board, so every test case records them. ## MAME driver source MAME's driver headers give the chip list, the clocks, the ROM names and the known bugs in one place. They also name the people whose research they use. - The Neo Geo driver credits Charles MacDonald's MVS technical notes ([MAME: snk/neogeo.cpp](https://github.com/mamedev/mame/blob/master/src/mame/snk/neogeo.cpp)). - The CPS-2 driver credits Razoola and the CPS2 decryption team ([MAME: capcom/cps2.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps2.cpp)). - The CPS-3 driver credits David Haywood, Andreas Naive, Tomasz Slanina and ElSemi ([MAME: capcom/cps3.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps3.cpp)). - The Taito F3 driver cites arcade-projects.com measurements of F3 sync ([MAME: taito/taito_f3.cpp](https://github.com/mamedev/mame/blob/master/src/mame/taito/taito_f3.cpp)). MAME takes some of its timing values from such community measurements. Drivers set clocks with `XTAL(...)` values and divisors. A comment such as "verified on pcb" marks a measured value. A value without such a comment is MAME's best estimate. ### Machine flags The flags show whether MAME emulates a game well enough to use as ground truth ([MAME gamedrv.h](https://github.com/mamedev/mame/blob/master/src/emu/gamedrv.h)). | Flag | Meaning | |---|---| | MACHINE_NOT_WORKING | "Imperfect emulation prevents using the system as intended" | | MACHINE_UNEMULATED_PROTECTION | Protection is not emulated | | MACHINE_IMPERFECT_GRAPHICS | Graphics are imperfect | | MACHINE_IMPERFECT_SOUND | Sound is imperfect | | MACHINE_IMPERFECT_TIMING | Timing is imperfect | | MACHINE_NODEVICE_LAN | "unemulated local area networking" | | MACHINE_SUPPORTS_SAVE | Save states work | | MACHINE_MECHANICAL | A mechanical game | | MACHINE_IS_BIOS_ROOT | A BIOS set | MAME also marks a ROM as BAD_DUMP when the dump is known to be faulty. The Sega Model 1 TGP ROMs for Virtua Fighter and three other games have this mark ([Early 3D boards](https://tanepiper.github.io/game-frame/arcade/early-3d.md#sega-model-1)). MAME also marks some of its own timing as wrong. The Taito F3 CPU clock is one case ([Other 2D makers](https://tanepiper.github.io/game-frame/arcade/other-2d-makers.md#taito-f2-and-f3)). The Triforce, Chihiro, System 246 and Lindbergh families do not run in MAME. For those games, take ground truth from other emulators or from owned hardware. ## ROM sets and CHD A ROM set lists every chip dump from a board. That includes BIOS sets such as Atari System 1's ([MAME: atari/atarisy1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/atari/atarisy1.cpp)). It also includes the PAL images on CPS-1 B-boards ([MAME: capcom/cps1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps1.cpp)). Disks and optical media are stored as **CHD** (Compressed Hunks of Data). MAME's `chdman` tool works with them ([MAME chdman docs](https://github.com/mamedev/mame/blob/master/docs/source/tools/chdman.rst)). - Create: `createhd`, `createcd`, `createdvd`, `createld` and `createraw`. - Extract: the matching `extract*` commands. - Check: `info` and `verify`. - Codecs: zlib, zstd, lzma, huff and flac. The CD variants are cdzl, cdzs, cdlz and cdfl. LaserDisc uses avhu. - Hunk size runs from 16 bytes to 1 MiB. To read the files on a CPS-3 CD or a GD-ROM, extract them from the CHD first. ## NVRAM and DIP settings as test inputs The game reads NVRAM and DIP switches. A test sets them like any other input. - Williams 6809 games keep settings and high scores in battery-backed CMOS RAM ([MAME: williams/williams.cpp](https://github.com/mamedev/mame/blob/master/src/mame/williams/williams.cpp)). - CPS-2 A-boards have a 93C46 serial EEPROM. CPS-1 drivers include MAME's serial EEPROM device. The Neo Geo includes NVRAM ([MAME: capcom/cps2.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps2.cpp); [capcom/cps1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps1.cpp); [snk/neogeo.cpp](https://github.com/mamedev/mame/blob/master/src/mame/snk/neogeo.cpp)). - Neo Geo MVS motherboards differ by generation. Memory-card headers, coin lockouts, LED displays and soldered BIOS vary between MV-1, MV-1F, MV-1A, MV-1B and MV-1C. - Fatal Fury 3 in MAME crashes in the ending unless the language is Japanese. The BIOS region and language settings change which code the game runs ([MAME: snk/neogeo.cpp](https://github.com/mamedev/mame/blob/master/src/mame/snk/neogeo.cpp)). - The NAOMI driver applies "usability defaults, i.e. 31kHz dip switch and non-canonical service mode settings in NVRAM". MAME's default NVRAM can differ from a factory board ([MAME naomi.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/naomi.cpp)). - JVS I/O is "the most common type". It is used on Sega driving games, NAOMI, Hikaru, Triforce and Chihiro ([MAME naomi.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/naomi.cpp)). Each test case records its DIP settings, its BIOS choice and region, and the NVRAM image it started from. The list of outside inputs is in [inputs](https://tanepiper.github.io/game-frame/observe/inputs.md). ## Attract mode Attract-mode demos play from recorded inputs or from scripted AI. What a demo shows depends on the random state and on NVRAM. To compare a port's attract mode with the original, start both from the same NVRAM image and the same power-on state. Released source from the era shows that attract mode, audits, operator adjustments and diagnostics are separate modules. NBA Jam's source has ATTRACT.ASM, AUDIT.ASM, ADJUST.ASM and DIAG.ASM. Smash TV's has DEMO.ASM and HSTD.ASM for the high-score table ([historicalsource/nba-jam](https://github.com/historicalsource/nba-jam); [historicalsource/smashtv](https://github.com/historicalsource/smashtv)). A full reimplementation rebuilds all of them. ## Input recording MAME's `-record` and `-playback` "do not work reliably for all systems". Playback can desync if the configuration (.cfg), NVRAM (.nv) and memory card files differ from the original run. MAME suggests recording and playing back with all of them deleted ([MAME docs: commandline-all.rst](https://github.com/mamedev/mame/blob/master/docs/source/commandline/commandline-all.rst)). The same page lists other options for capturing ground truth. | Option | What it does | |---|---| | `-exit_after_playback` | Quits at the end of a playback | | `-mngwrite`, `-aviwrite`, `-wavwrite` | Capture video and audio | | `-nvram_directory`, `-snapshot_directory` | Set where state goes | | `-debugger`, `-debugscript` | Run the debugger and feed it commands from a file | | `-debugger_host`, `-debugger_port` | Open a remote debugger | | `-autoboot_script` | Runs a Lua script at start | | `-plugins`, `-plugin` | Load plugins | MAME's Lua memory taps and its `trace` and `wpset` debugger commands are not covered here. See [running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md) and the [re-observe](https://tanepiper.github.io/game-frame/skills/re-observe.md) skill for capture in general. ## Refresh rates and frame drift Boards with the same crystal and the same totals have the same refresh rate. A rate marked "(computed)" is the pixel clock divided by the totals in the driver's `set_raw(...)` screen line. | Group | Crystal and totals | Refresh | Boards | |---|---|---|---| | Midway TMS34010 | 506 x 289 | about 54.71 Hz (computed) | Y-unit, T-unit, W-unit | | Irem | 32 MHz master, 512 x 284 | about 55.02 Hz (computed) | M72 | | Sega and Cave | measured or set | 57.23 Hz (System 18, measured); about 57.55 Hz (Cave) | System 18, Cave 68000 | | Data East | 384 x 272 | about 57.44 Hz (computed) | DEC0 | | Taito | 26.686 MHz / 4, 432 x 262 | 58.94 Hz | F3 | | 6 MHz pixel clock | 384 x 264 | about 59.19 Hz (computed) | Neo Geo, Konami TMNT, Taito Bubble Bobble | | 8 MHz pixel clock | 512 x 262 | about 59.64 Hz (computed); 59.6388 Hz measured on CPS-2 | CPS-1, CPS-2, Sega X Board | | Atari | 456 x 262 | about 59.92 Hz (computed) | System 1, GT | | 18.432 MHz / 3 | 384 x 264 | about 60.61 Hz (computed) | Galaxian, Pac-Man, Galaga, Namco System 1 and 2, Donkey Kong | | Vector | 12.096 MHz / 4096 / 12 / 4 | about 61.5 Hz (computed) | Asteroids | The sources are the driver links on each family page. The source for the Bubble Bobble entry is [MAME: taito/bublbobl.cpp](https://github.com/mamedev/mame/blob/master/src/mame/taito/bublbobl.cpp). A port that runs at a fixed 60 Hz drifts against a recording. At 60 Hz it shows about 317 more frames per minute than a 54.71 Hz Midway board. It shows about 22 more frames per minute than a 59.64 Hz CPS board. Both figures are computed from the rates above. On a Midway board, a frame-by-frame comparison goes out of step within seconds. Some boards have a nominal rate in MAME. The Sega Y Board, the PlayChoice-10 and Taito F2 are set to 60 Hz without raw screen timing. Treat those rates as unknown. ## Disassembly and emulation support per arcade CPU | CPU | Boards | Ghidra | Unicorn | Other tools | |---|---|---|---|---| | 8080 | Space Invaders | Built in (`8085`) | None | MAME | | Z80 | Namco, Nintendo, Sega System 1, sound CPUs | Built in | None | MAME | | 6502, 65C02 | Atari, Data East sound | Built in | None | MAME | | 6809, HD6309 | Williams, Namco System 1 | Built in under MC6800 | None | MAME | | Konami-1 | Konami | None (decrypt first, then 6809) | None | MAME's decrypting disassembler | | 68000 to 68040 | Atari, Sega, Capcom, SNK, Taito, Namco 21/22 | Built in | M68K | MAME | | NEC V30, V33, V35 | Irem | x86 16-bit real mode (not confirmed) | x86 16-bit (not confirmed) | MAME | | TMS34010 | Midway Y/T/W-unit, Hard Drivin' | Extension [Ghidra_TMS34010](https://github.com/ZPaul2Fresh8/Ghidra_TMS34010) | None | MAME | | TMS320, ADSP-21xx, MB8623x, DSP32C | 3D geometry, sound | None built in | None | MAME | | ADSP-21062 SHARC | Model 2B, Hornet | [ghidra-sharc](https://github.com/roblg/ghidra-sharc) targets ADSP-214xx | None | MAME | | NEC V60 | Model 1 | None found | None | MAME | | i960 | Model 2 | Extension [ghidra_i960](https://github.com/mumbel/ghidra_i960) | None | MAME | | SH-2, SH-4 | CPS-3, ST-V, NAOMI | Built in (SuperH, SuperH4) | None | MAME, Flycast | | PowerPC | Model 3, Konami, Triforce | Built in | PPC | Supermodel, Dolphin | | MIPS R3000, R5900 | ZN, Namco 11/12, System 246 | Built in MIPS; R5900 by extension | MIPS (no R5900) | MAME, Play! | | x86 | Chihiro, Type X, Lindbergh, Ring, ES3 | Built in | x86 | lindbergh-loader, TeknoParrot | | 8051, PIC | 8751 and PIC protection MCUs | Built in (8051, PIC) | None | MAME | Ghidra's bundled processors have no V60, i960, SHARC, TMS34010 or TMS320 entries ([Ghidra/Processors](https://github.com/NationalSecurityAgency/ghidra/tree/master/Ghidra/Processors)). The MC6800 module holds the 6800, 6805, 6809 and H6309 specs. The SuperH module holds SH-1, SH-2 and SH-2A ([MC6800 languages](https://github.com/NationalSecurityAgency/ghidra/tree/master/Ghidra/Processors/MC6800/data/languages); [SuperH languages](https://github.com/NationalSecurityAgency/ghidra/tree/master/Ghidra/Processors/SuperH/data/languages)). A community TMS34010 module exists. Its completeness is not confirmed. No V60 module is known (not confirmed). Unicorn has ARM, ARM64, MIPS, x86, PowerPC, SPARC, M68K, RISC-V, S390X and TriCore. It has no Z80, 6502, 6809, 8080, SH-2 or TMS34010 ([unicorn.h](https://github.com/unicorn-engine/unicorn/blob/master/include/unicorn/unicorn.h)). For Z80, 6502 and 6809 games, take ground truth from MAME's debugger and Lua, or from a small purpose-built CPU core. The [CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md) page lists such cores. The [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md) skill covers the Unicorn path for the CPUs it supports. FinalBurn Neo, Demul and JConfig are not covered here. ## Source releases and the shipped ROM Released source from the era is assembly language for the board's CPU, even on the TMS34010 games of the 1990s. Original source shows what the programmers meant. The source may not match the final revision, so use the shipped ROM for exact behaviour. The [Williams and Midway](https://tanepiper.github.io/game-frame/arcade/williams-and-midway.md) page lists the source releases and their provenance. GameFrame's rule against game material means a project does not copy such source into its own repository ([Legal](https://tanepiper.github.io/game-frame/legal.md)). --- # Encryption and protection Arcade encryption was usually built into a custom CPU and keyed from battery-backed RAM. When the battery dies, the key is lost and the board "suicides". Preservation groups broke the ciphers. They extracted keys from working boards. Later they wrote keys back into dead boards so that the boards run again. MAME's source describes each algorithm. Each MAME driver contains decryption code that an agent can follow. Decrypt the program before any disassembly. The law on working around protection differs by country ([Legal](https://tanepiper.github.io/game-frame/legal.md), [the law worldwide](https://tanepiper.github.io/game-frame/preservation/law/index.md)). You decide which route to take. | Scheme | Maker | CPU | Encrypts | Key store | On a dead battery | |---|---|---|---|---|---| | FD1094 | Sega | Custom 68000 | Program-space words only | 8 KB battery-backed RAM | Runs with a wrong key | | FD1089A, FD1089B | Sega | Custom 68000 | Opcodes and data | 16 KB internal RAM | Stops | | MC8123 and 315-5xxx parts | Sega | Custom Z80 | Not covered here | Not covered here | Not covered here | | Kabuki | Capcom | Custom Z80 | Opcodes and data | Battery-backed RAM | Pin 28 low gives a plain Z80 | | CPS-2 | Capcom | 68000 board | Opcodes only | SRAM in a custom IC | Blue or green screen | | CPS-3 | Capcom | SH-2 derivative | Boot flash and program areas | SRAM on the CPU die | Thought to fall back to SFIII 2nd Impact keys | | Konami-1 | Konami | Custom 6809 | Opcodes only | Fixed by address | No battery | | NEO-CMC, NEO-PCM2, NEO-SMA | SNK and Playmore | Separate chips | Graphics, sound, samples, program | In the chip | Not covered here | ## Sega ### Hardware System 16B configurations in MAME include FD1089A, FD1089B, FD1094, an MC8123 sound Z80 and an Intel 8751 protection MCU. System 1 uses many encrypted Z80 parts (315-5033, 315-5041, 315-5048, 315-5051, 315-5065, 315-5098, 315-5178 and others) and the MC8123 ([MAME: sega/segas16b.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/segas16b.cpp); [sega/system1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/system1.cpp)). ### Protection **FD1094** is a custom 68000. It decrypts only words fetched from program space and leaves data alone. It keeps its key in 8 KB of battery-backed RAM. Decryption is a chain of conditional XORs and bit swaps controlled by 32 variables. Eight of them vary by address and repeat every 0x2000 words. The CPU has 256 internal "states" that the program can switch. A final step replaces about 300 or about 5000 values with FFFF. This step stops tricks that dump the program through PC-relative reads. When the battery dies the CPU still runs, with a wrong key ([MAME: sega/fd1094.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/fd1094.cpp)). **FD1089A and FD1089B** encrypt opcodes and data with related mappings. Only 8 of each 16 bits change. Only 12 address bits select the key. MAME's notes call it clearly derived from the Z80-based MC8123 and much weaker. Fewer than 128 bytes of its 16 KB internal RAM are enough to decrypt a whole game. When the battery dies this CPU stops ([MAME: sega/fd1089.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/fd1089.cpp)). Later Sega boards use the 315-5881 crypt device and per-game key chips. They are on the [early 3D](https://tanepiper.github.io/game-frame/arcade/early-3d.md), [console-derived](https://tanepiper.github.io/game-frame/arcade/console-derived.md) and [PC-based](https://tanepiper.github.io/game-frame/arcade/pc-based.md) pages. ### Tools Follow MAME's FD1094 and FD1089 code to decrypt these CPUs. Who extracted the FD1094 keys, and how, is not confirmed from a primary source. ### Projects to learn from None listed yet. ### For an exact reimplementation - An FD1094 program can switch state at run time. A static decrypt needs the state of each code region. Take ground truth from a running emulator. - A dump from a dead-battery FD1094 board runs with a wrong key. Check that the set in use holds the right key. ## Capcom ### Hardware CPS-1 QSound games use Kabuki for the sound Z80. CPS-2 and CPS-3 encrypt the main program. See [Capcom CPS](https://tanepiper.github.io/game-frame/arcade/capcom-cps.md) for the boards. ### Protection **Kabuki** is a custom Z80 module that runs encrypted code with its key in battery-backed RAM. With the battery removed, pulling pin 28 low makes it behave as a normal Z80. The cipher uses swaps of adjacent bit pairs, rotations and an XOR. It treats opcodes and data differently ([MAME: capcom/kabuki.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/kabuki.cpp)). **CPS-2** encrypts its main program. The key is stored in SRAM inside a battery-powered custom IC. A board with a dead battery shows only a blue or green screen. The driver notes that no CPS-2 game encrypted its sound program. Kabuki parts still appear as the sound Z80 on some boards ([MAME: capcom/cps2.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps2.cpp)). Andreas Naive broke the CPS-2 algorithm. Nicola Salmoria wrote the code. Charles MacDonald and Razoola extracted data from the hardware. Only opcodes are encrypted. The cipher is two 4-round Feistel networks over the 16-bit opcode and the low 16 address bits, with a 64-bit key: D = FN2(E, K XOR EX(FN1(A, K))). The watchdog opcodes and the address range are also stored in battery-backed RAM ([MAME: capcom/cps2crypt.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps2crypt.cpp)). The full break came in January 2007 (secondary source) ([Capcom wiki (Wikipedia-derived)](https://capcom.fandom.com/wiki/CPS2)). **CPS-3** decrypts inside the SH-2 derivative on the security cartridge. The key is stored in battery-powered SRAM on the CPU die. The CPU monitors voltage and cart removal. It erases the key on tampering. A dead cartridge is thought to fall back to keys identical to SFIII 2nd Impact. Reflashing verified this. The data on the CD and SIMMs is not encrypted beyond the boot flash and the program areas ([MAME: capcom/cps3.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps3.cpp)). CPS-1 B-boards use protected PALs. MAME's ROM definitions hold images of the PAL type found on the boards ([MAME: capcom/cps1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps1.cpp)). ### Tools Collectors make dead CPS-2 boards work again in several ways (secondary source) ([Capcom wiki](https://capcom.fandom.com/wiki/CPS2); [shmups.system11.org forum](https://shmups.system11.org/viewtopic.php?p=1390355); [MiSTer forum: Phoenix ROMs](https://mail.misterfpga.org/viewtopic.php?p=36552)). - "Phoenix" ROM sets are modified game data. They run decrypted on boards whose key was lost. Their title screens show "PHOENIX" labels. - Decrypted but otherwise unmodified sets exist, for example "Avalaunch" sets. - Hardware key writers exist, for example InfiniKey. In September 2016 ArcadeHacker, with Eduardo Cruz and others, published a CPS-2 "desuicide" method. To make it, they reverse engineered the key-loading protocol. The code is on GitHub. It needs only an Arduino and test clips to write a key back. A follow-up on 3 April 2017 described at least 11 types of Capcom custom security silicon ([Hackaday: desuicide tag](https://hackaday.com/tag/desuicide)). ### Projects to learn from MAME's `cps2crypt.cpp` describes the cipher in full. The ArcadeHacker desuicide code shows the key-loading protocol. ### For an exact reimplementation - A Phoenix set is a modified program. It is a poor reference for exact behaviour. Use a set that holds the original encrypted program and its key. - A CPS-3 game needs the boot flash and program areas decrypted. The CD and SIMM data needs no further decryption. ## Konami ### Hardware Konami-1 is a 6809 with scrambled opcodes ([MAME: konami/konami1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/konami/konami1.cpp)). ### Protection Opcode bytes are XORed with 0x22, 0x82, 0x28 or 0x88. The value depends on address bits 1 and 3 (`pc & 0xa`). Data bytes are left alone. ### Tools MAME's Konami-1 device is a 6809 core with a decrypting disassembler (`SPLIT_DECRYPTION`). The MAME debugger shows decrypted opcodes. Ghidra has no Konami-1 module. Decrypt the opcodes first. Then load the result as 6809 code ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-6809)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Data bytes are plain. A decrypted image must keep opcodes and data apart, because the XOR applies only to opcode fetches. ## SNK Neo Geo ### Hardware Late Neo Geo games have protection chips. MAME lists them ([MAME: snk/neogeo.cpp](https://github.com/mamedev/mame/blob/master/src/mame/snk/neogeo.cpp)). Example sets are The King of Fighters '99, 2000 and 2003. ### Protection - **NEO-CMC 90G06CF7042** decrypts the C-ROM graphics and bank-switches the S-ROM. - **NEO-CMC 90G06CF7050** adds decryption of the M-ROM sound program. - **NEO-PCM2** (SNK 1999 and Playmore 2002) decrypts V-ROM samples and decodes the P-ROM. - **NEO-SMA** decrypts and bank-switches the P-ROM program. It provides a random number generator. It stores 256 kb of game data. ### Tools MAME's Neo Geo driver models each chip. Whether the NEO-CMC keys came from analysis or from bootlegs is not confirmed. ### Projects to learn from None listed yet. ### For an exact reimplementation - A random number generator inside a protection chip is part of the game's behaviour. Reproduce it from MAME's model. ## Protection MCUs ### Hardware Many makers used protection MCUs. | Maker | Board or game | MCU | |---|---|---| | Taito | Bubble Bobble | M68705P3 or M6801U4 | | Data East | DEC0 | Intel 8751, M68705R3 | | Irem | M72 | Intel 8751 or 80C31 | | Midway | W-unit | PIC16C57 | | Sega | System 16B | Intel 8751 | Sources: [MAME: taito/bublbobl.cpp](https://github.com/mamedev/mame/blob/master/src/mame/taito/bublbobl.cpp); [dataeast/dec0.cpp](https://github.com/mamedev/mame/blob/master/src/mame/dataeast/dec0.cpp); [irem/m72.cpp](https://github.com/mamedev/mame/blob/master/src/mame/irem/m72.cpp); [williams/midwunit.cpp](https://github.com/mamedev/mame/blob/master/src/mame/williams/midwunit.cpp). ### Protection A protection MCU often holds game logic such as enemy behaviour or lookup tables. If its ROM was never dumped, MAME may use a simulation. ### Tools Ghidra has built-in 8051 and PIC modules. Its MC6800 module holds the 6800 and 6805 specs. How the Taito 68705 ROMs were dumped is not covered here. ### Projects to learn from None listed yet. ### For an exact reimplementation - Check whether MAME runs the MCU's dumped ROM or a simulation. A port built on a simulation has the same errors as the simulation. - Treat the MCU program as part of the game. Trace it like the main CPU. --- # Discrete logic and the 8080 The first arcade games of 1972 to 1978 come in two kinds. Pong and Breakout have no CPU. To reimplement them, you simulate the circuit. Space Invaders and the Midway 8080 boards run an Intel 8080 program beside analog sound circuits. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | Atari Pong, Breakout | 1972 on | None (discrete logic) | Not covered here | Not covered here | None | Netlist emulation | Nothing to load | | Midway 8080 black-and-white | 1978 on | Intel 8080 at 1.9968 MHz | Analog circuits plus TI SN76477 | about 59.54 Hz (computed) | None listed | In MAME | Built in (`8085`) | ## Atari Pong and Breakout ### Hardware Pong (Atari, 1972) has no CPU. MAME emulates it as a netlist of discrete logic. Breakout uses the same approach ([MAME: atari/pong.cpp](https://github.com/mamedev/mame/blob/master/src/mame/atari/pong.cpp)). ### Protection None listed. ### Tools MAME's netlist driver models the circuit. There is no program to decompile, so Ghidra and Unicorn do not apply. ### Projects to learn from MAME's `pong.cpp` netlist. ### For an exact reimplementation - Simulate the circuit. The game's behaviour comes from the logic gates, the counters and their timing. - Use MAME's netlist as the description of the circuit. ## Midway 8080 boards and Space Invaders ### Hardware Space Invaders (Taito, 1978; licensed to Midway in the US) uses an Intel 8080 CPU. Tomohiro Nishikado designed it. The game draws sprites with the help of a Fujitsu MB14241 shifter. Sound comes from analog circuits plus a TI SN76477 ([Wikipedia (mirror): Space Invaders](https://static.hlt.bme.hu/semantics/external/pages/SOAR/en.wikipedia.org/wiki/Space_Invaders.html)). Giant Bomb gives the Japanese release as 19 June 1978. Another source says November 1978 ([Giant Bomb releases](https://giantbomb.com/wiki/Games/Space_Invaders/Releases)). MAME's Midway 8080 black-and-white hardware uses a 19.968 MHz master clock. The CPU runs at master / 10, which is 1.9968 MHz. The pixel clock is master / 4. With totals of 320 by 262 the screen runs at about 59.54 Hz (computed) ([MAME: midw8080/mw8080bw.h](https://github.com/mamedev/mame/blob/master/src/mame/midw8080/mw8080bw.h)). ### Protection None listed. ### Tools - Ghidra decodes the 8080 with its built-in `8085` module. - Unicorn has no 8080 backend. Take ground truth from MAME's debugger or from a small 8080 core ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-8080-z80-and-sm83)). - The [practices page](https://tanepiper.github.io/game-frame/arcade/practices.md#disassembly-and-emulation-support-per-arcade-cpu) has the full CPU table. ### Projects to learn from Computer Archeology hosts commented disassemblies of Space Invaders and Sea Wolf ([Computer Archeology: Arcade](https://www.computerarcheology.com/Arcade/)). ### For an exact reimplementation - Run the port at about 59.54 Hz, the board's computed rate. - Reproduce the MB14241 shifter as the game uses it. - The sound is part circuit and part chip. An exact port needs a model of the analog sound as well as the 8080 code ([Sound](https://tanepiper.github.io/game-frame/hardware/sound.md)). --- # Z80 and 6502 boards The boards of 1979 to the mid-1980s run Z80 or 6502 programs, often with several CPUs on one board. Namco and Nintendo boards use timings based on 18.432 MHz and run at about 60.61 Hz. Atari's vector games draw lines with the beam of a vector display. Nintendo's arcade systems reuse NES chips. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | Namco Galaxian | 1979 | Z80 at 3.072 MHz | Not covered here | about 60.61 Hz (computed) | None listed | In MAME | Z80 built in | | Namco Pac-Man | 1980 | Z80 at 3.072 MHz | Namco WSG | about 60.61 Hz (computed) | None listed | In MAME | Z80 built in | | Namco Galaga | 1981 | Three Z80s at 3.072 MHz | Namco 54xx noise | about 60.61 Hz (computed) | None listed | In MAME | Z80 built in | | Nintendo Donkey Kong | 1981 | Z80 at 4 MHz | Intel 8035 at 6 MHz | about 60.61 Hz (computed) | None listed | In MAME | Z80, 8048 built in | | Atari Asteroids | 1979 | 6502 at 1.512 MHz | POKEY on Asteroids Deluxe | about 61.5 Hz (computed) | None listed | In MAME | 6502 built in | | Sega System 1 | 1980s | Z80 at 4 MHz | Sound CPU, two SN76489A | about 60.10 Hz (computed) | Encrypted Z80 parts, MC8123 | In MAME | Z80 built in (after decryption) | | Nintendo VS. System | 1980s | RP2A03 | In the RP2A03 | NTSC line count | Not covered here | In MAME | 6502 built in | | Nintendo PlayChoice-10 | 1980s | Z80 at 4 MHz menu, plus NES hardware | NES | nominal 60 Hz | Not covered here | In MAME | Z80, 6502 built in | ## Namco Galaxian, Pac-Man and Galaga ### Hardware Namco's early boards use the same 18.432 MHz crystal ([MAME: galaxian/galaxian.h](https://github.com/mamedev/mame/blob/master/src/mame/galaxian/galaxian.h); [galaxian.cpp](https://github.com/mamedev/mame/blob/master/src/mame/galaxian/galaxian.cpp)). - **Galaxian (1979)** uses an 18.432 MHz master clock with a Z80 at 3.072 MHz. Its 384 by 264 totals give about 60.61 Hz (computed). Sidam bootlegs use a 12 MHz crystal instead. - **Pac-Man (Namco, 1980; Midway licence in the US)** has one Z80 at 3.072 MHz and Namco's wavetable sound (WSG). Its screen line is `set_raw(18.432 MHz / 3, 384, 0, 288, 264, 0, 224)`. That gives 288 by 224 visible pixels at about 60.61 Hz (computed) ([MAME: pacman/pacman.cpp](https://github.com/mamedev/mame/blob/master/src/mame/pacman/pacman.cpp)). - **Galaga (1981)** has three Z80s at 3.072 MHz each. Namco custom chips 51xx (I/O) and 54xx (noise sound) run at 1.536 MHz. They communicate through a 06xx bus interface. The timing matches Pac-Man ([MAME: namco/galaga.cpp](https://github.com/mamedev/mame/blob/master/src/mame/namco/galaga.cpp)). ### Protection None listed. ### Tools Ghidra decodes the Z80 with its built-in module. Unicorn has no Z80 backend. Take ground truth from MAME's debugger and Lua, or from a small Z80 core ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-8080-z80-and-sm83)). ### Projects to learn from Computer Archeology hosts commented disassemblies of Galaga, with the main, secondary and sound CPUs on separate pages. It also has Frogger, Scramble, Time Pilot, Crazy Climber, Phoenix and Moon Patrol. Its menu has code or hardware pages for Pooyan and The Pit ([Computer Archeology: Arcade](https://www.computerarcheology.com/Arcade/)). ### For an exact reimplementation - Run the port at about 60.61 Hz. - On Galaga, run the three CPUs interleaved in the right order. Some of the game's behaviour depends on this timing. - Model the 51xx and 54xx custom chips as the CPUs see them. ## Nintendo Donkey Kong ### Hardware Donkey Kong (1981) uses a 61.44 MHz crystal and a 6.144 MHz pixel clock, for about 60.61 Hz (computed). Its Z80 runs at 4 MHz, "verified in schematics". An Intel 8035 at 6 MHz runs the sound program ([MAME: nintendo/dkong.h](https://github.com/mamedev/mame/blob/master/src/mame/nintendo/dkong.h); [dkong.cpp](https://github.com/mamedev/mame/blob/master/src/mame/nintendo/dkong.cpp)). ### Protection None listed. ### Tools Ghidra has built-in Z80 and 8048 modules. Unicorn has neither. ### Projects to learn from Computer Archeology's menu has a Donkey Kong page ([Computer Archeology: Arcade](https://www.computerarcheology.com/Arcade/)). ### For an exact reimplementation - Trace the 8035 sound program as well as the Z80 game program. ## Atari Asteroids and the vector games ### Hardware Asteroids (1979) draws on a vector display. Its master clock is 12.096 MHz, with a 6502 at 1.512 MHz. MAME sets the vector refresh to (12.096 MHz / 4096) / 12 / 4, which is about 61.5 Hz (computed). Lunar Lander divides by 6 instead of 4. Asteroids Deluxe adds a POKEY at master / 8 ([MAME: atari/asteroid.cpp](https://github.com/mamedev/mame/blob/master/src/mame/atari/asteroid.cpp)). ### Protection None listed. ### Tools Ghidra decodes the 6502 with its built-in module. Its p-code for `ADC` is not exact, so do not use Ghidra's emulation as ground truth ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#ghidras-6502-adc-is-not-exact)). Unicorn has no 6502 backend. ### Projects to learn from - The historicalsource account holds original assembly source for Asteroids. It includes VECAN.XX, VECMAC.XX and VECINC.XX for the vector generator, with a map file ASTROD.MAP ([historicalsource/asteroids](https://github.com/historicalsource/asteroids)). - The same account holds Asteroids Deluxe, Battlezone, Black Widow, Gravitar, Lunar Lander, Major Havoc (a disassembly), Quantum, Red Baron, Space Duel, Star Wars and Tempest ([GitHub: historicalsource](https://github.com/historicalsource?tab=repositories)). The repositories give no licence or provenance statement. - Computer Archeology has commented disassemblies of Asteroids and Omega Race, and a Tempest page ([Computer Archeology: Arcade](https://www.computerarcheology.com/Arcade/)). ### For an exact reimplementation - A vector game draws a list of beam strokes each frame. Compare these lists before you compare pixels. - Run the port at the vector refresh, about 61.5 Hz on Asteroids. Lunar Lander uses a different divisor. ## Sega System 1 ### Hardware Sega System 1 (Flicky, Choplifter and others) uses a Z80 at 4 MHz from a 20 MHz master clock. Its sound CPU and two SN76489A run at 20 / 8 or 20 / 16, depending on the board revision. The screen runs at about 60.10 Hz (computed) ([MAME: sega/system1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/system1.cpp)). ### Protection System 1 uses many encrypted Z80 parts (315-5033, 315-5041, 315-5048, 315-5051, 315-5065, 315-5098, 315-5178 and others) and the MC8123 ([protection](https://tanepiper.github.io/game-frame/arcade/protection.md#sega)). ### Tools Decrypt the program with MAME's model of the part. Then load it as Z80 code in Ghidra. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the board revision. The sound clock divisor depends on it. - Use the decryption for the exact encrypted part on the board. ## Nintendo VS. System and PlayChoice-10 ### Hardware Nintendo's arcade systems reuse NES chips. The **VS. System** uses an RP2A03 CPU and a 2C0x PPU with 341 dots per line and the NTSC line count. Dual-screen versions use two CPUs and two screens ([MAME: nintendo/vsnes.cpp](https://github.com/mamedev/mame/blob/master/src/mame/nintendo/vsnes.cpp)). The **PlayChoice-10** runs its menu on a Z80 at 4 MHz beside the NES hardware. Both of its screens are set to a nominal 60 Hz ([MAME: nintendo/playch10.cpp](https://github.com/mamedev/mame/blob/master/src/mame/nintendo/playch10.cpp)). ### Protection Not covered here. ### Tools A VS. game's code is NES code, so NES tools work on it ([tools for console work](https://tanepiper.github.io/game-frame/consoles/tools.md)). ### Projects to learn from None listed yet. ### For an exact reimplementation - On a dual-screen VS. game, run both CPUs and both screens. - The PlayChoice-10 rate is nominal in MAME. Treat it as unknown. --- # Williams and Midway Williams built 6809 boards for Defender and Robotron. Midway's Y, T and W-units ran their games on the TMS34010 graphics CPU at the slowest refresh of the late 2D boards. Much of the original assembly source for these games is now public. Midway Zeus, the later 3D board, runs its game on a TI DSP with a custom 3D controller. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | Williams 6809 | 1980 on | MC6809E at 1 MHz | M6802 or M6808 sound CPU | about 60.10 Hz (computed) | None listed | In MAME | Built in (MC6800 module) | | Midway Y-unit | 1988 on | TMS34010 at 40, 48 or 50 MHz | Williams CVSD or ADPCM, Z80 at 4 MHz, MSM6295 | about 54.71 Hz (computed) | None listed | In MAME | Extension | | Midway T-unit | 1992 on | TMS34010 at 50 MHz | DCS (2K variant) | Not covered here | None listed | In MAME | Extension | | Midway W-unit | 1994 on | TMS34010 | DCS (8K variant) on an ADSP-2105 at 10 MHz | Not covered here | PIC16C57 | In MAME | Extension (TMS34010); none built in for the ADSP | | Midway Zeus | 1997 on | TI TMS32C032 DSP at 60 MHz | Not covered here | Not covered here | None listed | Imperfect (2018 talk) | None built in | ## Williams 6809 boards ### Hardware Williams' 6809 hardware ran Defender (1980) and Robotron 2084 (1982, Williams and Vid Kidz) ([MAME: williams/williams.cpp](https://github.com/mamedev/mame/blob/master/src/mame/williams/williams.cpp)). - The MC6809E runs at 1 MHz. - The pixel clock is 8 MHz. Totals of 512 by 260 give about 60.10 Hz (computed). - Early games use an M6802 sound CPU. Later ones use an M6808. - The memory map puts a blitter at CA00 to CA07. The driver notes that "Stargate and Defender do not have blitter". - A watchdog is at CBFF. - 1K by 4 bits of battery-backed CMOS RAM at CC00 to CFFF holds settings and high scores. ### Protection None listed. ### Tools Ghidra's MC6800 module holds the 6809 spec ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-6809)). Unicorn has no 6809 backend. Take ground truth from MAME's debugger and Lua, or from a small 6809 core. ### Projects to learn from - Defender's source is 6809 assembly files such as AMODE1.SRC, DEFA7.SRC and ROMC0.SRC ([historicalsource/defender](https://github.com/historicalsource/defender)). - Robotron's is .ASM files such as RRSCRIPT.ASM, RRTEST1.ASM and RRTABLE.ASM, including diagnostic modules ([historicalsource/robotron](https://github.com/historicalsource/robotron)). - The same account holds Joust, Sinistar and Stargate ([GitHub: historicalsource](https://github.com/historicalsource?tab=repositories)). - Computer Archeology has a commented disassembly of Defender ([Computer Archeology: Arcade](https://www.computerarcheology.com/Arcade/)). The repository descriptions are short game blurbs. The repositories give no licence or provenance statement. No primary source shows an official release of the Defender or Robotron source by Williams or Eugene Jarvis. GameFrame's rule against game material means a project does not copy this source into its own repository. Treat it as third-party evidence ([Legal](https://tanepiper.github.io/game-frame/legal.md)). ### For an exact reimplementation - Load the CMOS settings and high scores as an input to each test ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#nvram-and-dip-settings-as-test-inputs)). - Model the blitter on games that have it. Defender and Stargate do not. - The source shows what the programmers meant. It may not match the final revision, so use the shipped ROM for exact behaviour. ## Midway Y-unit and Z-unit ### Hardware The Y-unit ran Smash TV (1990) and Mortal Kombat. NARC (1988) ran on the related Z-unit ([MAME: williams/midyunit.cpp](https://github.com/mamedev/mame/blob/master/src/mame/williams/midyunit.cpp)). - The TMS34010 graphics CPU runs at 40 MHz on "slow" games: Smash TV, Trog and High Impact. - It runs at 48 MHz on "fast" games: NARC, MK, Total Carnage and Strike Force. - It runs at 50 MHz on Terminator 2. - Sound is Williams CVSD or ADPCM. The ADPCM sets have a Z80 at 4 MHz and an MSM6295. - Standard-resolution timing uses 506 by 289 totals, about 54.71 Hz (computed). MAME sets the TMS34010's input clock. The chip's effective instruction rate is a fraction of that clock (not confirmed). ### Protection None listed. ### Tools - The [Ghidra_TMS34010](https://github.com/ZPaul2Fresh8/Ghidra_TMS34010) extension adds the TMS34010. Its completeness is not confirmed. - Ghidra has no built-in TMS34010 module. Unicorn has none either. Use MAME to produce ground truth. ### Projects to learn from - Smash TV's source includes ADJUST.ASM, AUDIT.ASM, DEMO.ASM, DIAG.ASM, HSTD.ASM for the high-score table, and a MAKEFILE ([historicalsource/smashtv](https://github.com/historicalsource/smashtv)). - The historicalsource account also holds NARC, Total Carnage and Trog ([GitHub: historicalsource](https://github.com/historicalsource?tab=repositories)). ### For an exact reimplementation - Run the port at about 54.71 Hz. At 60 Hz a port shows about 317 more frames per minute ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#refresh-rates-and-frame-drift)). - Record which clock the game uses: 40, 48 or 50 MHz. - Rebuild attract mode, audits, adjustments and diagnostics. They are separate modules in the source. ## Midway T-unit ### Hardware The T-unit ran Mortal Kombat (1992) and NBA Jam (1993). It runs the TMS34010 at 50 MHz with a 4 MHz pixel clock. Sound is DCS (2K variant) ([MAME: williams/midtunit.cpp](https://github.com/mamedev/mame/blob/master/src/mame/williams/midtunit.cpp)). ### Protection None listed. ### Tools As for the Y-unit. ### Projects to learn from - NBA Jam's source includes ATTRACT.ASM, AUDIT.ASM, ADJUST.ASM, DIAG.ASM, DIPSW.EQU, DIPEQU.ASM and batch build files ([historicalsource/nba-jam](https://github.com/historicalsource/nba-jam)). - The historicalsource account also holds Mortal Kombat II, NBA Jam Tournament Edition, NBA Hangtime, Open Ice and Revolution X ([GitHub: historicalsource](https://github.com/historicalsource?tab=repositories)). ### For an exact reimplementation - Read the DIP switch equates in the source as a list of settings to record per test. - Model the DCS sound board as a second program. ## Midway W-unit ### Hardware The W-unit ran Mortal Kombat 3 (1994) and WWF WrestleMania (1995). It adds an ADSP-2105 at 10 MHz for DCS sound (8K variant) ([MAME: williams/midwunit.cpp](https://github.com/mamedev/mame/blob/master/src/mame/williams/midwunit.cpp)). ### Protection The board has a PIC16C57 security chip. Ghidra has a built-in PIC module ([protection](https://tanepiper.github.io/game-frame/arcade/protection.md#protection-mcus)). ### Tools As for the Y-unit. Ghidra has no built-in ADSP-21xx module. ### Projects to learn from None listed beyond the T-unit sources. ### For an exact reimplementation - Check whether MAME runs the PIC16C57's dumped program or a simulation. - The ADSP-2105 sound program uses a different instruction set. Trace it separately. ## Midway Zeus ### Hardware Midway Zeus (1997) ran Mortal Kombat 4 and Invasion. Its main CPU is a TI TMS32C032 DSP at 60 MHz. The Zeus chip is a Midway custom 3D controller. It has 512 registers in the CPU's address space and 16 MB of Wave RAM in two banks ([MAME wiki, Midway Zeus](https://wiki.mamedev.org/index.php/Midway_Zeus)). Zeus 2 renders to a 32 bpp framebuffer with a different register layout ([Aaron Giles, December 2007 log](https://aarongiles.com/old/?m=200712)). A 2018 talk describes Zeus as a microcoded VLIW design at 66 MHz ([Reverse Engineering Zeus Part 1 (siliconpr0n, 2018)](https://siliconpr0n.org/media/mtvre/2018-03-14_Reverse_Engineering_Zeus_Part_1.pdf)). ### Protection None listed. ### Tools - The 2018 talk calls MAME's support imperfect. The driver's current flags are unknown. - Ghidra has no built-in TMS320 module. Unicorn has none either. ### Projects to learn from - Aaron Giles used Cruis'n Exotica and The Grid as his Zeus 2 test cases. - The siliconpr0n talk on reverse engineering Zeus. ### For an exact reimplementation - Game logic runs on a DSP. Plan for a TMS320 disassembler or a small core. - Check MAME's Zeus output against another record before trusting it as ground truth. --- # Atari coin-op boards Atari moved from the 6502 to the 68000 family and to other CPUs. System 1 is a BIOS board with game cartridges. System 2 uses a DEC T-11, which no disassembler listed here supports. The GT board runs a 68EC020. Each Hard Drivin' game runs on a 68010, TMS34010s and polygon DSPs. The 6502 vector games are on the [Z80 and 6502](https://tanepiper.github.io/game-frame/arcade/z80-and-6502-era.md#atari-asteroids-and-the-vector-games) page. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | Atari System 1 | 1984 | MC68010 at 7.16 MHz | 6502 at 1.79 MHz, YM2151, POKEY, TMS5220C | about 59.92 Hz (computed) | None listed | In MAME | 68000 built in | | Atari System 2 | 1984 | DEC T-11 at 10 MHz | As System 1 | about 60.10 Hz (computed) | None listed | In MAME | None for the T-11 | | Atari Hard Drivin' | 1988 | M68010 at 8 MHz | Not covered here | Not covered here | None listed | 55 entries, one not working | 68000 built in; extension for the TMS34010; none for the DSPs | | Atari GT | 1994 | MC68EC020 at 25 MHz | CAGE board | about 59.92 Hz (computed) | None listed | In MAME | 68000 built in | ## Atari System 1 ### Hardware System 1 (1984) is a BIOS board plus game cartridges. Its first game was Peter Pack Rat. It uses an MC68010 at 7.16 MHz. A 6502 sound CPU runs at 1.79 MHz with a YM2151, a POKEY and a TMS5220C speech chip. Its screen is 336 by 240 visible at about 59.92 Hz (computed) ([MAME: atari/atarisy1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/atari/atarisy1.cpp)). ### Protection None listed. ### Tools - Ghidra's built-in 68000 module covers the MC68010. Unicorn's M68K backend runs it ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-68000)). - The 6502 sound CPU has a Ghidra module and no Unicorn backend. - The [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md) skill covers Unicorn ground truth. ### Projects to learn from None listed yet. ### For an exact reimplementation - The ROM set includes a BIOS set. Record which BIOS each test uses ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#rom-sets-and-chd)). - Treat the 6502 sound program as a second program to trace. ## Atari System 2 ### Hardware System 2 (1984) ran Paperboy. It uses a DEC T-11 at 10 MHz with the same sound chips as System 1. Its medium-resolution screen shows 512 by 384 at about 60.10 Hz (computed) ([MAME: atari/atarisy2.cpp](https://github.com/mamedev/mame/blob/master/src/mame/atari/atarisy2.cpp)). ### Protection None listed. ### Tools The DEC T-11 is related to the PDP-11. Ghidra's bundled processors and Unicorn's architectures do not include it. Use MAME to produce ground truth. ### Projects to learn from None listed yet. ### For an exact reimplementation - Plan for a T-11 disassembler or a small core. Neither tool listed here supports the CPU. ## Atari Hard Drivin' ### Hardware Hard Drivin' (1988) runs an M68010 at 8 MHz from a 32 MHz crystal. Graphics run on a TMS34010 "GSP" from a 48 MHz crystal. Some boards add a second TMS34010 "MSP" at 50 MHz. The polygon DSPs differ by board ([MAME harddriv.cpp](https://github.com/mamedev/mame/blob/master/src/mame/atari/harddriv.cpp); [MAME harddriv.h](https://github.com/mamedev/mame/blob/master/src/mame/atari/harddriv.h)). - An ADSP-2100 at 32 MHz / 4. - An ADSP-2101 at 12 MHz. - Two ADSP-2105 at 10 MHz on the DS III board. - A DSP32C at 40 MHz on some boards. ### Protection None listed. ### Tools - The driver holds 55 entries, starting with "Hard Drivin' (cockpit, rev 7)". One has MACHINE_NOT_WORKING. - Ghidra's 68000 module covers the 68010. The [Ghidra_TMS34010](https://github.com/ZPaul2Fresh8/Ghidra_TMS34010) extension covers the GSP and MSP. Ghidra has no built-in module for the ADSP-21xx or the DSP32C. ### Projects to learn from None listed yet. ### For an exact reimplementation - Expect three or more instruction sets per game. The game logic usually runs on the main CPU. A port must also reproduce the DSP programs ([early 3D boards](https://tanepiper.github.io/game-frame/arcade/early-3d.md)). - Record which board variant and which DSP the set uses. ## Atari GT ### Hardware The GT board (1994) ran T-MEK and Primal Rage. It uses an MC68EC020 at 25 MHz. Sound is on Atari's CAGE board. The screen runs at about 59.92 Hz (computed) ([MAME: atari/atarigt.cpp](https://github.com/mamedev/mame/blob/master/src/mame/atari/atarigt.cpp)). ### Protection None listed. ### Tools Ghidra's 68000 module is a 68040 superset with 68020 and 68030 languages. Unicorn's M68K backend runs the code. ### Projects to learn from None listed yet. ### For an exact reimplementation - Trace the program on the CAGE sound board separately. ## Atari source releases The historicalsource account holds original assembly source for many Atari coin-op games: Asteroids, Asteroids Deluxe, Battlezone, Black Widow, Centipede, Crystal Castles, Food Fight, Gravitar, Liberator, Lunar Lander, Major Havoc (a disassembly), Millipede, Missile Command, Pole Position, Quantum, Red Baron, Space Duel, Sprint, Star Wars and Tempest. It also has an "atari-coin-op-assembler" repository ([GitHub: historicalsource](https://github.com/historicalsource?tab=repositories)). The repositories give no licence or provenance statement. GameFrame's rule against game material means a project does not copy this source into its own repository. Treat it as third-party evidence. Use the shipped ROM for exact behaviour ([Legal](https://tanepiper.github.io/game-frame/legal.md)). --- # Sega 2D boards Sega's 68000 boards of the late 1980s ran beat 'em ups and sprite-scaling games. Many of them encrypt the program inside a custom 68000, the FD1089 or the FD1094. System 18 has one of the few refresh rates in MAME marked "verified on pcb". Sega System 1, the earlier Z80 board, is on the [Z80 and 6502](https://tanepiper.github.io/game-frame/arcade/z80-and-6502-era.md#sega-system-1) page. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | System 16B | 1988 (Altered Beast) | 68000 at 10 MHz | Z80 at 5 MHz, YM2151 at 4 MHz | about 60.05 Hz (computed) | FD1089A, FD1089B, FD1094, MC8123, 8751 | In MAME | 68000 built in (after decryption) | | System 18 | Not covered here | 68000 at 10 MHz | Z80 at 8 MHz, YM3438, RF5C68 | 57.23 Hz (measured) | Not covered here | In MAME | 68000 built in | | X Board | Not covered here (After Burner) | Two 68000s at 12.5 MHz | Not covered here | about 59.64 Hz (computed) | Not covered here | In MAME | 68000 built in | | Y Board | 1988 | Three 68000s at 12.5 MHz | Not covered here | nominal 60 Hz | Not covered here | In MAME | 68000 built in | ## System 16B ### Hardware System 16B ran Altered Beast (1988). It uses a 68000 at 10 MHz, a Z80 at 5 MHz, a YM2151 at 4 MHz and a 315-5195 memory mapper. It shows 320 by 224 at about 60.05 Hz (computed) ([MAME: sega/segas16b.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/segas16b.cpp)). ### Protection System 16B configurations in MAME include FD1089A, FD1089B, FD1094, an MC8123 sound Z80 and an Intel 8751 protection MCU. The [protection page](https://tanepiper.github.io/game-frame/arcade/protection.md#sega) describes each cipher. - The FD1094 decrypts program-space words only. It has 256 internal states that the program can switch. - The FD1089 encrypts opcodes and data. Fewer than 128 bytes of its internal RAM are enough to decrypt a whole game. ### Tools - Ghidra's 68000 module and Unicorn's M68K backend work on the program after decryption ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-68000)). - Follow MAME's `fd1094.cpp` and `fd1089.cpp` to decrypt the program. - The Intel 8751 MCU has a built-in Ghidra 8051 module. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record which encrypted CPU the set uses. - On an FD1094 game, take ground truth from a running emulator. A static decrypt needs the state of each code region. - Model the 315-5195 memory mapper as the game programs it. ## System 18 ### Hardware System 18 has a 68000 at 10 MHz, a Z80 at 8 MHz, a YM3438 and an RF5C68 PCM chip. Its refresh is 57.23 Hz, "verified on pcb" ([MAME: sega/segas18.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/segas18.cpp)). ### Protection Not covered here. ### Tools As for System 16B. ### Projects to learn from None listed yet. ### For an exact reimplementation - Run the port at 57.23 Hz. MAME's value for this rate was measured on a real board. ## X Board ### Hardware The X Board ran After Burner and other sprite-scaling games ([MAME: sega/segaxbd.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/segaxbd.cpp)). - Two 68000s run at 12.5 MHz from a 50 MHz master clock. - The board also has a 315-5248 multiplier and a 315-5250 compare and timer chip. - The screen shows 320 by 224 at about 59.64 Hz (computed). - Some games add Z80s at 8 MHz for link and motor control. ### Protection Not covered here. ### Tools As for System 16B. ### Projects to learn from None listed yet. ### For an exact reimplementation - Model the 315-5248 multiplier and the 315-5250 chip as the CPUs see them. The game logic uses their results. - Run both 68000s interleaved. - On a game with link or motor Z80s, record what those CPUs send and receive. ## Y Board ### Hardware The Y Board ran Galaxy Force 2 and Power Drift (both 1988). It runs three 68000s at 12.5 MHz. MAME sets its refresh to a nominal 60 Hz with `set_refresh_hz(60)`. Its rate is not derived from raw timing ([MAME: sega/segaybd.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/segaybd.cpp)). ### Protection Not covered here. ### Tools As for System 16B. ### Projects to learn from None listed yet. ### For an exact reimplementation - Treat the Y Board's refresh rate as unknown. Measure it from a board or a recording before relying on it. - Run the three 68000s interleaved. --- # Capcom CPS Capcom's CP System boards ran from 1988 to 2004. CPS-1 and CPS-2 are 68000 boards with an 8 MHz pixel clock and a refresh near 59.64 Hz. CPS-2 encrypts its program with a Feistel cipher keyed from battery-backed RAM. CPS-3 uses an encrypted SH-2 derivative and loads its games from CD. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | CPS-1 | 1988 on | 68000 at 10 MHz, later 12 MHz | Z80 at 3.58 MHz, YM2151, MSM6295; QSound on some | about 59.64 Hz (computed) | Kabuki on QSound games; protected PALs | In MAME | 68000 built in | | CPS-2 | 1993 to 2004 | 68000 at 16 MHz | Z80 at 8 MHz, QSound DSP-16A at 60 MHz | 59.6388 Hz (measured) | Opcode Feistel cipher | In MAME | 68000 built in (after decryption) | | CPS-3 | 1996 to 1999 | SH-2 derivative, modelled at 25 MHz | Not covered here | 59.59 Hz | Encrypted CPU on a security cartridge | In MAME | SuperH built in (after decryption) | ## CPS-1 ### Hardware CPS-1 arrived in 1988 with Forgotten Worlds. Final Fight (1989) and Street Fighter II (1991) followed ([MAME: capcom/cps1.h](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps1.h); [cps1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps1.cpp)). - The 68000 runs at 10 MHz, "verified on pcb". Later boards run it at 12 MHz. - A Z80 at 3.58 MHz drives a YM2151 and an OKI MSM6295 at 1 MHz. - QSound CPS-1 games, such as Warriors of Fate (1992), use a Z80 at 8 MHz with a 250 Hz audio interrupt. - The screen uses an 8 MHz pixel clock with 512 by 262 totals and 384 by 224 visible. That gives about 59.64 Hz (computed). ### Protection QSound CPS-1 games use the Kabuki custom Z80 for sound ([protection](https://tanepiper.github.io/game-frame/arcade/protection.md#capcom)). CPS-1 B-boards use protected PALs. MAME's ROM definitions hold images of the PAL type found on the boards. CPS-1 drivers include MAME's serial EEPROM device. ### Tools - Ghidra's 68000 module and Unicorn's M68K backend cover the main program ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-68000)). - The Z80 sound program has a Ghidra module and no Unicorn backend. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the 68000 clock of the board revision: 10 MHz or 12 MHz. - Run the port at about 59.64 Hz. At 60 Hz it shows about 22 more frames per minute ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#refresh-rates-and-frame-drift)). - On a QSound game, decrypt the Kabuki sound program first. ## CPS-2 ### Hardware CPS-2 ran from 1993 to 2004. Its first game was Super Street Fighter II. About 50 games in over 170 variants ran on it ([MAME: capcom/cps2.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps2.cpp)). - An A-board holds the 68000, RAM, graphics, sound and I/O. It also has a 93C46 serial EEPROM. - A B-board holds the game ROMs and custom chips. - The boards sit in plastic boxes colour-coded by region. - MAME runs the 68000 at 16 MHz and the Z80 at 8 MHz. - The QSound DSP is "CAPCOM-Q1", an AT&T DSP-16A at 60 MHz. - The driver's PCB notes give VSync 59.6388 Hz and HSync 15.4445 kHz. The sound interrupt is about 249.93 to 250.07 Hz. A figure of 59.1 Hz is sometimes quoted for CPS-2. The driver's measured note says 59.6388 Hz. Its raw timing gives 59.637 Hz (computed). ### Protection CPS-2 encrypts its main program. Only opcodes are encrypted. The cipher is two 4-round Feistel networks with a 64-bit key. The key is stored in SRAM inside a battery-powered custom IC. A board with a dead battery shows only a blue or green screen. No CPS-2 game encrypted its sound program. The [protection page](https://tanepiper.github.io/game-frame/arcade/protection.md#capcom) has the cipher, the Phoenix sets and the desuicide work. ### Tools - Follow MAME's `cps2crypt.cpp` to decrypt the program. - After decryption, Ghidra's 68000 module and Unicorn's M68K backend work on the program. - Ghidra has no built-in module for the DSP-16A. ### Projects to learn from - The CPS-2 decryption team's work in MAME, credited in the driver to Razoola and others. - The ArcadeHacker desuicide code ([Hackaday: desuicide tag](https://hackaday.com/tag/desuicide)). ### For an exact reimplementation - Use a set with the original encrypted program and its key. A Phoenix set is a modified program. - Run the port at 59.6388 Hz, the measured rate. - Load the 93C46 EEPROM image as an input to each test ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#nvram-and-dip-settings-as-test-inputs)). ## CPS-3 ### Hardware CPS-3 ran from late 1996 to 1999 and six games were released on it. Among them are Red Earth (1996) and Street Fighter III (1997) ([MAME: capcom/cps3.cpp](https://github.com/mamedev/mame/blob/master/src/mame/capcom/cps3.cpp)). - The main board holds custom ASICs and 72-pin custom SIMMs of flash ROM. The program SIMMs are 64 Mbit. The graphics and sound SIMMs are 128 Mbit. - A SCSI CD-ROM drive loads the game. - A security cartridge holds a boot flash ROM, NVRAM and the custom CPU. - That CPU is an encrypted Hitachi SH-2 derivative. Decapping showed an SH7010-series (SH7014) core. MAME models it as an SH7604 at 25 MHz. - The screen runs at 42.954545 MHz / 5 and 59.59 Hz. ### Protection The SH-2 derivative decrypts the code. The key is stored in battery-powered SRAM on the CPU die. The CPU erases the key when it detects tampering. A dead cartridge is thought to fall back to keys identical to SFIII 2nd Impact. The data on the CD and SIMMs is not encrypted beyond the boot flash and the program areas. ### Tools - The CD is stored as a CHD. Extract its files with `chdman` first ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#rom-sets-and-chd)). - Ghidra's SuperH module holds SH-1, SH-2 and SH-2A. Unicorn has no SH-2 backend. Take ground truth from MAME. ### Projects to learn from The CPS-3 driver credits David Haywood, Andreas Naive, Tomasz Slanina and ElSemi. ### For an exact reimplementation - Decrypt the boot flash and program areas with MAME's model before disassembly. - Run the port at 59.59 Hz. --- # Neo Geo SNK's Neo Geo ran as an arcade board (MVS) and as a home system (AES). The two differ in timing as well as in BIOS. Late games have protection chips. One of these chips contains a random number generator. Official re-releases run the MVS code under emulation. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | Neo Geo MVS | 1990 to 2004 | 68000 at 12 MHz | Z80 at 4 MHz, YM2610 at 8 MHz | about 59.19 Hz (computed) | NEO-CMC, NEO-PCM2, NEO-SMA on late games | In MAME | 68000 and Z80 built in | | Neo Geo AES | Not covered here | 24.167829 MHz input clock (NTSC) | Not covered here | Not covered here | Not covered here | In MAME; clock marked incorrect | 68000 and Z80 built in | ## Neo Geo MVS ### Hardware The MVS launched in 1990 and was discontinued in 2004. Alpha Denshi (ADK) designed it. It came as 1, 2, 4 and 6-slot motherboards ([MAME: snk/neogeo.h](https://github.com/mamedev/mame/blob/master/src/mame/snk/neogeo.h); [neogeo.cpp](https://github.com/mamedev/mame/blob/master/src/mame/snk/neogeo.cpp)). - A 24 MHz master clock feeds a 68000 at 12 MHz, a Z80 at 4 MHz, a YM2610 at 8 MHz and a 6 MHz pixel clock. - The 384 by 264 totals give about 59.19 Hz (computed). - The hardware can show 320 by 224. Most games are designed for 304 pixels wide. - The board includes NVRAM. MVS motherboards differ by generation. Memory-card headers, coin lockouts, LED displays and soldered BIOS vary between MV-1, MV-1F, MV-1A, MV-1B and MV-1C. ### Protection Late games have protection chips. MAME lists them. Example sets are The King of Fighters '99, 2000 and 2003. - NEO-CMC 90G06CF7042 decrypts the C-ROM graphics and bank-switches the S-ROM. - NEO-CMC 90G06CF7050 adds decryption of the M-ROM sound program. - NEO-PCM2 decrypts V-ROM samples and decodes the P-ROM. - NEO-SMA decrypts and bank-switches the P-ROM program. It provides a random number generator. It stores 256 kb of game data. The [protection page](https://tanepiper.github.io/game-frame/arcade/protection.md#snk-neo-geo) has more on each chip. ### Tools - Ghidra's 68000 and Z80 modules cover the program and the sound code. Unicorn's M68K backend runs the 68000 code ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-68000)). - MAME's driver credits Charles MacDonald's MVS technical notes. ### Projects to learn from Hamster's ACA Neo Geo series re-releases SNK titles in their MVS arcade form. Its emulator team is in Hokkaido. ACA2 Neo Geo was announced in May 2024 ([Wikipedia: Arcade Archives](https://en.wikipedia.org/wiki/Arcade_Archives); [Wikipedia: Hamster Corporation](https://en.wikipedia.org/wiki/Hamster_Corporation)). ### For an exact reimplementation - Run the port at about 59.19 Hz. - Design for a 304-pixel visible width on most games. - Record the BIOS, the region and the language with every test. Fatal Fury 3 in MAME crashes in the ending unless the language is Japanese ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#nvram-and-dip-settings-as-test-inputs)). - Record the motherboard generation when the game uses a feature that differs between generations, such as the memory card. - On a NEO-SMA game, reproduce the chip's random number generator from MAME's model. ## Neo Geo AES ### Hardware The AES home system's input clock is noted as 24.167829 MHz for NTSC. MAME marks its own AES clock as incorrect ([MAME: snk/neogeo.cpp](https://github.com/mamedev/mame/blob/master/src/mame/snk/neogeo.cpp)). ### Protection Not covered here. ### Tools As for the MVS. ### Projects to learn from None listed yet. ### For an exact reimplementation - Pick one system per test. The MVS and AES differ in timing as well as in BIOS. - Do not rely on MAME's AES timing. --- # Other 2D makers This page covers the 2D boards of Namco, Konami, Taito, Irem, Data East, Toaplan and Cave. Most run a 68000 with a Z80 for sound. Each maker has its own crystal, so the refresh rates run from about 55 Hz to about 60.6 Hz. Several boards have a protection MCU. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | Namco System 1 | 1987 on | Three MC6809E at 1.536 MHz | YM2151, CUS30 | about 60.61 Hz (computed) | None listed | In MAME | MC6800 module | | Namco System 2 | Not covered here | Two 68000s at 12.288 MHz | YM2151, C140 | about 60.61 Hz (computed) | None listed | In MAME | 68000 built in | | Konami 68000 (TMNT) | 1989 | 68000 at 8 MHz | Z80 at 3.58 MHz, YM2151 | about 59.19 Hz (computed) | Not covered here | In MAME | 68000 built in | | Taito F2 | Not covered here | 68000 at 12 MHz | Z80 at 4 MHz, YM2610 | nominal 60 Hz | Not covered here | In MAME | 68000 built in | | Taito F3 | 1992 | 68EC020, set to 16 MHz | 68000, ES5505, ES5510 | 58.94 Hz | Not covered here | In MAME; CPU clock marked wrong | 68000 built in | | Irem M72 | 1987 | NEC V30 at 8 MHz | Z80, YM2151, DAC | about 55.02 Hz (computed) | Intel 8751 or 80C31 | In MAME | x86 16-bit (not confirmed) | | Irem M92 | 1991 | NEC V33 at 9 MHz | NEC V35 at 14.318 MHz | about 60.01 Hz (computed) | Not covered here | In MAME | x86 16-bit (not confirmed) | | Data East DEC0 | Not covered here | 68000 at 10 MHz | 65C02, YM2203, YM3812, MSM6295 | about 57.44 Hz (computed) | Intel 8751, M68705R3 | In MAME | 68000 built in | | Toaplan 1 | 1988 | 68000 at 10 MHz | Z80 at 3.5 MHz, YM3812 | Not covered here | Not covered here | In MAME | 68000 built in | | Cave 68000 | 1994 on | 68000 at 16 MHz | YMZ280B, or MSM6295, or Z80 and YM2151 | about 57.55 Hz | Not covered here | In MAME | 68000 built in | ## Namco System 1 and 2 ### Hardware Namco System 1 (from 1987) runs three MC6809E at 1.536 MHz for the main, sub and sound tasks. An HD63701 MCU runs at 6.144 MHz. Sound is a YM2151 plus Namco's CUS30 wavetable. The screen runs at Namco's usual 60.61 Hz (computed) ([MAME: namco/namcos1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/namco/namcos1.cpp)). Namco System 2 uses two 68000s at 12.288 MHz, a YM2151 and a C140 PCM chip. It shows 288 by 224 at about 60.61 Hz (computed) ([MAME: namco/namcos2.cpp](https://github.com/mamedev/mame/blob/master/src/mame/namco/namcos2.cpp)). ### Protection None listed. ### Tools - Ghidra's MC6800 module holds the 6809 spec. Unicorn has no 6809 backend ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#the-6809)). - Ghidra's 68000 module and Unicorn's M68K backend cover System 2. ### Projects to learn from None listed yet. ### For an exact reimplementation - Run the three System 1 CPUs and the HD63701 interleaved. - Run the port at about 60.61 Hz. ## Konami 68000 boards ### Hardware Konami's 68000 boards, such as TMNT (1989), use a 68000 at 8 MHz, a Z80 at 3.58 MHz and a YM2151. The screen runs at 24 MHz / 4 with 384 by 264 totals, about 59.19 Hz (computed). One configuration is marked "verified against real hardware" ([MAME: konami/tmnt.cpp](https://github.com/mamedev/mame/blob/master/src/mame/konami/tmnt.cpp)). ### Protection Not covered here for the 68000 boards. Konami's 6809-based Konami-1 CPU scrambles opcodes ([protection](https://tanepiper.github.io/game-frame/arcade/protection.md#konami)). Konami's other 6809 and Z80 boards are not covered here. ### Tools Ghidra's 68000 module and Unicorn's M68K backend cover the main program. ### Projects to learn from None listed yet. ### For an exact reimplementation - Run the port at about 59.19 Hz. ## Taito F2 and F3 ### Hardware **Taito F2** uses a 68000 at 12 MHz, a Z80 at 4 MHz and a YM2610 at 8 MHz. MAME sets it to a nominal 60 Hz. Some variants use an OKI MSM6295 and different clocks "verified on pcb" ([MAME: taito/taito_f2.cpp](https://github.com/mamedev/mame/blob/master/src/mame/taito/taito_f2.cpp)). **Taito F3** ran Ring Rage, Arabian Magic and Riding Fight (all 1992). It uses a 68EC020 main CPU. MAME sets it to 16 MHz with a note that it "should be 30.47618_MHz_XTAL / 2". The Taito EN sound board has a 68000 at 15.238 MHz, an Ensoniq ES5505, an ES5510 effects DSP at 10 MHz and an MC68681 DUART. The screen runs at 26.686 MHz / 4 with 432 by 262 totals and 58.94 Hz. That rate comes from the crystal and from measurements in an arcade-projects thread. Another F3 configuration uses 58.97 Hz ([MAME: taito/taito_f3.cpp](https://github.com/mamedev/mame/blob/master/src/mame/taito/taito_f3.cpp); [taito_f3.h](https://github.com/mamedev/mame/blob/master/src/mame/taito/taito_f3.h); [taito_en.cpp](https://github.com/mamedev/mame/blob/master/src/mame/taito/taito_en.cpp)). The Taito B System is not covered here. Bubble Bobble's board uses a 6 MHz pixel clock at about 59.19 Hz (computed) ([MAME: taito/bublbobl.cpp](https://github.com/mamedev/mame/blob/master/src/mame/taito/bublbobl.cpp)). ### Protection Bubble Bobble uses an M68705P3 or an M6801U4 protection MCU ([protection](https://tanepiper.github.io/game-frame/arcade/protection.md#protection-mcus)). How the Taito 68705 ROMs were dumped is not covered here. ### Tools - Ghidra's 68000 module covers the 68EC020 and the sound 68000. Unicorn's M68K backend runs them. - Ghidra has no built-in module for the ES5510 DSP. ### Projects to learn from None listed yet. ### For an exact reimplementation - A note in MAME says its F3 CPU clock is wrong. Do not use MAME's timing as ground truth for a cycle-exact F3 port. - Run an F3 port at 58.94 Hz. Check which configuration the game uses. Another configuration uses 58.97 Hz. - Treat the F2's nominal 60 Hz as unknown. ## Irem M72 and M92 ### Hardware **Irem M72** ran R-Type (1987). It uses an NEC V30 at 8 MHz from a 32 MHz master. The driver comment reads "16 MHz external freq (8MHz internal)". A Z80 drives a YM2151 and an 8-bit DAC. Some games have an Intel 8751 MCU. The screen runs at about 55.02 Hz (computed) ([MAME: irem/m72.cpp](https://github.com/mamedev/mame/blob/master/src/mame/irem/m72.cpp)). **Irem M92** ran Blade Master (1991). It uses an NEC V33 at 9 MHz and an NEC V35 sound CPU at 14.318 MHz. It shows 320 by 240 at about 60.01 Hz (computed) ([MAME: irem/m92.cpp](https://github.com/mamedev/mame/blob/master/src/mame/irem/m92.cpp)). ### Protection M72 uses an Intel 8751 or an 80C31 as a protection MCU. ### Tools The NEC V-series is 8086-compatible at the instruction level. Ghidra's and Unicorn's x86 16-bit modes can probably decode most of Irem's code. Check the V-series extra instructions and the V35's on-chip peripherals by hand (not confirmed) ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#x86)). Ghidra's 8051 module covers the 8751. ### Projects to learn from None listed yet. ### For an exact reimplementation - Run an M72 port at about 55.02 Hz. - Check every instruction Ghidra fails to decode against the V-series extras. - The MCU may hold game logic. Check whether MAME runs its dump or a simulation. ## Data East DEC0 ### Hardware DEC0 is the Data East board from the time of Bad Dudes. It uses a 68000 at 10 MHz, verified as "68000P12 running at 10Mhz". A 65C02 at 1.5 MHz drives a YM2203, a YM3812 and an OKI MSM6295. Some games add a HuC6280, an Intel 8751 or an M68705R3. The screen runs at about 57.44 Hz (computed). An older comment gives 57.41 Hz ([MAME: dataeast/dec0.cpp](https://github.com/mamedev/mame/blob/master/src/mame/dataeast/dec0.cpp)). ### Protection Data East uses an Intel 8751 and an M68705R3 as protection MCUs. ### Tools Ghidra covers the 68000, the 65C02 and the 8051. Unicorn covers only the 68000. ### Projects to learn from None listed yet. ### For an exact reimplementation - Run the port at about 57.44 Hz. - Treat the protection MCU program as part of the game. ## Toaplan 1 ### Hardware Toaplan 1 ran Truxton (1988). It uses a 68000 at 10 MHz, a Z80 at 3.5 MHz, a YM3812 and a 7 MHz pixel clock. One configuration adds a TMS320-family DSP at 14 MHz ([MAME: toaplan/toaplan1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/toaplan/toaplan1.cpp)). ### Protection Not covered here. ### Tools Ghidra and Unicorn cover the 68000. Neither has a TMS320 module. ### Projects to learn from None listed yet. ### For an exact reimplementation - On the configuration with the DSP, trace its program as well. ## Cave 68000 boards ### Hardware Cave's 68000 boards ran Mazinger Z (1994) and DoDonPachi (1997). They run a 68000 at 16 MHz. Many games use a YMZ280B at 16.9344 MHz. Others use an OKI MSM6295 or a Z80 with a YM2151. MAME sets the refresh to 15625 / 271.5, about 57.55 Hz ([MAME: atlus/cave.cpp](https://github.com/mamedev/mame/blob/master/src/mame/atlus/cave.cpp)). ### Protection Not covered here. ### Tools Ghidra's 68000 module and Unicorn's M68K backend cover the main program. ### Projects to learn from None listed yet. ### For an exact reimplementation - Run the port at about 57.55 Hz. - Record which sound chip the game uses. --- # Early 3D boards Every early 3D board has a general CPU and one or more DSPs or custom geometry chips. Each DSP runs its own program. The game logic usually runs on the main CPU. A port must reproduce the DSP programs too, so expect two or more instruction sets per game. Ghidra has no built-in module for most of these DSPs. Atari's Hard Drivin' is on the [Atari](https://tanepiper.github.io/game-frame/arcade/atari-coin-op.md#atari-hard-drivin) page. Midway Zeus is on the [Williams and Midway](https://tanepiper.github.io/game-frame/arcade/williams-and-midway.md#midway-zeus) page. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | Namco System 21 | 1988 on | Two M68000 at 12.288 MHz | MC6809E | Not covered here | None listed | In MAME | 68000 built in; none for the TMS320C25 | | Namco System 22, Super 22 | 1993 on | M68020 at 49.152 MHz / 2 | C352 on an M37702 or M37710 | Not covered here | None listed | 2 of 42 not working | 68000 built in; none for the DSPs | | Sega Model 1 | 1992 | NEC V60 at 16 MHz | 68000 at 10 MHz, YM3438 | Not covered here | Not covered here | TGP ROMs BAD_DUMP on four games | None for the V60 | | Sega Model 2 | 1993/1994 | i960KB at 25 MHz | 68000, SCSP | Not confirmed | 315-5881, 315-5838 | In MAME | Extension for the i960 | | Sega Model 3 | 1996 | PowerPC 603ev, 66 to 166 MHz | 68000, two SCSP | 57.524 Hz (Supermodel) | 315-5881 | Known faults | PowerPC built in | | Taito JC | 1995 | M68040 at 20 MHz | Not covered here | Not covered here | Not covered here | 2 of 19 not working | 68000 built in; none for the TMS320C51 | | Konami GTI Club, Hornet | 1996, 1998 | PowerPC 403GA at 32 MHz | 68000 at 16 MHz | Not covered here | X76 secure EEPROM on some | 58 of 72 Hornet entries not working | PowerPC built in; SHARC extension | | Gaelco 3D | 1996 | M68000 at 15 MHz | ADSP-2115 at 16 MHz | Not covered here | Not covered here | 1 of 20 not working | 68000 built in | ## Namco System 21 ### Hardware Namco System 21 "Polygonizer" (1988 onward) runs two M68000s at 12.288 MHz and an MC6809E for sound ([MAME namcos21.cpp](https://github.com/mamedev/mame/blob/master/src/mame/namco/namcos21.cpp)). The DSP setup varies by game. - Winning Run and Driver's Eyes each use one TMS320C25. - Cyber Sled and Star Blade use one master C67 plus four slave C67s. A C67 is a TMS320C25 with custom ROM. - MAME clocks the C67s from a 40 MHz crystal ([MAME namcos21_dsp_c67.cpp](https://github.com/mamedev/mame/blob/master/src/mame/namco/namcos21_dsp_c67.cpp)). The master DSP parses the display list and applies high-level geometry. The slaves transform, project and clip each primitive's vertices. ### Protection None listed. ### Tools Ghidra and Unicorn cover the 68000. Neither has a TMS320 module. Use MAME to run the DSP programs. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record which DSP setup the game uses: one TMS320C25 or five C67s. - Reproduce the master and slave DSP programs as well as the 68000 code. ## Namco System 22 and Super System 22 ### Hardware System 22 (1993 onward) uses an M68020 at 49.152 MHz / 2 ([MAME namcos22.cpp](https://github.com/mamedev/mame/blob/master/src/mame/namco/namcos22.cpp)). - The Super System 22 board notes list an MC68EC020FG25 at 24.576 MHz. - The board has "two TI320C25 DSP (printed as C71)". - The Prop Cycle test menu shows that the main CPU uploads code to the master DSP. The master uploads code to the slave. - Sound and I/O run on a C74, a Mitsubishi M37702 MCU with mask ROM. Super System 22 uses an M37710 at 16.384 MHz with no internal ROM. - Sound output is the Namco C352, a 32-channel PCM chip at 24.576 MHz. ### Protection None listed. ### Tools The first entries are Ridge Racer variants from 1993. Of 42 entries, 2 have MACHINE_NOT_WORKING. Ghidra has no built-in module for the TMS320C25 or the M37702. ### Projects to learn from Arcade Archives Ridge Racer (Namco, 1993, System 22) came out on 5 June 2025. It was the first Arcade Archives 2 title, on Switch 2, PS5 and Xbox Series X|S ([Game Developer press release](https://www.gamedeveloper.com/press-release/-arcade-archives-series-announces-first-simultaneous-five-platform-release-with-arcade-archives-ridge-racer-)). Air Combat 22 followed on 3 July 2025. Rave Racer was announced for 26 February ([VGC, Rave Racer](https://www.videogameschronicle.com/news/namcos-last-arcade-ridge-racer-game-rave-racer-is-getting-a-home-version-for-the-first-time)). ### For an exact reimplementation - The uploaded DSP code exists only in RAM. Capture it from a running emulator. ## Sega Model 1 ### Hardware Model 1 (1992) runs an "NEC uPD70615GD-16-S V60 CPU, running at 16.000MHz" with a Fujitsu MB86233 "TGP" coprocessor. The header gives no TGP clock. Sound is a TMP68000N-10 at 10 MHz with a YM3438 at 8 MHz. The monitor runs at 24 kHz horizontal ("medium resolution") ([MAME model1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/model1.cpp)). The games are Virtua Racing (1992), Virtua Fighter (1993), Tecwar/Netmerc (1993, Sega and Virtuality), Star Wars Arcade (1994) and Wing War (1994). Wikipedia puts Model 1 at 180,000 polygons per second and 6,500 per frame ([Wikipedia, List of Sega arcade system boards](https://en.wikipedia.org/wiki/List_of_Sega_arcade_system_boards)). ### Protection Not covered here. ### Tools - The TGP program ROMs for Virtua Fighter (315-5724) are marked BAD_DUMP. So are those for Star Wars Arcade, Wing War and Netmerc (315-5711). The Virtua Racing TGP ROM (315-5573) is not. - Virtua Fighter's collision is "significantly broken due to imperfect TGP RAM port emulation or hookup". - No Ghidra module for the V60 is known (not confirmed). Unicorn has none. ### Projects to learn from M2 developed Sega Ages Virtua Racing for the Switch (Japan April 2019, other regions June 2019). It runs at 60 fps and up to 1080p. The arcade ran at 30 fps and 496 by 384 ([Nintendo Life review](https://www.nintendolife.com/reviews/switch-eshop/sega_ages_virtua_racing)). Whether that release emulates the Model 1 or reimplements it is not confirmed. ### For an exact reimplementation - For the games with BAD_DUMP TGP ROMs, do not use MAME as ground truth for the geometry stage. - Plan for a V60 disassembler. - The Model 1 TGP clock is unknown. ## Sega Model 2 ### Hardware MAME's header describes Model 2 (1993/1994; Daytona USA 1994) as "i960KB + (5x TGP) or (2x SHARC) or (2x TGPx4)" ([MAME model2.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/model2.cpp)). | Variant | Main CPU | Geometry | Sound | |---|---|---|---| | Model 2 | i960KB at 25 MHz | MB86234 TGPs at 50 MHz | Model 1 sound board | | Model 2A-CRX | i960KB | MB86234 | 68000 at 45.1584 MHz / 4, SCSP at 45.1584 MHz / 2 | | Model 2B-CRX | i960KB | ADSP-21062 SHARCs at 32 MHz | As 2A | | Model 2C-CRX | i960KB | MB86235 at 20 MHz | As 2A | MAME's video timing uses a 16 MHz pixel clock, 656 by 496 totals and 424 by 384 visible. The driver notes this may not be accurate. Wikipedia says co-development with GE Aerospace (Compu-Scene) began in September 1992, with 300,000 textured polygons per second at 60 fps ([Wikipedia, List of Sega arcade system boards](https://en.wikipedia.org/wiki/List_of_Sega_arcade_system_boards)). ### Protection - A 315-5881 crypt device on the 2A, the 2B and Zero Gunner. - A 315-5838 compression device, with a 317-0229 variant. - Simulated PIC protection for "To The MAXX". - A protection ROM overlay for GTX. ### Tools - [ghidra_i960](https://github.com/mumbel/ghidra_i960) adds the i960. - [ghidra-sharc](https://github.com/roblg/ghidra-sharc) targets the ADSP-214xx. The Model 2B uses the ADSP-21062. - Ghidra has no module for the MB8623x TGPs. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the variant. The geometry processor and its instruction set depend on it. - Do not rely on MAME's video timing. - "To The MAXX" uses simulated protection in MAME. A port built on that simulation has the same errors. ## Sega Model 3 ### Hardware Model 3 (1996; Virtua Fighter 3 and Scud Race) uses a PowerPC 603ev ([MAME model3.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/model3.cpp)). - MAME's header lists "Step 1.0: 66 MHz PPC", "Step 1.5: 100 MHz PPC, faster 3D engine", "Step 2.0: 166 MHz PPC, even faster 3D engine" and "Step 2.1: 166 MHz PPC, same 3D engine as 2.0". - A Guru readme in the same file says "The CPU on Scud Race runs at 66MHz". That conflicts with the Step 1.5 figure. - The host bridge is a Motorola MPC105 or MPC106, depending on the step. - The Real3D graphics have PCI IDs 0x16c311db (315-5827, Step 1.x) and 0x178611db (315-6022, Step 2). Both use Mitsubishi M5M410092FP 3D-RAM. - Sound is a 68000-12 with two 315-5687 SCSP chips. The optional Digital Sound Board v1 has a Z80 at 4 MHz. The v2 board has a 68EC000 at 11 MHz. Supermodel states that the Model 3 runs at "57.524 frames per second". It also states that Real3D, then a Lockheed Martin company, developed the graphics ([Supermodel README](https://github.com/trzy/Supermodel)). ### Protection Protection is a 315-5881 on a security board (171-7405B) with a 315-6050 Lattice ispLSI 2032. MAME maps it at 0xf01a0000. ### Tools - Supermodel emulates the Model 3 under the GPL on Windows, Linux and macOS. - Ghidra's PowerPC module and Unicorn's PPC backend cover the main CPU ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#powerpc)). - Known MAME issues include "broken FPU values in matrices" in Magical Truck Adventure ("cpu core bug?") and texture faults in Spikeout. ### Projects to learn from Supermodel's source. ### For an exact reimplementation - Record the step of the board. The CPU clock and the 3D engine depend on it. - Run the port at 57.524 Hz, Supermodel's figure. - Check floating-point results against more than one emulator. MAME has a known FPU fault on one game. ## Taito JC System ### Hardware Taito JC (1995) runs an M68040 at 20 MHz and a TMS320C51 DSP at 40 MHz. Games include Dangerous Curves and Landing Gear (both 1995) ([MAME taitojc.cpp](https://github.com/mamedev/mame/blob/master/src/mame/taito/taitojc.cpp)). ### Protection Not covered here. ### Tools Of 19 entries, 2 have MACHINE_NOT_WORKING. Most have IMPERFECT_GRAPHICS and IMPERFECT_TIMING. ### Projects to learn from None listed yet. ### For an exact reimplementation - MAME flags its timing as imperfect on most games. Do not rely on it. ## Konami GTI Club and Hornet ### Hardware GTI Club (1996) and Hornet (1998) use a PowerPC 403GA at 32 MHz, a 68000 at 16 MHz for sound and up to two ADSP-21062 SHARCs at 36 MHz. Hornet games include Gradius IV (1998). GTI Club: Rally Cote D'Azur dates from 1996 ([MAME gticlub.cpp](https://github.com/mamedev/mame/blob/master/src/mame/konami/gticlub.cpp); [MAME hornet.cpp](https://github.com/mamedev/mame/blob/master/src/mame/konami/hornet.cpp)). ### Protection Some configurations use an X76 secure EEPROM. ### Tools - Of 72 Hornet entries, 58 have MACHINE_NOT_WORKING. Operation Thunder Hurricane (1997) does not work. - Ghidra's PowerPC module covers the main CPU. ghidra-sharc targets the ADSP-214xx. ### Projects to learn from None listed yet. ### For an exact reimplementation - Most Hornet games do not run in MAME. Take ground truth from other emulators or from owned hardware. ## Gaelco 3D ### Hardware Gaelco 3D (1996) runs an M68000 at 15 MHz on Speed Up, with an ADSP-2115 sound DSP at 16 MHz. Settings are stored in a 93C66 serial EEPROM ([MAME gaelco3d.cpp](https://github.com/mamedev/mame/blob/master/src/mame/gaelco/gaelco3d.cpp)). ### Protection Not covered here. ### Tools One of 20 entries does not work. ### Projects to learn from None listed yet. ### For an exact reimplementation - Load the 93C66 EEPROM image as an input to each test. Sega Hikaru (1999) is not covered yet. --- # Console-derived boards From 1994 to about 2009 many arcade boards reuse a console's CPU, GPU and sound chip. They add arcade media: ROM boards, GD-ROM, DVD or hard disk. They add a JVS I/O board and a security chip per game. A dump without the matching key or security-chip data is incomplete. MAME runs the earlier families. It runs none of the Triforce, Chihiro or System 246 entries. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | Sony ZN-1, ZN-2 | Not covered here | CXD8530CQ; CXD8661R | Z80, 68000 or AT89C4051; QSound on Capcom | Not covered here | CAT702 | 3 of 116 not working | MIPS built in | | Namco System 11 | 1994/1995 | CXD8530CQ | Not covered here | Not covered here | Per-game key chips | In MAME | MIPS built in | | Namco System 12 | 1996 on | CXD8661R or CXD8606BQ at 100 MHz | Not covered here | Not covered here | Per-game key chips | In MAME | MIPS built in | | Sega ST-V | 1995 | Two SH7604 at 28.6364 MHz | 68000 at 11.2896 MHz | Not covered here | 315-5881 on some carts | 67 of 120 not working | SuperH built in | | Sega NAOMI, NAOMI 2 | 1998, 2000 | SH-4 at 200 MHz | AICA | Not covered here | 315-5881, M1 and M4 carts, DIMM DES | In MAME | SuperH4 built in | | Atomiswave | 2003 | Not covered here | Not covered here | Not covered here | 8-bit key from the cart CPLD | In MAME | Not covered here | | Triforce | 2002 | PowerPC 750 (MAME uses a PPC603 placeholder) | Not covered here | Not covered here | Key chips, PIC16C621A | All 73 not working | PowerPC built in | | Sega Chihiro | 2002 | Xbox x86 | Not covered here | Not covered here | DIMM board DES on GD-ROM | All 64 not working | x86 built in | | Namco System 246, 256, Super 256 | 2001 to 2010 | R5900 at 294 MHz | SPU2 | Not covered here | Security cart per game | All 60 not working | MIPS built in; R5900 by extension | ## Sony ZN-1 and ZN-2 ### Hardware ZN-1 and ZN-2 are "Arcade PlayStation Hardware" in MAME ([MAME zn.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sony/zn.cpp)). - ZN-1 uses a CXD8530CQ CPU on a 67.7376 MHz crystal and a CXD8561Q GPU. - ZN-2 uses a CXD8661R on a 100 MHz crystal and a CXD8654Q GPU. - Sound CPUs vary by licensee: Z80, 68000 or AT89C4051. Capcom uses QSound. ### Protection Each board has a CAT702 protection chip. On the Capcom ZN-1 board it is labelled 'CP01', next to a Capcom BIOS (COH1000C.353). ### Tools Of 116 entries, 3 do not work. Ghidra's MIPS module and Unicorn's MIPS backend cover the CPU ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#mips)). PlayStation tools work on the code ([tools for console work](https://tanepiper.github.io/game-frame/consoles/tools.md)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the licensee's sound CPU and the BIOS. - Keep the CAT702 data with the dump. ## Namco System 11 and 12 ### Hardware System 11 (1994/1995) uses the CXD8530CQ with a Namco C76 MCU at 16.9344 MHz for I/O ([MAME namcos11.cpp](https://github.com/mamedev/mame/blob/master/src/mame/namco/namcos11.cpp)). System 12 (1996 onward) uses a CXD8661R or CXD8606BQ at 100 MHz and an H8/3002 sub CPU ([MAME namcos12.cpp](https://github.com/mamedev/mame/blob/master/src/mame/namco/namcos12.cpp)). ### Protection Game boards have key chips. Tekken 2 uses C406. Tekken runs on board COH-100, which has no key chip. Ehrgeiz (1998, COH-700) uses KC021. ### Tools As for ZN. Ghidra has no H8 module among its bundled processors. ### Projects to learn from None listed yet. ### For an exact reimplementation - Keep the key chip data with each dump. - Model the C76 or H8/3002 I/O CPU as the game sees it. ## Sega ST-V ### Hardware ST-V (1995) is Saturn-based. It has two SH7604 (SH-2) CPUs at 28.6364 MHz and a 68000 at 11.2896 MHz for sound ([MAME stv.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/stv.cpp)). ### Protection Some cartridges use the 315-5881 chip found on NAOMI M2 carts. Tecmo World Cup '98 is one ([MAME 315-5881_crypt.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/315-5881_crypt.cpp)). ### Tools Of 120 entries, 67 have MACHINE_NOT_WORKING. Ghidra's SuperH module covers the SH-2. Unicorn has no SH-2 backend ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#superh)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Run both SH-2s interleaved. - Check the game's MAME flags before using it as ground truth. ## Sega NAOMI, NAOMI 2 and Atomiswave ### Hardware NAOMI (1998) runs an SH-4 at 200 MHz from a 33.3333 MHz crystal. Its graphics are the "VideoLogic/NEC 'CLX2/HOLLY' chipset and PowerVR2 GPU". AICA sound runs from a 33.8688 MHz oscillator. NAOMI 2 (2000) adds a 315-6289 "VideoLogic/NEC 'ELAN' T&L coprocessor" ([MAME naomi.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/naomi.cpp)). NAOMI has 32 MB of main RAM, 16 MB for graphics and 8 MB for sound, with ROM boards and an optional GD-ROM drive ([Wikipedia, List of Sega arcade system boards](https://en.wikipedia.org/wiki/List_of_Sega_arcade_system_boards)). The filter board outputs VGA at 15 kHz or 31.5 kHz, chosen by a DIP switch. ### Protection Sega made six known cart PCB types: 171-7885A, 171-7919A, 171-7930B, 171-7978B, 171-8132B and 171-8346C. Each cart has three security parts ([MAME naomi.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/naomi.cpp)). - A 315-5881, "usually different per game", with a 317-xxxx key number. Crazy Taxi, for example, is 840-0002C with key 317-0248-COM. In the key suffix, "-JPN means it requires Japanese BIOS, -COM will run with any BIOS". - An X76F100 secure serial flash. - A 315-6213 Xilinx XC9536 CPLD. M4 carts decrypt with a Xilinx Spartan XC3S50 FPGA and a PIC16C621A holding a 32-bit key. The cipher is a stream cipher on a 16-bit, 2-round SP-network with four 4-to-4 S-boxes. Its feed value resets every 16 blocks of 32 bytes ([MAME naomim4.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/naomim4.cpp)). M1 carts are identified by an Actel ID. They take a key as a parameter ([MAME naomim1.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/naomim1.cpp)). GD-ROM games load through the DIMM board. Its 315-6322 chip handles "DIMM SDRAM controller, DES decryption, host system communication" for NAOMI, Triforce and Chihiro. The option board has a PIC16 ([MAME naomigd.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/naomigd.cpp)). Atomiswave (2003) ROM boards decrypt with an 8-bit key derived from the cartridge CPLD. MAME computes that key from the decrypted code ([MAME awboard.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/awboard.cpp)). ### Tools - Flycast, derived from reicast, emulates the Dreamcast, NAOMI, NAOMI 2 and Atomiswave ([Flycast README](https://github.com/flyinghead/flycast)). - Ghidra's SuperH4 module covers the SH-4. Unicorn has no SH-4 backend. - A GD-ROM is stored as a CHD. Extract its files with `chdman` first ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#rom-sets-and-chd)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the BIOS region with each test. A -JPN key needs the Japanese BIOS. - MAME's default NVRAM applies "usability defaults". It can differ from a factory board ([practices](https://tanepiper.github.io/game-frame/arcade/practices.md#nvram-and-dip-settings-as-test-inputs)). - Record the 15 kHz or 31.5 kHz DIP setting. - Keep the 317-xxxx key with the dump. ## Triforce ### Hardware Triforce (2002) is GameCube-based, from Sega, Namco and Nintendo. MAME says the "Correct CPU is a PowerPC 750 (what Apple called "G3") with paired-single vector instructions added". Its driver uses a PPC603 placeholder at 64 MHz ([MAME triforce.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/triforce.cpp)). ### Protection Sega titles come on GD-ROM. They load through the DIMM board and use key chips. Virtua Striker 2002 is GDT-0001 with 317-0337-JPN. F-Zero AX is GDT-0004 with 317-0362-COM. Some also have a PIC16C621A. Namco titles use ROM carts. ### Tools - All 73 Triforce entries in MAME have MACHINE_NOT_WORKING. - Mainline Dolphin removed Triforce support (the AM-Baseboard) in 2016 for lack of a maintainer ([Dolphin Progress Report, August 2016](https://dolphin-emu.org/blog/2016/09/01/dolphin-progress-report-august-2016/)). - Reports say it returned to mainline Dolphin in February 2026 (secondary source) ([Time Extension](https://www.timeextension.com/news/2026/02/gamecube-and-wii-emulator-dolphin-adds-support-for-triforce-arcade-platform-from-namco-nintendo-and-sega); [RetroHandhelds](https://retrohandhelds.gg/dolphin-emulator-finally-brings-triforce-arcade-support-to-main-branch/)). - Ghidra's PowerPC module and Unicorn's PPC backend cover the CPU. Check how each tool handles the paired-single instructions ([CPU families](https://tanepiper.github.io/game-frame/computers/cpu-families.md#powerpc)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Do not use MAME as ground truth for any Triforce game. Take ground truth from Dolphin or from owned hardware. ## Sega Chihiro ### Hardware Chihiro (2002) is Xbox-based ([MAME chihiro.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/chihiro.cpp)). - Its bottom board is close to the V1 retail Xbox board, with 128 MB of RAM. - It has an nVidia MCPX X2 where retail uses the MCPX X3. - A Sega base board handles JVS and video output. - A media board with a TSOP48 flash holds an .xbe loader and reads DVD-ROM or GD-ROM. Wikipedia names The House of the Dead III (2002) as the first game ([Wikipedia, List of Sega arcade system boards](https://en.wikipedia.org/wiki/List_of_Sega_arcade_system_boards)). ### Protection GD-ROM games load through the DIMM board, which does DES decryption. ### Tools All 64 MAME entries have MACHINE_NOT_WORKING. Ghidra and Unicorn cover x86. ### Projects to learn from None listed yet. ### For an exact reimplementation - Do not use MAME as ground truth for any Chihiro game. - The media board holds an .xbe loader. Ghidra and Unicorn cover the x86 code once the files are decrypted ([re-ghidra](https://tanepiper.github.io/game-frame/skills/re-ghidra.md)). ## Namco System 246, 256 and Super 256 ### Hardware These boards (2001 to 2010) are PS2-based. They have the R5900 "Emotion Engine", an R3000 IOP, VU0, VU1, the GS and the SPU2. MAME uses an R5000LE placeholder, "actually R5900 @ 294 MHz" ([MAME namcops2.cpp](https://github.com/mamedev/mame/blob/master/src/mame/namco/namcops2.cpp)). ### Protection Games ship on CD, DVD or a 20 to 80 GB hard disk, each with a security cart (NMxxxxx). Battle Gear 3 (Taito, 2002) uses a 20 GB disk with cart NM00010. The "Dongle selects region (J/O) using same HDDs". ### Tools - All 60 MAME entries have MACHINE_NOT_WORKING. - The Play! PS2 emulator has a "Namco System 2x6 Arcade Support" section. Arcade files go in an `arcaderoms` folder. They boot with `--arcade "arcade id"` ([Play! README](https://github.com/jpd002/Play-)). - Ghidra's MIPS module needs an extension for the R5900. Unicorn's MIPS backend has no R5900. ### Projects to learn from None listed yet. ### For an exact reimplementation - Record the security cart with each test. It selects the region. ## Konami Bemani boards ### Hardware Konami's Bemani games first ran on console-based boards. Later versions ran on PCs. - System 573 is based on the original PlayStation. It was used mainly for Bemani titles ([Wikipedia, System 573](https://en.wikipedia.org/wiki/System_573)). - Beatmania IIDX moved from the PlayStation-based Twinkle hardware to the Windows XP-based "Bemani PC Type 1" with 9th Style. e-Amusement also first appeared with 9th Style ([Wikipedia, Beatmania IIDX 9th Style](https://en.wikipedia.org/wiki/Beatmania_IIDX_9th_Style)). - Python 2 is PS2-based, with an Emotion Engine and 32 MB of Rambus memory. GuitarFreaks and DrumMania V to V3 ran on it ([Wikipedia, GuitarFreaks and DrumMania](https://en.wikipedia.org/wiki/GuitarFreaks_and_DrumMania); [gamerepair.info, Python 1](https://gamerepair.info/hardware/22_python_1)). - Dance Dance Revolution X ran on Bemani PC Type 4 under Windows XP Embedded ([Wikipedia, Dance Dance Revolution X](https://en.wikipedia.org/wiki/Dance_Dance_Revolution_X)). ### Protection Konami's security plugs are not covered here. ### Tools For the PlayStation-based boards, use MIPS tools. For the PC-based boards, follow the PE path ([PC-based boards](https://tanepiper.github.io/game-frame/arcade/pc-based.md)). ### Projects to learn from None listed yet. ### For an exact reimplementation - Record which hardware generation each version of a game ran on. --- # PC-based boards From 2004 arcade boards are x86 PCs with custom I/O, a locked OS image and a hardware key. Ghidra handles the x86 code. Most of the effort goes into getting decrypted, unlocked files from hardware that you own. Once the files are decrypted, use GameFrame's Windows PE path. For Linux boards, use an ELF loader. | Board | Years | Main CPU and clock | Sound | Refresh | Protection | MAME status | Ghidra | |---|---|---|---|---|---|---|---| | Taito Type X to X4 | 2004 on | Celeron 2.5 GHz to Core i5-4590(S) | Not covered here | Not covered here | Dongle schemes (not covered here) | Not covered here | x86 built in | | Sega Lindbergh | 2005/2006 | Celeron D 335 at 2.8 GHz or Pentium 4 at 3.00 GHz | VIA VT1616 AC97 | Not covered here | PIC keychip with AES-CBC; locked CF and HDD | 18 of 19 not working | x86 built in | | Sega RingEdge, RingWide | 2009, 2011 | Pentium Dual-Core; Celeron | Not covered here | Not covered here | Ring keychip, AES 128 ECB on the bus | Not covered here | x86 built in | | Sega Nu, Nu 2, ALLS | 2013 on | Core i3-3220; Core i5-6500 | Not covered here | Not covered here | Keychips (not covered here) | Not covered here | x86 built in | | Namco ES1, ES3 | Not covered here | Core i5-3550S on ES3 | Not covered here | Not covered here | HASP HL Max dongle and BitLocker on ES3 | Not covered here | x86 built in | ## Taito Type X family ### Hardware | Model | OS | CPU | Graphics | Games | |---|---|---|---|---| | Type X (2004) | Windows XP Embedded | Celeron at 2.5 GHz, 256 MB DDR266 | AGP Radeon 9600 SE, 9600 XT or X700 PRO | Not covered here | | Type X2 | Not covered here | Celeron D 352, Pentium 4 651 or Core 2 Duo E6400; DDR2, 512 MB to 4 GB | PCIe | Street Fighter IV (2008), BlazBlue: Calamity Trigger (2008) | | Type X Zero | Windows Embedded Standard 7 | Atom 230 at 1.6 GHz | GeForce 9400M | Not covered here | | Type X3 | WES7 64-bit or XP Embedded SP3 32-bit | Core i5-2400 | Radeon HD 6770 base | Not covered here | | Type X4 | Windows Embedded 8 Standard or WES7 64-bit | Core i5-4590(S) | GeForce GTX 960 | Street Fighter 6: Type Arcade (2023) | Type X uses a PATA or SATA disk. NESiCAxLive is an add-on for digital distribution of games to arcade owners ([Wikipedia, Taito Type X](https://en.wikipedia.org/wiki/Taito_Type_X)). ### Protection The Type X dongle schemes are not covered here. ### Tools - TeknoParrot is a closed-source compatibility layer for Windows. It runs PC-based arcade games from Lindbergh, RingEdge, Nu, Type X, Namco ES3 and others. OpenParrot and the GUI are open source. Some features need Patreon support ([Emulation General Wiki, TeknoParrot](https://emulation.gametechwiki.com/index.php/TeknoParrot)). - Ghidra and Unicorn cover x86 and x86-64. The [re-ghidra](https://tanepiper.github.io/game-frame/skills/re-ghidra.md) and [re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md) skills cover the PE path. ### Projects to learn from TeknoParrot and OpenParrot. ### For an exact reimplementation - Work from decrypted files taken from owned hardware. - Record the model. The OS and the GPU change between generations. ## Sega Lindbergh ### Hardware Lindbergh (2005/2006) runs Linux ([MAME lindbergh.cpp](https://github.com/mamedev/mame/blob/master/src/mame/sega/lindbergh.cpp)). - The red box has a Celeron D 335 at 2.8 GHz with 512 MB. - The yellow box has a Pentium 4 at 3.00 GHz with 1 GB. - Both use an Intel 875 northbridge, a 6300ESB southbridge, a custom nVidia GeForce card and VIA VT1616 AC97 audio. - The machine boots a customised MontaVista Linux from a Compact Flash card. - Games install from DVD to the hard disk through `/usr/sbin/segaboot`. ### Protection "Both the CF and HDD are locked and unreadable on a regular PC." The PIC keychip has an AES-CBC engine. Everything on the keychip except its key can be dumped. 1. The CF derives a 176-byte secondary key. It decrypts 16 x 0x00 through 16 x 0x0b through the PIC. 2. It then decrypts DVD and HDD files with a custom cipher (`segaboot`, `lxdecrypt_hard`). 3. `lxunlock.hdb` unlocks the disk. ### Tools - Eighteen of 19 MAME entries do not work. - lindbergh-loader runs Lindbergh games on modern Linux in place of the original hardware, with JVS pass-through ([lindbergh-loader README](https://github.com/lindbergh-loader/lindbergh-loader)). - A Lindbergh game is a Linux ELF program. lindbergh-loader works like GameFrame's Wine path for Windows games ([running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md)). ### Projects to learn from lindbergh-loader. ### For an exact reimplementation - Do not use MAME as ground truth. Take ground truth from lindbergh-loader or from owned hardware. - Record the box colour. The red and yellow boxes differ in CPU and memory. ## Sega RingEdge, RingWide, Nu and ALLS ### Hardware The Ring, Nu and ALLS boards run embedded editions of Windows ([Wikipedia, List of Sega arcade system boards](https://en.wikipedia.org/wiki/List_of_Sega_arcade_system_boards)). - RingEdge (2009) uses a Pentium Dual-Core on Windows Embedded Standard 2009. - RingWide (2011) uses a Celeron on Windows Embedded Standard 2009. - Nu (2013) uses a Core i3-3220 with a GeForce GTX 650 Ti on Windows Embedded 8 Standard. - Nu 2 uses a Core i5-6500 with a GTX 950. - ALLS (2018) runs Windows 10 IoT Enterprise LTSC. The RingEdge, RingWide and ALLS CPU clocks are not confirmed. ### Protection The Ring keychip contains "a PIC microcontroller, a dedicated cryptography chip, a hardware SHA engine for authentication, and flash storage". "All bus communication is encrypted using AES 128 ECB, using a different key for each data direction." The keychip returns a four-character game ID. It decrypts with the game key. It keeps billing play counts ([sega.bsnk.me, Ring Keychip](https://sega.bsnk.me/ringedge/hardware/keychip/)). The ALLS and Nu keychips are not covered here. ### Tools - Community keychip emulators exist. micekeychip is open source and incomplete ([sega.bsnk.me, Keychip Modding](https://sega.bsnk.me/ringedge/manual/keychip/)). - TeknoParrot runs RingEdge and Nu games. ### Projects to learn from The sega.bsnk.me documentation of the Ring keychip. ### For an exact reimplementation - Work from decrypted files taken from owned hardware. - The keychip keeps play counts. Record its state with each test where the game reads it. ## Namco ES1 and ES3 ### Hardware ES3 is described as Windows Embedded Standard 7 on a Core i5-3550S with 8 or 16 GB of DDR3 and a GeForce GTX 650 Ti or GTX 680. ES1 is described as Debian-based Linux. Both descriptions come from secondary sources ([Emulation General Wiki, Namco](https://emulation.gametechwiki.com/index.php/Namco); [Wikipedia, Namco System N2](https://en.wikipedia.org/wiki/Namco_System_N2)). A forum post says ES3 runs Tekken 7, Time Crisis 5 and Mario Kart Arcade GP DX, with custom BIOSes on the graphics cards (secondary source) ([arcade-projects.com](https://arcade-projects.com/goto/post?id=348615)). ### Protection ES3 is protected by a HASP HL Max USB dongle and BitLocker (secondary source). ### Tools TeknoParrot runs Namco ES3 games. ### Projects to learn from None listed yet. ### For an exact reimplementation - Work from decrypted files taken from owned hardware. - On ES3, record the graphics card. The cards have custom BIOSes (secondary source). --- # Hardware of the era A PC game from 1985 to 2005 uses its hardware far more directly than a modern program. DOS games program the timer, the video card, the sound card and the joystick port themselves. Windows games of the late 1990s use DirectX or a card maker's own 3D API. The hardware a game was written for affects much of its code: the CPU's floating-point unit, the video mode, the sound chip, the 3D card's features. When you rebuild such a game, you replace the hardware with modern equivalents. To do that exactly, you need to know what each piece of hardware did, how the game used it, and which of its details the player can see or hear. ## Ask your agent Find the hardware layer. Copy this prompt into your coding agent: ```text In my GameFrame project, find every place the game touches hardware. 1. Read docs/hardware/README.md and the pages for this game's devices. 2. In the disassembly, find IN, OUT and INT instructions and writes to A000, B800 and 0040. For a Windows game, list the imports from DirectX, Glide, OpenGL and WinMM. 3. Name the driver functions (set a mode, set a palette, play a sound, read the joystick). 4. List the values the game reads back from the hardware. They are inputs. 5. Propose one reference configuration (video, sound, input) and ask me to confirm it. ``` ## Pages - [CPUs and floating point](https://tanepiper.github.io/game-frame/hardware/cpus.md): the x86 generations, the x87 FPU, MMX, 3DNow! and SSE, fixed point, timing loops, self-modifying code. - [2D graphics](https://tanepiper.github.io/game-frame/hardware/graphics-2d.md): CGA, EGA, VGA, Mode 13h, Mode X, VESA, palettes and the vertical retrace. - [3D accelerators](https://tanepiper.github.io/game-frame/hardware/graphics-3d.md): 3dfx and Glide, Rendition, PowerVR, S3, ATI, Matrox, NVIDIA, Direct3D and OpenGL, and software renderers. - [Sound](https://tanepiper.github.io/game-frame/hardware/sound.md): the PC speaker, AdLib and OPL FM synthesis, Sound Blaster, Gravis Ultrasound, MIDI devices, DirectSound, A3D and EAX. - [Memory, storage, input and networks](https://tanepiper.github.io/game-frame/hardware/other.md): EMS, XMS and DOS extenders, CD audio, the game port, IPX, serial links and modems. ## General rules for hardware code 1. **Find every place the game touches hardware.** In DOS games, search the disassembly for `IN`, `OUT`, `INT` and writes to segments `A000`, `B800` and `0040`. In Windows games, list the imports from DirectX, Glide, OpenGL, WinMM and the card makers' DLLs. 2. **Label the driver layer.** Most games wrap hardware access in a small set of functions (set a mode, set a palette, play a sample, read the joystick). Name them, and treat the rest of the game as their callers. 3. **Decide what the port must copy.** It must copy the picture and the sound the player perceives. It usually does not need to copy how the game produced them on the card. A frame drawn by Glide can be drawn by Vulkan, as long as it looks the same. 4. **Keep what the game logic reads back.** Some games read values from the hardware and use them in their rules: a timer count, the retrace state, a joystick reading, a sound position. Those values are inputs ([Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md)). 5. **Choose one reference configuration.** The original looked and sounded different on different cards. Pick the configuration your project reproduces (for example "VGA, Sound Blaster 16, General MIDI through a Roland SC-55") and write it in the project's `AGENTS.md`. --- # CPUs and floating point The CPU a game was written for sets the number formats and instructions it uses for arithmetic. A port that rounds one result differently can change a whole simulation. Learn which number formats and which instructions the game uses before you port any maths. ## The x86 generations | CPU | Year | What it added that games used | |---|---|---| | 8088 and 8086 | 1979 and 1978 | 16-bit real mode, 1 MB address space in 64 KB segments | | 80286 | 1982 | 16-bit protected mode (rarely used by games), faster | | 80386 | 1985 | 32-bit registers and protected mode, flat memory through DOS extenders | | 80486 | 1989 | The 486DX has the floating-point unit built in (the 486SX does not); an 8 KB cache | | Pentium | 1993 | Two integer pipelines (U and V), a much faster FPU, the `RDTSC` cycle counter | | Pentium MMX | 1997 | MMX: 64-bit integer vector instructions in the FPU registers | | AMD K6-2 | 1998 | 3DNow!: vector instructions for pairs of 32-bit floats | | Pentium III | 1999 | SSE: vector and scalar 32-bit float instructions in new XMM registers | | Pentium 4 | 2000 | SSE2: 64-bit floats and integers in XMM registers | A game's minimum CPU (on the box, or in the setup program) tells you which instructions to expect. Some games had separate code paths for different CPUs: a 3DNow! path, an MMX path and a plain path. The game chose one at start-up by testing the CPU with `CPUID`. Find that test. Then decide which path your project reproduces. ## Number formats ### Integers and fixed point Before the Pentium, floating point was slow or absent, so most games used **fixed point**: an integer whose low bits are the fraction. Common formats are 16.16 (16 integer bits, 16 fraction bits, as in Doom), 8.8 and 24.8. - Multiplying two 16.16 numbers gives a 32.32 result. The game keeps the middle 32 bits. On a 386 this is one `IMUL` into `EDX:EAX` followed by `SHRD`. Check whether the game rounds or truncates. Check what it does on overflow. - Division often uses a reciprocal table or a shift. Copy the table and the exact method. - `SAR` (arithmetic shift) rounds toward minus infinity, and `IDIV` rounds toward zero. The two give different results for negative numbers. Keep the one the game uses ([ABI](https://tanepiper.github.io/game-frame/abi.md#integers)). - 16-bit games wrap at 65,536. Use wrapping arithmetic in the port where the original relies on it. ### The x87 FPU From the 486DX until SSE2 became common (around 2004), floating point meant the x87 FPU. Before the 486DX, the FPU was a separate chip (8087, 287, 387) that few players owned. - Registers hold 80-bit extended precision values. A result is rounded to 32 or 64 bits only when it is stored to memory (`FST`, `FSTP`). Intermediate results inside one expression keep more precision. A port computing in 32-bit floats throughout gets different last bits. - The **precision control** field of the FPU control word can make every result round to 24 bits (float) or 53 bits (double). Direct3D sets 24-bit precision by default when it creates a device, unless the game asks it not to (`D3DCREATE_FPU_PRESERVE` in Direct3D 8 and 9, `DDSCL_FPUPRESERVE` in DirectX 7). So a Direct3D game's arithmetic may change after the device is created. Read the control word the game runs with: `FLDCW` instructions and calls to `_controlfp`. - Conversion from float to integer: the C cast truncates, and MSVC called `_ftol` to do it, changing the rounding mode twice. Games often replaced `_ftol` with a faster trick (`FISTP` with the default rounding mode, which rounds to nearest even, or adding 1.5 × 2^52 and reading the low bits). Each gives different results at .5. Find which one your game uses. - `FSIN`, `FCOS`, `FPATAN` and `FSQRT` are hardware instructions with their own accuracy. A C library may call them or use its own code. Emulate the exact path. ### MMX, 3DNow! and SSE - **MMX** uses the x87 registers. A game must execute `EMMS` before using floating point again. MMX code is usually for pixels: blending, colour conversion, decompression. Its results are integer and exact; port it with ordinary integer code or with SIMD that gives the same bits. - **3DNow!** gives pairs of 32-bit floats, with fast reciprocal and square-root estimates (`PFRCP`, `PFRSQRT`). The estimates have AMD's own accuracy. If game logic uses them, the port must reproduce the estimate exactly, usually with a table or by emulating it. - **SSE** adds `RCPSS` and `RSQRTSS` estimates. Intel specifies only a maximum error, so the exact bits differ between Intel and AMD CPUs and between models. If a game's rules depend on them, record the CPU the original ran on. - Games with several paths gave different results on different CPUs. Pick one path and document it as the reference. ## Timing Many DOS games measured the CPU at start-up with a delay loop and used the count to time short waits or to choose detail levels. On a fast modern CPU the count overflows or the game runs too fast. Turbo Pascal's `CRT` unit is the best-known case: its start-up calibration fails with "Runtime error 200" on CPUs faster than about 200 MHz. - Find calibration loops: a loop with a counter run between two timer ticks. - In the port, take the result of the calibration as an input with a fixed value, and record which value ([Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md)). - Games that time with `RDTSC` assume a fixed clock speed. Replace them with the game's frame or tick clock. ## Code that changes itself Some games of the 1990s wrote into their own code to make inner loops faster: they patched constants into instructions before running a loop (for example a texture's width into the `ADD` of a texture mapper). Ghidra shows the instruction with the placeholder value. - Find writes to code addresses: in the Ghidra export, look for stores whose target is in a code section. - Read the patched value from the writer, and treat it as a variable in the port. - GameFrame's emulators run self-modifying code correctly, so ground truth from them is still exact. ## Checklist for arithmetic 1. List the number formats the game uses: fixed point formats, float and double, packed integer vectors. 2. Read the FPU control word at the point the game runs its rules. 3. Find the float-to-integer conversion the game uses. 4. Find any CPU-specific code paths, and choose one. 5. Emulate every maths routine on many inputs and compare bit for bit (`re-emulate`). --- # 2D graphics DOS games drew straight into video memory. The video mode sets how memory maps to pixels, how many colours there are and how the palette works. To reproduce the picture exactly, the port must handle the mode the game used. ## The video standards | Standard | Year | Modes games used | Notes | |---|---|---|---| | CGA | 1981 | 320 × 200 in 4 colours from fixed palettes; 640 × 200 in 2 colours; text modes | Memory at `B800:0000`, even and odd lines in two banks | | EGA | 1984 | 320 × 200 in 16 fixed colours; 640 × 350 in 16 colours from 64 | Planar memory at `A000:0000`: four bit planes, one per colour bit | | VGA | 1987 | 320 × 200 in 256 colours (Mode 13h); 640 × 480 in 16 colours | Palette of 256 entries, each 6 bits per channel | | Mode X (VGA, unchained) | used from about 1991 | 320 × 240 in 256 colours, with several pages | Planar memory; documented by Michael Abrash | | VESA VBE (SVGA) | VBE 1.2 in 1991, 2.0 in 1994 | 640 × 480 and higher in 256 colours or more | Banked memory in VBE 1.x; a linear frame buffer from VBE 2.0 | Most DOS games from 1990 to 1996 use Mode 13h or Mode X. Later games use VESA modes. ## Mode 13h One byte per pixel, 320 bytes per line, at `A000:0000`, 64,000 bytes in all. It is the easiest mode to port: the game's screen is an array of palette indexes. - The game sets it with `INT 10h`, `AX = 0013h`. - Many games draw into a buffer in normal memory and copy it to `A000` once a frame. Capture that buffer and the screen separately; HUDs are sometimes drawn straight to `A000` ([Capturing the interface](https://tanepiper.github.io/game-frame/observe/ui.md)). ## Mode X and other unchained modes The game turns off the VGA's "chain 4" setting, so each byte address covers four pixels, one in each of four planes. The game selects planes with the sequencer's map mask (port `3C4h`, index 2) before writing. Mode X allows 320 × 240 with square pixels and several screen pages for page flipping. - In the port, convert the four planes back into a linear picture: pixel *x* is in plane *x* mod 4 at offset *x* / 4. - Page flipping changes the start address register (CRTC, port `3D4h`, indexes `0Ch` and `0Dh`). The visible page is the one the start address points to at the retrace. - Games also used the VGA's latches to copy four pixels at once, and the write modes to fill quickly. Port the resulting pixels. You do not need to copy the method. ## Palettes The VGA palette is written through ports `3C8h` (start index) and `3C9h` (red, green, blue, 6 bits each). Values range from 0 to 63. - Convert 6-bit values to 8 bits the way your reference output does. `v × 255 / 63` and `(v << 2) | (v >> 4)` give slightly different results. Choose one and compare with captures of the original. - Fades, flashes and colour cycling change the palette and leave the pixels as they are. A capture of video memory alone misses them. Capture the palette with every frame. - Some games changed the palette in the middle of a frame (for example a different palette for the status bar). Look for palette writes timed to the horizontal retrace. ## The vertical retrace Port `3DAh` bit 3 is set while the screen is in its vertical retrace. Games wait for it to avoid tearing and to time their frames. - If a game waits for the retrace each frame, the display's refresh rate sets the game's speed. VGA Mode 13h refreshes at 70 Hz; Mode X 320 × 240 at 60 Hz. - Some games count retraces to time animations or the whole game. Then a frame is 1/70 or 1/60 of a second. Use that rate in the port. - Port `3DAh` bit 0 is set while the display is blanked, in either retrace. Games use it for horizontal timing in raster effects. ## Text modes Menus, setup programs and some games use text mode 3 (80 × 25, 16 colours) at `B800:0000`, two bytes per cell: the character and the attribute. The font is the card's 8 × 16 font, which a game can replace. Port text screens with the original's code page 437 characters and the VGA palette for text. ## Windows 2D Windows games used GDI (slow), WinG (1994), and from 1995 DirectDraw. DirectDraw gives the game a pointer to a surface in video memory, so the drawing code is often the same as in a DOS game. - `IDirectDrawSurface::Lock` returns the pointer and the pitch. The pitch can be larger than the width; the game must use it. A port must not assume that the pitch equals the width. - 8-bit modes use a DirectDraw palette (`IDirectDrawPalette::SetEntries`) with 8-bit values. - 16-bit modes come as 5-6-5 or 5-5-5 bit layouts. The game checks which with the pixel format and may have two code paths. ## Checklist 1. Find the mode set calls and list every mode the game uses. 2. Find the palette writes and every place the palette changes. 3. Find the frame copy or page flip, and the retrace wait. 4. Capture the original's screen memory, palette and timing for several scenes ([Running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md)). 5. Rebuild the picture in the port from the same memory and palette, and compare pixel by pixel. --- # 3D accelerators From 1996, PC games could draw 3D with a separate graphics card. For a few years every card maker had its own API. Games shipped a separate renderer for each card. By 2000 Direct3D and OpenGL had replaced most of them. A game of this period may contain several renderers, and the picture the player saw depended on the card. ## The cards and their APIs | Card | Maker | Year | API | Notes | |---|---|---|---|---| | ViRGE | S3 | 1995 | S3D, Direct3D | Cheap and slow; few games used S3D | | Vérité V1000 | Rendition | 1996 | RRedline, Speedy3D | vQuake was written for it | | PowerVR PCX1, PCX2 | NEC and VideoLogic | 1996, 1997 | PowerSGL, Direct3D | Tile-based deferred rendering; a separate card that drew into the 2D card's memory | | Voodoo Graphics | 3dfx | 1996 | Glide | A separate card beside the 2D card; 16-bit colour; the market leader | | Rage, Rage Pro | ATI | 1996, 1997 | ATI CIF, Direct3D | | | Mystique, G200 | Matrox | 1996, 1998 | Direct3D, Matrox's own API (MSI) | | | Riva 128 | NVIDIA | 1997 | Direct3D, OpenGL | | | Voodoo2 | 3dfx | 1998 | Glide | Two cards could work together (SLI) | | Riva TNT, TNT2 | NVIDIA | 1998, 1999 | Direct3D, OpenGL | 32-bit colour | | Voodoo3 | 3dfx | 1999 | Glide, Direct3D, OpenGL | A single 2D and 3D card; 16-bit colour output | | GeForce 256 | NVIDIA | 1999 | Direct3D 7, OpenGL | Hardware transform and lighting | | GeForce 3, Radeon 8500 | NVIDIA, ATI | 2001 | Direct3D 8, OpenGL with extensions | Programmable vertex and pixel shaders | ## Glide 3dfx's Glide was a thin API close to the Voodoo hardware. It was the most used of the card makers' APIs. Many games had a Glide renderer as their best-looking option. - Games load `glide.dll`, `glide2x.dll` or `glide3x.dll` and call functions such as `grDrawTriangle`, `grTexSource`, `grAlphaBlendFunction`, `grBufferSwap`. - The game passes vertices already transformed to screen coordinates, with `1/w` for perspective correction. The game does all the 3D maths itself, so the maths is in the game's machine code. Port it exactly. - The Voodoo drew with 16-bit colour and dithering. It also filtered textures in its own way. The player saw those effects. Decide whether the port reproduces the 16-bit look or draws in full colour, and record the choice as a deviation if it differs. - Wrappers such as nGlide and dgVoodoo translate Glide calls to modern APIs. They show which parts of Glide a game needs. Use them to run the original on a modern machine for captures. ## Direct3D | Version | Year | What changed | |---|---|---| | Direct3D 3 (DirectX 3) | 1996 | Execute buffers: a list of instructions and vertices built in memory | | Direct3D 5 | 1997 | `DrawPrimitive`: draw calls with vertex arrays | | Direct3D 6 | 1998 | Multitexturing, texture compression | | Direct3D 7 | 1999 | Hardware transform and lighting | | Direct3D 8 | 2000 | Vertex and pixel shaders (1.x), DirectDraw merged into Direct3D | | Direct3D 9 | 2002 | Shader model 2, and shader model 3 from DirectX 9.0c (2004); used by games until well after 2010 | - Find the version from the imports (`ddraw.dll` with `IDirect3D3`, `d3d8.dll`, `d3d9.dll`) and the interface identifiers the game asks for. - Games before Direct3D 7 usually transform vertices themselves, as with Glide. From version 7 the card may do it. Then port the matrices and lights the game sets. - Shaders from Direct3D 8 and 9 ship compiled. Disassemble them to read the maths ([Rendering](https://tanepiper.github.io/game-frame/game-patterns.md#rendering-and-ui)). - Direct3D can change the FPU precision when it creates a device ([CPUs](https://tanepiper.github.io/game-frame/hardware/cpus.md)). ## OpenGL OpenGL 1.1 (1997) was used by id Software's games and their licensees, and by many Mac and Linux ports. On 3dfx cards, Quake and similar games used a "MiniGL" driver: an `opengl32.dll` with only the functions Quake needed, which translated them to Glide. Later games use extensions, which you find by searching the strings for `GL_ARB_`, `GL_EXT_` and `GL_NV_`. ## Software renderers Most games until about 1999 also had a software renderer. It drew every pixel with the CPU into a frame buffer. Software renderers are exact. The same input gives the same pixels on every machine. They are the best reference for the port's renderer, and the easiest to compare. ## How to approach a game with several renderers 1. List the renderers: separate DLLs (Unreal's `GlideDrv.dll`, `D3DDrv.dll`, `SoftDrv.dll`) or code paths chosen in the options. 2. Find the interface between the game and its renderers. It is usually a table of functions or a C++ class with virtual methods. The game sends everything it draws through it. 3. Choose one renderer as the reference. Write the choice in the project's `AGENTS.md`. The software renderer is easiest to compare exactly; the Glide renderer is often what players remember. 4. Port the interface. Write one modern renderer behind it. 5. Capture frames from the original with the reference renderer and compare. For hardware renderers, compare with a tolerance and look at the difference image. ## Effects that depend on the card - **16-bit colour and dithering**: banding and a dither pattern the player saw. - **Fog**: table fog (per pixel) and vertex fog gave different results on different cards. - **Texture filtering and mipmaps**: cards chose mipmap levels differently. - **Gamma**: 3dfx cards had their own gamma setting; games often set it. - **Resolution**: a game written for 640 × 480 may show more of the world at higher resolutions, or stretch its interface. Decide what the port shows and record it. --- # Sound Sound hardware in DOS games is programmed directly. A game's music may exist only as commands to a synthesiser chip. Its timing may come from the sound card's interrupts. To reproduce the sound exactly, you need to know which device the game wrote for and how it drove it. ## The devices | Device | Year | How games use it | Notes | |---|---|---|---| | PC speaker | 1981 | Tones from timer channel 2 (port `42h`), switched on through port `61h` | Some games played samples by switching the speaker very fast | | AdLib | 1987 | The Yamaha YM3812 (OPL2) FM chip at ports `388h` and `389h` | 9 voices of FM synthesis; music is a stream of register writes | | Roland MT-32 | 1987 | MIDI through an MPU-401 interface (port `330h`) | Its own synthesis and sounds; many Sierra games were written for it | | Sound Blaster | 1989 | An OPL2 for music, and an 8-bit digital channel (the DSP) with DMA | Settings in the `BLASTER` environment variable: port, IRQ, DMA | | Sound Blaster Pro | 1991 | Stereo; later models have the YMF262 (OPL3) | | | Sound Blaster 16 | 1992 | 16-bit digital sound with 16-bit DMA | | | Gravis Ultrasound | 1992 | 32 hardware voices playing samples from the card's own memory | Popular with tracker music and the demo scene | | General MIDI modules (Roland SC-55) | 1991 | MIDI through an MPU-401 | General MIDI fixed the instrument numbers | ## FM synthesis (OPL2 and OPL3) Music for AdLib and Sound Blaster is a stream of writes to the chip's registers: which voice, which frequency, which instrument settings, key on and key off. The chip turns these into sound. - **Capture the register writes**, with the time of each, from the original running in an emulator. This is exact ground truth for the music code, independent of the chip. - **Port the music driver** so it produces the same writes at the same times. Compare the two lists write by write. - **Emulate the chip** in the port to turn writes into samples. Good OPL emulators (Nuked OPL3, DOSBox's) are close to the real chip; Nuked OPL3 aims at exact output. - The driver's tempo usually comes from a timer interrupt the game programmed. Find the interrupt rate and the driver's tick counter. Small errors in the rate change the music's speed over a song. ## Digital sound (Sound Blaster DSP) The game sets up a DMA transfer from a buffer to the card and tells the card's DSP the sample rate. When the buffer has played, the card raises an interrupt. The game then refills the buffer. - Sample rates are set as a "time constant": `256 - 1,000,000 / rate`. The real rate is the rate that constant gives, which is not exactly the rate the game asked for. Compute it. - Games mix several effects into one buffer in software. If the sound must match, port the mixer's volume scaling and clipping exactly. - If the game counts sound interrupts to time anything, that count is an input. ## MIDI MIDI music is a list of note and controller events, often stored in a game's own format or as standard MIDI files. The sound depends entirely on the synthesiser. The same music sounds very different on an MT-32, a Sound Canvas and a Sound Blaster's FM. - Port the MIDI player: the events and their timing must be exact. - Play the events through a software synthesiser with sounds of the reference device (an MT-32 emulator such as Munt, or General MIDI sound fonts). - MT-32 games often upload their own instrument settings to the device at start-up with SysEx messages. Capture and replay them. ## Sound drivers in DOS games Many games used a sound library instead of programming cards themselves: the Miles Audio Interface Library, DIGPAK and MIDPAK, the HMI Sound Operating System. Each loads a driver for the player's card. Label the library and port what the game asks of it: play this sample, play this song, set this volume ([Middleware](https://tanepiper.github.io/game-frame/engines/middleware.md)). ## Windows sound | API | Year | Notes | |---|---|---| | WinMM (`waveOut`, `midiOut`) | 1991 | Simple and with high latency | | DirectSound | 1995 | Mixing by the system or the card; 3D sound from DirectX 3 | | DirectMusic | 1999 | Music with styles and segments that change with the game | | A3D (Aureal) | 1997 | Positional 3D sound in hardware | | EAX (Creative) | 1998 | Reverb and environment effects on top of DirectSound 3D | A3D and EAX effects need hardware that no longer exists. Software reimplementations exist (OpenAL Soft supports EAX effects). Decide whether the port reproduces them and record it. ## Checklist 1. Find which devices the game supports, from the setup program and the drivers it ships. 2. Choose the reference device and write it in the project's `AGENTS.md`. 3. Find the timer or interrupt that drives the music, and its rate. 4. Capture the music's register writes or MIDI events from the original, with times. 5. Port the music driver and compare the events. Then compare the audio. --- # Memory, storage, input and networks This page covers memory beyond 640 KB, CDs, joysticks and networks, and how to recognise each in a game's code. ## Memory in DOS A DOS program runs in real mode with 640 KB of conventional memory. Games used several ways to get more. | Method | What it is | Signs in the code | In the port | |---|---|---|---| | EMS (expanded memory) | Pages of 16 KB mapped into a 64 KB window, through `EMM386` or a card | `INT 67h`; the string `EMMXXXX0` | Ordinary memory; keep any logic that depends on page numbers | | XMS (extended memory) | Memory above 1 MB, copied to and from conventional memory through `HIMEM.SYS` | `INT 2Fh` with `AX = 4310h` to find the driver, then far calls | Ordinary memory | | Overlays | Parts of the program loaded on demand into the same memory | Borland's `.OVR` files, or the game's own loader | One program; but know which overlay is loaded at which time while you read addresses | | DOS extenders | 32-bit protected mode with flat memory, with DOS calls passed to real mode | `DOS/4GW` (Watcom, LE executables), `PMODE/W`, `CWSDPMI` (DJGPP), Phar Lap | See [DOS games](https://tanepiper.github.io/game-frame/dos.md) for loaders and emulators | | DPMI | The interface extenders use to ask for memory, selectors and interrupts | `INT 31h` | GameFrame's `dos_env` emulates the calls games use | Some games lowered detail or disabled sound when memory was short. Find the memory checks and fix their result in the port, so every run takes the same path. ## Storage and CDs - **Hard disk files** are read with DOS calls (`INT 21h`) or the C runtime. GameFrame's `fileio_trace.py` turns a DOSBox-X log into a map of the reads. - **CD audio**: many games played music as Red Book audio tracks on the CD, through `MSCDEX` (`INT 2Fh`, `AX = 15xxh`) in DOS or MCI (`mciSendCommand`) in Windows. The music is then not in any file. The port needs the player's CD audio tracks ripped to files. Document how the player does that. - **CD checks**: a game checks for its disc by reading a file or a volume label. The port reads the player's files and does not need the check. Breaking copy protection is restricted in many countries ([Your own copy and the law](https://tanepiper.github.io/game-frame/preservation/law/index.md)). - **Streaming**: some games read from the CD while playing (video, music, level data). The read times then affected the game's timing. A port reads from disk and need not reproduce delays unless the game logic depended on them. ## Input devices | Device | How DOS games read it | How Windows games read it | |---|---|---| | Keyboard | Their own `INT 9` handler reading port `60h`; BIOS `INT 16h` | Window messages, DirectInput | | Mouse | The mouse driver, `INT 33h` | Window messages, DirectInput | | Joystick (game port) | Write to port `201h`, then time how long each axis bit stays set | WinMM `joyGetPosEx`, DirectInput | | Gamepads (Gravis GamePad, Sidewinder) | Through the game port, some with their own digital protocols | DirectInput | The game port is an analogue device read by timing. The count depends on the CPU's speed, so games calibrated the joystick at start-up. Port the result of the calibration, the position in the game's own range, as an input ([Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md)). ## Networks and multiplayer | Method | Years | Signs in the code | |---|---|---| | Serial cable (null modem) | from the 1980s | The UART at port `3F8h` or `2F8h`; `INT 14h` | | Modem | from the 1980s | Serial port code plus `AT` command strings (`ATDT`, `ATZ`) | | IPX on a local network | 1993 to about 2000 | `INT 7Ah` or `INT 2Fh` with `AX = 7A00h` to find the IPX driver | | NetBIOS | early 1990s | `INT 5Ch` | | DirectPlay | 1996 to about 2003 | `dplayx.dll`, `IDirectPlay` interfaces | | TCP/IP (Winsock) | from 1995 | `wsock32.dll`, `ws2_32.dll` | Multiplayer games of this time are usually lockstep: every computer runs the whole game and only commands travel ([Strategy](https://tanepiper.github.io/game-frame/games/strategy.md), [Networking](https://tanepiper.github.io/game-frame/game-patterns.md#networking-if-any)). The message format is the input record for multiplayer. Find it, together with the packet a game sends each turn. - A port can replace the transport (IPX, serial) with a modern one. The port must keep the messages and the turn timing if two copies of the port must play together. The same holds if the port must play with the original, which is rarely worth it. - Tools from the time (Kali, Kahn) tunnelled IPX over the internet. DOSBox can do the same, so you can run two copies of the original and capture their messages. --- # Software engines and middleware Most games from the 1980s and early 1990s were written from scratch. Each studio had its own drawing code, its own file formats and its own tools. From the mid-1990s, more and more games were built on a licensed engine (Build, the Quake engines, Unreal) or on an engine the studio reused across several games (Westwood's, Blizzard's, BioWare's Infinity Engine). Most games also use **middleware**: libraries for one job, such as video (Smacker, Bink), sound (Miles, FMOD) or physics (Havok). Knowing the engine helps in several ways. - The file formats are often already documented by modders, sometimes for decades. - The engine's source code may have been released. Then you study the source for the engine and reverse engineer only the game code on top of it. - The game's rules may live in a script language the engine interprets (QuakeC, UnrealScript, Duke Nukem 3D's CON files, Infinity Engine scripts). Once the interpreter is ported, the port runs the rules from the game's data. - An engine reimplementation may already run the game. Its documentation and tests tell you how the formats and the scripts behave. Read its documents and its behaviour. Follow your project's rules about reading its code ([Legal](https://tanepiper.github.io/game-frame/legal.md)). - Middleware is library code from another company. Identify it early and leave it out of your analysis. Replace it in the port with a library that does the same job. ## Finding the engine - [Recognising an engine](https://tanepiper.github.io/game-frame/engines/recognising.md): strings, file names, magic numbers, libraries and debug paths that identify an engine or a library. ## Engine families - [The id Software engines](https://tanepiper.github.io/game-frame/engines/id-tech.md): Wolfenstein 3D, Doom, Quake, Quake II, Quake III, and the engines built from them. - [Build, Jedi and other 2.5D engines](https://tanepiper.github.io/game-frame/engines/build-2-5d.md): sector engines with a flat world and heights. - [Unreal Engine 1 and 2](https://tanepiper.github.io/game-frame/engines/unreal.md): packages, native classes and UnrealScript. - [Other licensed 3D engines](https://tanepiper.github.io/game-frame/engines/licensed-3d.md): GoldSrc, LithTech, NetImmerse and Gamebryo, RenderWare. - [Studio engines for 2D and isometric games](https://tanepiper.github.io/game-frame/engines/studio-2d.md): Westwood, Blizzard, the Infinity Engine, the Fallout engine, the adventure engines. ## Libraries - [Middleware](https://tanepiper.github.io/game-frame/engines/middleware.md): video, sound, physics, compression, scripting and online libraries, and what to do with each. ## How an engine changes each step | Step | Without a known engine | With a known engine | |---|---|---| | Inventory | Every file type is new | Look up the engine's formats first | | Decompile | Every function is unknown | Name the engine's functions from its source, its exports or a reimplementation's documents, and label them in Ghidra | | Trace | Find the rules in machine code | Look in the scripts first; the machine code holds the engine and the native parts of the game | | Emulate | Every routine may need ground truth | The engine's maths often matches its released source; check that first, then measure the game's own code | | Port | Write everything | Reuse a format reader, write the interpreter, port the native game code | | Verify | Compare with the original | Also compare with the engine's own demos, test maps and tools | ## A warning about versions Licensees changed the engines they bought. Duke Nukem 3D, Shadow Warrior and Blood all use the Build engine. Each of them changed its code. Deus Ex runs Unreal Engine 1 with Ion Storm's changes. A released engine source shows one version. Confirm every detail against the binary you are studying before you rely on it, and record which version the source was. --- # Recognising an engine A game built on an engine usually shows it in its files: archive formats, file extensions, DLL names, version strings, copyright notices and debug paths. Check these during inventory, before you decompile anything. `tools/pe/peinfo.py` prints imports, exports, the PDB path and engine fingerprints for Windows binaries; `tools/dos/dosinfo.py` does the same for DOS programs. ![peinfo.py on the sample game's game.exe. It prints the architecture, the image base, the link date, the PDB name, the sections with their entropy, and the imported DLLs with engine.dll first.](https://github.com/tanepiper/game-frame/blob/main/docs/assets/captures/peinfo.svg) ## Ask your agent Identify the engine. Copy this prompt into your coding agent: ```text In my GameFrame project, identify the engine and the libraries the game uses. 1. Read docs/engines/recognising.md and docs/engines/middleware.md. 2. Check the file extensions and magic numbers, the DLLs, the strings, the PDB path and the RTTI names (tools/pe/peinfo.py or tools/dos/dosinfo.py). 3. Write the engine and each library into docs/FINDINGS.md with the evidence for each. 4. Label the library code in Ghidra with a prefix, so later tracing skips it. 5. If the engine's source was released, tell me its version and licence before reading it. ``` ## Where to look | Place | What to look for | How | |---|---|---| | File extensions and archive magic | `.WAD` with `IWAD`/`PWAD`, `.PAK` with `PACK`, `.GRP` with `KenSilverman`, `.u` packages | `xxd FILE | head`, the table below | | Strings in the executable | engine names, version strings, copyright notices, error messages, console commands | `strings -n 6 GAME.EXE | less`; `strings.txt` in the Ghidra export | | Libraries beside the executable | `glide2x.dll`, `Engine.dll`, `mss32.dll`, `binkw32.dll`, `fmod.dll`, `smackw32.dll` | `ls`; `peinfo.py` lists imports | | The PDB path in a Windows binary | the studio's folder names, the engine's folder names | `peinfo.py` prints it | | RTTI class names (MSVC C++) | `.?AVUObject@@`, `.?AVNiNode@@`, `.?AVRwFrame@@` | `peinfo.py`; `strings` for `.?AV` | | Configuration files | `[Engine.Engine]` sections, `config.cfg` with console variables | open them | | The credits and the manual | "Uses Bink Video", "Build engine by Ken Silverman" | read them | ## Signatures of common engines | Engine | Typical games | Signatures | |---|---|---| | Wolfenstein 3D engine | Wolfenstein 3D, Spear of Destiny, Blake Stone | `VSWAP`, `GAMEMAPS`, `MAPHEAD`, `AUDIOT` files | | id Tech 1 (Doom) | Doom, Doom II, Heretic, Hexen, Strife | `.WAD` starting `IWAD` or `PWAD`; lumps `THINGS`, `LINEDEFS`, `SECTORS` | | Quake engine | Quake, Hexen II | `.PAK` starting `PACK`; `progs.dat` (QuakeC byte code); `.BSP` version 29 | | Quake II engine | Quake II, Heretic II, Kingpin, SiN, Soldier of Fortune | `gamex86.dll`; `.PAK`; `.BSP` starting `IBSP` version 38; `.wal` textures | | Quake III engine (id Tech 3) | Quake III Arena, Return to Castle Wolfenstein, Star Trek: Elite Force, Call of Duty | `.pk3` (ZIP) files; `.qvm` modules; `.BSP` `IBSP` version 46 or 47 | | GoldSrc | Half-Life, Counter-Strike, Team Fortress Classic | `hl.exe`, `hw.dll`, `sw.dll`, `client.dll`, `hl.dll`; `.wad` starting `WAD3`; `.BSP` version 30 | | Build engine | Duke Nukem 3D, Shadow Warrior, Blood, Redneck Rampage | `.GRP` starting `KenSilverman`; `.ART` tiles; `.MAP` with version 7 | | Jedi engine | Star Wars: Dark Forces, Outlaws | `.GOB` starting `GOB\x0a` (Dark Forces), `.LAB` archives (Outlaws); `.LEV`, `.INF`, `.O` files | | Unreal Engine 1 and 2 | Unreal, Unreal Tournament, Deus Ex, Rune, Splinter Cell | `Core.dll`, `Engine.dll`; packages `.u`, `.utx`, `.unr` starting with `C1 83 2A 9E` | | LithTech | Shogo, Blood II, No One Lives Forever, AvP 2 | `.REZ` archives; `lithtech.exe`; `.dat` worlds, `.abc` models | | NetImmerse and Gamebryo | Morrowind, Oblivion, Civilization IV, Dark Age of Camelot | `.nif` files starting `NetImmerse File Format` or `Gamebryo File Format` | | RenderWare | Grand Theft Auto III, Vice City, San Andreas, Burnout | `.dff` models, `.txd` texture dictionaries, `.rws` streams; `RenderWare` strings | | Infinity Engine | Baldur's Gate, Planescape: Torment, Icewind Dale | `chitin.key`, `.bif` archives; `.bcs` compiled scripts; `.are`, `.cre`, `.itm` | | Westwood engines | Dune II, Lands of Lore, Command & Conquer, Red Alert, Dune 2000 | `.PAK` archives (Dune II, Lands of Lore); `.MIX` archives (from Command & Conquer); `.SHP`, `.VQA`, `.AUD` | | Blizzard engines | Diablo, StarCraft, Diablo II, Warcraft III | `.MPQ` archives starting `MPQ\x1a`; `storm.dll` | | SCUMM | Monkey Island, Day of the Tentacle, Sam & Max | `.LFL`, `.000`, `.LA0`, `.SOU` files; resource blocks `ROOM`, `SCRP`, `LECF` | | Sierra SCI | King's Quest IV to VII, Space Quest III to 6, Gabriel Knight | `RESOURCE.MAP`, `RESOURCE.000`; `SCIV.EXE` or `SIERRA.EXE` | | AGS (Adventure Game Studio) | many independent adventures | `acsetup.cfg`; data appended to the executable, starting `CLIB` | | Unity | from 2005 | `UnityPlayer.dll`, `*_Data` folder, `globalgamemanagers` (see `re-inventory`) | | Unreal Engine 3 and later | from 2006 | `.upk` or `.pak`; `UE3` or `UE4` strings (see `re-inventory`) | A game may also use a modified engine with new formats. When the signatures are close but do not match, compare the header layout with the engine's documented format. A changed version number or one extra field is common. ## Libraries that are not the engine The executable often contains code from libraries the game linked in. Ghidra cannot tell your game's functions from a library's. Find and label the library code first, so you do not trace it by mistake. | Library code | How to spot it | What to do | |---|---|---| | The C runtime (MSVC, Watcom, Borland, DJGPP) | `malloc`, `printf`, `qsort`, `_ftol`, startup code before `main`; copyright strings | Apply function signatures for the compiler (Ghidra's Function ID, FLIRT-style signatures), or label by hand from the strings | | The C++ standard library | `std::` names in RTTI, `basic_string` | Label, do not trace | | zlib | `deflate 1.1.3 Copyright` and similar strings; the CRC table `0x77073096` | Label; use zlib in the port | | Middleware | see [Middleware](https://tanepiper.github.io/game-frame/engines/middleware.md) | Label; replace in the port | | Compiler helpers | `__allmul`, `__aullrem`, `_chkstk`, Watcom's register-call helpers | Label; they implement plain arithmetic | Once these are labelled, the remaining unlabelled code is the game and its engine. --- # The id Software engines Many first-person games of the 1990s ran on id Software's engines. Many other studios licensed them. id released the source code of each engine some years after its games, so these engines are the best documented of the era. Use them to learn how a game engine of the time worked. Use the released source as a reference when you study a game built on a licensed copy. | Engine | First game, year | Source released | Platform of the original | |---|---|---|---| | Wolfenstein 3D | Wolfenstein 3D, 1992 | 1995 | DOS, 16-bit real mode, Borland C++ and assembly | | id Tech 1 | Doom, 1993 | 1997, GPL from 1999 | DOS, 32-bit protected mode (DOS/4GW), Watcom C | | Quake engine | Quake, 1996 | 1999 | DOS (DJGPP), later Windows | | Quake II engine | Quake II, 1997 | 2001 | Windows | | id Tech 3 | Quake III Arena, 1999 | 2005 | Windows, Linux, Mac OS | ## Wolfenstein 3D The world is a 64 × 64 grid of tiles. The renderer casts one ray per screen column against the grid and draws a scaled wall slice for each. Sprites are drawn after the walls, clipped against a per-column depth buffer. Game time runs at 70 ticks per second, from the reprogrammed timer. What to note when you study a game built on it: - Most of the game logic is in C. Most of the drawing is in 16-bit assembly. - Actors use state tables: each state has a sprite, a duration in tics, a "think" function and the next state. - Maps are compressed with RLEW and Carmack compression; graphics use Huffman compression. ## id Tech 1 (Doom) The world is a 2D map of sectors with floor and ceiling heights. A precomputed binary space partition (BSP) tree orders the walls from front to back, so the renderer draws each screen column once. All world positions are 16.16 fixed point. - **Time.** The game runs at 35 tics per second. The renderer draws as often as it can, but the game state changes only once per tic. - **Thinkers.** Every moving object, door and lift is a thinker in a linked list, updated in list order every tic. - **Random numbers.** Doom has no random generator in the usual sense. `P_Random` returns the next entry of a fixed table of 256 bytes, and an index wraps around it. Demos depend on that index, so the port must keep the order of calls. - **Demos.** A demo file stores the game settings and then one command per player per tic (forward move, side move, turn, buttons). Playing it back runs the same game, so any change in the order of thinkers or random calls breaks old demos. Chocolate Doom exists to keep every quirk that demos depend on. - **Data.** Everything is in WAD files as named lumps. Levels are a group of lumps (`THINGS`, `LINEDEFS`, `SIDEDEFS`, `VERTEXES`, `SEGS`, `SSECTORS`, `NODES`, `SECTORS`, `REJECT`, `BLOCKMAP`). Heretic, Hexen and Strife licensed this engine and changed it. Hexen added a byte-code script language (ACS) and a different map format. ## Quake Quake has a true 3D world in a BSP tree with precomputed visibility (PVS) and light maps. The game logic is written in **QuakeC** and compiled to `progs.dat`, a byte-code file the engine interprets. Even single-player Quake runs as a client and a server in one process. - To study a Quake mod or a game on this engine, decompile `progs.dat` with a QuakeC decompiler before you look at machine code. The engine's own code changes far less between games. - The software renderer was written for the Pentium. It overlaps the floating-point divide for perspective correction with integer work, every 16 pixels. If an inner loop looks strange, look for this overlap. - id released GLQuake, an OpenGL version, as a separate free download in 1997. It ran on 3dfx cards through a "MiniGL" driver ([3D accelerators](https://tanepiper.github.io/game-frame/hardware/graphics-3d.md)). ## Quake II The game logic moved from byte code to a native DLL, `gamex86.dll`, written in C. The engine calls it through a table of functions. It calls the engine through another table. Licensees replaced the DLL to make their own games. When you study a Quake II-engine game, start at the DLL's single export, `GetGameAPI`, which returns the table of game functions. ## id Tech 3 (Quake III) Game code is C compiled to **QVM** byte code by a modified LCC compiler. The engine can run the QVM in an interpreter, compile it to machine code when it loads, or load a native DLL of the same code instead. Three modules exist: the server game (`qagame`), the client game (`cgame`) and the user interface (`ui`). Data is in `.pk3` files, which are ZIP archives. Licensees changed it heavily. Call of Duty's engine began as id Tech 3 and changed further with each game. ## How to use the released source 1. Find the version of the source that matches the game. Patches changed behaviour; the released code is usually the last patch. 2. Name the engine functions in your Ghidra project by matching them to the source. Strings, error messages and the order of functions in the binary help. 3. Mark every function that differs from the source. Those are the licensee's changes. Spend most of your reverse engineering on them. 4. Check that your project's rules allow reading the source. id's sources are under the GPL; reading them to understand behaviour is common. Copying them makes your project a derivative under the GPL ([Legal](https://tanepiper.github.io/game-frame/legal.md)). ## Projects to learn from | Project | Engine | What it shows | |---|---|---| | Chocolate Doom | id Tech 1 | Keeping demo compatibility by keeping the original's quirks | | Wolf4SDL | Wolfenstein 3D | A port of a 16-bit DOS engine to modern systems | | ioquake3 | id Tech 3 | Long-term maintenance of an engine with QVM modules | | QuakeSpasm, vkQuake | Quake | Keeping the original's look with new renderers | --- # Build, Jedi and other 2.5D engines Between Doom (1993) and true 3D engines (Quake, 1996), several engines drew 3D-looking worlds from 2D maps with heights. They are called 2.5D engines. The map is a set of polygons (sectors) seen from above, each with a floor and a ceiling height. The renderer draws walls column by column and floors and ceilings row by row. Most of these engines added features that Doom lacked: sloped floors, rooms over rooms, mirrors. ## Build Ken Silverman wrote the Build engine for 3D Realms. It was licensed widely. | Game | Year | Developer | Notes | |---|---|---|---| | Duke Nukem 3D | 1996 | 3D Realms | Game logic partly in CON script files | | Shadow Warrior | 1997 | 3D Realms | Its own game code in C | | Blood | 1997 | Monolith | Heavily changed engine and its own game code | | Redneck Rampage | 1997 | Xatrix | Built from the Duke Nukem 3D code | The engine source was released in 2000 and the Duke Nukem 3D game source in 2003. **How it works.** Sectors are polygons. Each wall points to the next wall of the same sector and, through a portal, to the wall of the neighbouring sector. The renderer walks from the player's sector through portals, drawing what it can see. Two sectors may overlap in the map, because the engine never checks that the world is consistent, so rooms can be in the same place on the map. Sprites can be flat on the floor or on a wall, or face the camera. **What to note.** - Positions are integers. Heights use a different scale from horizontal positions (a factor of 16 in Duke Nukem 3D). Watch the shifts when you read maths code. - Duke Nukem 3D's CON files define actors, weapons and sounds in a small script language. The game compiles them when it starts and interprets the result. Read the CON files before you trace actor behaviour in machine code. - The game timer runs at 120 ticks per second in Duke Nukem 3D; the game logic runs at a fraction of that (30 updates per second). Find the exact rate in the timer code of your game. - Files are packed in `.GRP` archives. Art is in `.ART` files with a shared palette and shade tables (`PALETTE.DAT`, `LOOKUP.DAT`). **Projects to learn from.** EDuke32 (a source port that keeps old CON scripts working), NBlood (Blood's game code reimplemented from reverse engineering, on EDuke32's engine), and Raze (several Build games in one modern engine). ## The Jedi engine LucasArts wrote the Jedi engine for Star Wars: Dark Forces (1995) and reused a much changed version of it for Outlaws (1997). Like Build, it has sectors with floor and ceiling heights, plus rooms above rooms and 3D object models. - Level logic is in `.INF` files: a text language that defines elevators, doors, switches and triggers as small state machines. - Dark Forces keeps its data in `.GOB` archives; Outlaws uses `.LAB` archives instead. Levels are `.LEV` (geometry) and `.O` (objects). - The source was never released. The Force Engine is a reimplementation built from reverse engineering. Its documents explain the formats and the INF language. ## Other engines of the type | Engine | Games | Notes | |---|---|---| | Doom engine variants | Heretic, Hexen, Strife | See [The id Software engines](https://tanepiper.github.io/game-frame/engines/id-tech.md) | | ROTT engine | Rise of the Triad (1994) | Built from the Wolfenstein 3D engine. Each map keeps one floor and one ceiling height; raised areas are built from floating platform objects | | Voxel Space (NovaLogic) | Comanche (1992), Delta Force (1998) | Terrain drawn from a height map and a colour map, one column at a time | | XnGine (Bethesda) | The Elder Scrolls: Daggerfall (1996) | True 3D for its time; Daggerfall Unity documents its formats | | Ultima Underworld engine (Looking Glass) | Ultima Underworld (1992), System Shock (1994) | Texture-mapped 3D with sloped floors, before Doom | ## How to approach a 2.5D game 1. Decode the map format first. With the sectors, walls and objects in hand, you can draw the map from above and see the level's layout. 2. Find the renderer's main loop. In these engines it is usually one large function that walks sectors or columns. It is the slowest code in the game, so it is often in assembly. 3. Separate the renderer from the game logic. You can rebuild the renderer to match its output. The game logic must be exact. 4. Find the timer rate and the game update rate. They are often different. 5. Compare frames from the same position and angle with the original ([Running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md)). A software renderer's output is exact, so compare pixel by pixel. --- # Unreal Engine 1 and 2 Epic Games' Unreal Engine 1 (Unreal, 1998) and Unreal Engine 2 (2002) were the main competitors to the Quake engines. They were licensed for many games, from first-person shooters to role- playing and stealth games. | Engine | Games | |---|---| | Unreal Engine 1 | Unreal (1998), Unreal Tournament (1999), Deus Ex (2000), Rune (2000), The Wheel of Time (1999), Undying (2001) | | Unreal Engine 2 | Unreal Tournament 2003 and 2004, Splinter Cell (2002), Postal 2 (2003), Lineage II (2003), Thief: Deadly Shadows (2004) | Epic never released the full source of these engines publicly. Licensees received it, and public headers came with the editing tools and some patches. Unreal Tournament's public headers (for native mods) describe many classes in detail. ## How the engine is organised - **Packages.** Everything is in package files: code (`.u`), textures (`.utx`), sounds (`.uax`), music (`.umx`), meshes (`.usx` in UE2) and maps (`.unr`, `.ut2`). A package has a header, a name table, an import table and an export table. Objects refer to each other by index into these tables. Readers for the format exist in many modding tools. - **Objects and classes.** Every object derives from `UObject`. Classes are defined in UnrealScript; some classes also have **native** parts written in C++ in the engine DLLs. A native class's C++ code is in a DLL such as `Engine.dll` or the game's own DLL. Its script code is in a `.u` package. - **UnrealScript.** The game logic is mostly UnrealScript, compiled to byte code inside the `.u` packages. The engine interprets it. Tools decompile the byte code back to readable script. Start there for game rules, as for QuakeC. - **The DLLs.** `Core.dll` holds the object system, the name table and the script interpreter. `Engine.dll` holds the actor system, levels and the renderer interface. Each renderer is a separate DLL (`GlideDrv.dll`, `D3DDrv.dll`, `OpenGLDrv.dll`, `SoftDrv.dll`). Each audio device is one too (`Galaxy.dll`). ## What to note - The DLLs export many C++ functions with their mangled names. Demangle them to get the class and method names before you start tracing. `tools/pe/peinfo.py` lists exports. - The script interpreter dispatches on byte codes through a table of native functions. Native functions have fixed numbers, which the script calls. Map the numbers to names early. - The game's tick takes the frame time. It is a variable time step, so replays need the frame times recorded ([The main loop](https://tanepiper.github.io/game-frame/game-patterns.md#the-main-loop)). - Licensees changed the engine and the package format version. Read the version field in each package header. Check which fields your game's version has. ## How to approach an Unreal Engine game 1. List the packages and read their name, import and export tables with an existing reader. 2. Decompile the `.u` packages to script. Most game behaviour is there. 3. Use the engine DLLs' exports to name the native functions in Ghidra. 4. Trace the native code that the script calls for the rules you need: physics, movement, collision, damage. 5. Reimplement the script interpreter and the native functions, or translate the scripts. A reimplemented interpreter keeps mods working. Translating the scripts is simpler if you only need one game. --- # Other licensed 3D engines Besides the id engines and Unreal, several engines were sold to other studios in the late 1990s and early 2000s. Each has well-known file formats that modders documented. ## GoldSrc Valve built GoldSrc for Half-Life (1998), starting from the Quake engine and adding parts of Quake II's code and its own systems. - `hl.exe` loads the engine from `hw.dll` (hardware rendering: OpenGL or Direct3D) or `sw.dll` (software rendering). - The game logic is in two native DLLs: `hl.dll` (the server: monsters, weapons, rules) and `client.dll` (the client: the HUD, prediction, effects). Mods replace them. The Half-Life SDK, released by Valve, contains their source code. - Maps are BSP version 30 with external textures in `.wad` files (`WAD3`). Models are `.mdl` with skeletal animation. - Scripted sequences, triggers and entity connections are in the map's entity list as text. - Xash3D FWGS is a compatible engine reimplementation that runs the original game DLLs. ## LithTech Monolith Productions made LithTech for Shogo (1998) and Blood II (1998), then licensed it. Later games include No One Lives Forever (2000) and Aliens versus Predator 2 (2001). - Data is in `.REZ` archives. Worlds are `.dat`, models `.abc` or `.ltb`, textures `.dtx`. - The game code is in DLLs (`object.lto` for the server objects, `cshell.dll` for the client), which the engine loads. They are ordinary Windows DLLs under other names. - Some LithTech games shipped with tools and SDK source for modding. Use them. ## NetImmerse and Gamebryo NetImmerse, from Numerical Design Limited (NDL), was renamed Gamebryo in 2003. NDL became part of Emergent Game Technologies in 2005. It is a scene-graph library more than a full engine. Studios wrote the game around it. - Games include Morrowind (2002), Dark Age of Camelot (2001), Oblivion (2006) and Civilization IV (2005). - Models and scenes are `.nif` files: a list of blocks, each a class (`NiNode`, `NiTriShape`, `NiTexturingProperty`), with references between blocks by index. The NifTools project documents every version of the format. - RTTI names starting with `Ni` identify the library code in the executable. Label it and move on to the game's own classes. ## RenderWare Criterion Software's RenderWare was a cross-platform graphics library used on PC and consoles (PlayStation 2, Xbox, GameCube). Games include Grand Theft Auto III (2001), Vice City (2002), San Andreas (2004) and Burnout (2001). - Files are chunks with a type, a size and a library version: `.dff` (models), `.txd` (texture dictionaries), `.rws` (streams). A reader can walk a file chunk by chunk from the headers. - The same game on PC and PlayStation 2 uses the same chunk format with different platform data inside, so a PC reader helps with the console version. ## How these engines change the work - The game code is often in separate DLLs. Study those first; the engine code is shared with other games and often documented by modders. - Look for an SDK. GoldSrc, LithTech and several Unreal licensees released SDKs with the game code's headers or source for modders. - Check the version of each format in its header before using a community reader. --- # Studio engines for 2D and isometric games Strategy games, role-playing games and adventures of the 1990s mostly ran on engines a studio built for itself and reused across its games. They were rarely sold, but each studio's games share formats and code, so work on one game helps with the others. ## Westwood Studios Dune II (1992), Lands of Lore (1993), Command & Conquer (1995), Red Alert (1996) and Dune 2000 (1998) share many formats. The formats changed between games, so check each header. - Dune II and Lands of Lore keep their files in `.PAK` archives. From Command & Conquer, the games use `.MIX` archives with entries named by a hash of the file name. - `.SHP` sprites (the layout differs between generations), `.VQA` video, `.AUD` audio with Westwood's ADPCM. - Rules for units and buildings are in INI files (`RULES.INI` in Red Alert), so many rules are data. Read them first. - Electronic Arts released the source of Command & Conquer and Red Alert in 2020. For these two games, read the source before you reverse engineer. ## Blizzard Entertainment Diablo (1996), StarCraft (1998), Diablo II (2000) and Warcraft III (2002) share `storm.dll` and the MPQ archive format. - `.MPQ` archives with a hash table of file names, optional compression (several methods) and encryption with keys derived from the file names. StormLib reads them. - `storm.dll` also holds shared code: memory, files, networking and drawing helpers. Its functions are exported by ordinal; community lists name many of them. - StarCraft's replays store commands per game tick. The simulation is fully deterministic. OpenBW reimplemented it exactly enough to play replays. - Diablo's Devilution project reconstructed the source with help from debug information left in the Japanese PlayStation port. Look for such builds of your game: demos, other platforms and beta versions sometimes kept symbols. ## The Infinity Engine (BioWare) Baldur's Gate (1998), Planescape: Torment (1999), Icewind Dale (2000) and their sequels. - `chitin.key` lists every resource and the `.bif` archive that holds it. - Areas (`.are`), creatures (`.cre`), items (`.itm`), spells (`.spl`), dialogues (`.dlg`) and 2DA tables (`.2da`, plain text tables of rules) hold almost all the game's rules. - Scripts (`.bcs`) are compiled lists of trigger and action blocks, run in a fixed order for each creature and area. Their trigger and action numbers are listed in `TRIGGER.IDS` and `ACTION.IDS`. - GemRB reimplements the engine from these formats. The IESDP documents every format. ## The Fallout engine (Interplay) Fallout (1997) and Fallout 2 (1998). - `.DAT` archives; `.FRM` sprites with several directions; `.MAP` maps; `.PRO` prototypes for items, critters and scenery. - Scripts are written in a C-like language (SSL) and compiled to `.INT` byte code, which the engine interprets. Decompilers exist. - Fallout 2 Community Edition is a reimplementation built from reverse engineering. ## Adventure engines Point-and-click adventures almost always ran on a script interpreter. See [Adventure and story games](https://tanepiper.github.io/game-frame/games/adventure.md) for the families (SCUMM, SCI, AGI) and ScummVM, which reimplements most of them. ## How to approach a studio engine 1. Find the archive format and list every file. These formats are usually documented already. 2. Read the rule tables and text formats before any code. In these games, many of the rules are in data. 3. Find the script interpreter if there is one. List its opcodes. 4. Trace the native code for what remains: the simulation step, combat formulas, pathfinding, AI. See the family pages for what must be exact ([Strategy](https://tanepiper.github.io/game-frame/games/strategy.md), [RPGs](https://tanepiper.github.io/game-frame/games/rpg.md)). --- # Middleware Middleware is a library that a game licenses for one job. In most cases it does not need to be reverse engineered. Identify it, label its functions, and replace it in the port with a library that does the same job. Check whether the game depends on any exact behaviour of the library, such as its random numbers, its rounding or its timing. ## Common libraries of the era | Job | Library | Signs in the files | In the port | |---|---|---|---| | Video | Smacker (RAD Game Tools, 1994) | `.SMK` files starting `SMK2` or `SMK4`; `smackw32.dll` | FFmpeg decodes Smacker | | Video | Bink (RAD, 1999) | `.BIK` files starting `BIK`; `binkw32.dll` | FFmpeg decodes most Bink 1 files | | Video | Westwood VQA, Sierra VMD, Cinepak in AVI | `.VQA`, `.VMD`, `.AVI` | FFmpeg, or a small decoder from the documented format | | Sound | Miles Sound System (RAD), earlier the Audio Interface Library | `mss32.dll`, `MSS` strings; in DOS, `AIL` driver files (`.DIG`, `.MDI`) | A modern audio library; MIDI through a software synthesiser | | Sound | DIGPAK and MIDPAK, HMI Sound Operating System | DIGPAK drivers as `.COM` files and MIDPAK music drivers as `.ADV` files, called through `INT 66h`; `HMI*.386`; `SOS` strings | The same | | Sound | FMOD | `fmod.dll`, `fmodex.dll`; `FMOD` strings | A modern library; FMOD's tracker-module playback has its own quirks to match | | Sound | DirectSound, DirectMusic, A3D, EAX | imports from `dsound.dll`, `dmusic`; `a3d.dll`; `EAX` strings | See [Sound hardware](https://tanepiper.github.io/game-frame/hardware/sound.md) | | 3D graphics | Direct3D, OpenGL, Glide | imports from `ddraw.dll`, `d3d8.dll`, `opengl32.dll`, `glide2x.dll` | See [3D accelerators](https://tanepiper.github.io/game-frame/hardware/graphics-3d.md) | | Physics | Havok (from 2000), MathEngine, PhysX (from 2005) | `hk` prefixes in RTTI and strings; `PhysX` DLLs | Physics middleware results must be matched only if the game's rules depend on them; often they are cosmetic | | Animation | Granny 3D (RAD) | `.gr2` files; `granny2.dll` | A reader for the format, documented by modders | | Scripting | Lua (from 1993), Python, Tcl | `lua_pcall`, `Lua 5.0` strings; `python2x.dll` | Use the same language version; decompile compiled scripts (unluac) | | Compression | zlib, LZSS, LZW, Huffman, PKWARE DCL ("implode") | zlib's version strings and CRC table; `PKWARE` strings | A standard library, or the documented algorithm | | Online | GameSpy SDK, DirectPlay, Kali | `gamespy` strings; `dplayx.dll` | Replace with your own networking; do not contact old services ([Online games](https://tanepiper.github.io/game-frame/games/online.md)) | | Protection | SafeDisc, SecuROM, StarForce, CD checks | `.icd` files, `secdrv.sys`, `~df394b.tmp`; checks for a disc label | Breaking it is restricted in many countries ([Legal](https://tanepiper.github.io/game-frame/legal.md)); the port reads the player's files and does not need it | ## When the port must match the middleware Most middleware can be replaced freely. A few cases need care: - **Music timing.** If the game logic waits for the music (a level ends when a song ends, a cut scene follows the music), the port must match the music library's timing. Measure it. - **Random numbers.** A few libraries use the C runtime's `rand`. If the game also uses `rand`, the library's calls change the game's sequence. Trace every caller of the random function. - **Video frame counts.** Some games count video frames to time events. Use the same frame rate and frame count as the original file. - **Scripting.** Use the same script language version as the game. Lua 4.0 and 5.0 differ in their libraries and byte code, and Lua 5.3 added an integer type. - **Physics.** If physics sets a game result (a ball's bounce in a sports game), the port must match the physics library's exact results. This is hard to match without the same library. Decide early whether your project can accept a deviation and record it. ## How to identify a library inside the executable Libraries are often linked statically, so there is no separate DLL. 1. Search the strings for version and copyright notices. Most libraries include one. 2. Look for the library's constant tables: CRC tables, Huffman tables, DCT coefficients. 3. Compare the function with the library's source if it is open (zlib, Lua). The structure of a compiled zlib inflate loop is easy to recognise once you have seen it. 4. Label the whole library's functions in Ghidra with a prefix (`zlib_`, `lua_`, `smk_`), so you can skip them while tracing. --- # Crates Clean Rust implementations of hardware and runtime pieces that many old games need. Each one comes from a case-study reimplementation, where it was checked against the original program. Here they are generic and tested without any game's files. They are free to use under Apache 2.0. | Crate | What it is | Comes from | |---|---|---| | [gameframe-opl2](https://tanepiper.github.io/game-frame/crates/opl2.md) | The Yamaha YM3812 (OPL2) FM chip of the AdLib and Sound Blaster cards, at its own 49,716 Hz, with a band-limited resampler | Frontier: Elite II, where its output matches the original AdLib driver sample for sample | | [gameframe-wma](https://tanepiper.github.io/game-frame/crates/wma.md) | A Windows Media Audio v2 decoder for xWMA and ASF streams, with no dependencies, building for WebAssembly | Un-Banished, where it decodes every sound of the game within 60 dB of FFmpeg | | [gameframe-msvc-crt](https://tanepiper.github.io/game-frame/crates/msvc-crt.md) | The Microsoft C runtime's `sinf`, `cosf`, `acosf`, `atan2f`, `qsort` order and `rand`, bit for bit | Un-Banished, matching the game's runtime on 200,000 calls per function | | [gameframe-turbo-pascal](https://tanepiper.github.io/game-frame/crates/turbo-pascal.md) | Turbo Pascal's 6-byte `Real` with its exact arithmetic, `Random`, and Pascal data layouts | Tyrian 2026, matching the original runtime on 40,000 operands per routine | ## Ask your agent Check a crate against my game. Copy this prompt into your coding agent: ```text In my GameFrame project, check whether matches this game. 1. Read the crate's README in GameFrame's crates/ folder. 2. Find the game's own version of the code: the runtime functions, the music driver or the decoder. 3. Run the original under GameFrame's emulator on many inputs (the re-emulate skill). 4. Compare the results with the crate bit for bit and report the first difference. 5. If they match, add the crate as a dependency. If not, trace the game's version. ``` ## Use them The crates live in the repository's `crates/` folder, a Cargo workspace. Add one as a git dependency: ```toml [dependencies] gameframe-opl2 = { git = "https://github.com/tanepiper/game-frame" } ``` Each crate's page shows its API, an example, what it does not model, and how to check that it matches your game. ## When to use a crate, and when to check it first A crate reproduces one program's version of a chip or a runtime. Your game may use another version: a later compiler's maths functions, a different Pascal runtime, an OPL3 instead of an OPL2. Before you rely on a crate for exact results: 1. Find the matching code in your game: the runtime functions, the music driver, the decoder. 2. Run the original on many inputs under GameFrame's emulator (`re-emulate`). 3. Compare the results with the crate bit for bit. If they match, use the crate. If they differ, trace your game's version and port it. ## Rules for adding a crate - It comes from a reimplementation where it was checked against the original program. Its README says how it was checked. - It is generic: no game's names, formats or rules in its code. - Its tests run without any game's files. Tests that need a tool (such as FFmpeg) skip when the tool is missing. - It has no `unsafe` code, few dependencies, and builds for `wasm32-unknown-unknown`. - Its README follows [Writing](https://tanepiper.github.io/game-frame/working/writing.md): what it is, how to use it, what it does not model, how to check it against your game, and its licence. - Code from another project keeps that project's licence notice (as `gameframe-wma` does for its codec tables). --- # gameframe-opl2 An emulator of the Yamaha YM3812 (OPL2), the FM synthesis chip on the AdLib card and the Sound Blaster family. DOS games play their music by writing to the chip's registers. Give this crate the same writes at the same times. It makes the samples the chip would make. - It runs at the chip's own rate: one sample every 72 master clocks, 3,579,545 / 72 = 49,716 samples a second. - It models what the published die and datasheet analyses describe: the log-sine and exponent tables, the phase generators with vibrato, the envelope generator with its global counter, the four waveforms, feedback, FM and additive connections, rhythm mode, the noise generator, and the two timers with the status register. - Everything is integer and deterministic. The same writes give the same samples on every platform, including WebAssembly. - A band-limited resampler converts the output to 44.1 or 48 kHz without aliasing. It can adjust its ratio a little to keep a host's audio buffer steady. - No dependencies and no `unsafe` code. It comes from the Frontier: Elite II reimplementation, where it plays the music and effects of the game's own AdLib driver. Its output is identical sample for sample to the original driver running on that project's DOS runtime (*measured*). ## Use it ```toml [dependencies] gameframe-opl2 = { git = "https://github.com/tanepiper/game-frame" } ``` ```rust use gameframe_opl2::{Opl2, Resampler, SAMPLE_RATE}; let mut opl = Opl2::new(); // A sine tone on channel 0: operator settings, then frequency and key on. opl.write(0x20, 0x01); // modulator: multiplier 1 opl.write(0x23, 0x01); // carrier: multiplier 1 opl.write(0x40, 0x3f); // modulator silent opl.write(0x43, 0x00); // carrier at full level opl.write(0x63, 0xf0); // carrier attack fast, decay 0 opl.write(0x83, 0x00); // carrier sustain at full level opl.write(0xa0, 0x41); // frequency number, low bits opl.write(0xb0, 0x32); // key on, block 4, frequency number high bits let mut chip = vec![0i16; 4_972]; // 0.1 s at the chip's rate opl.generate(&mut chip); let mut resampler = Resampler::new(SAMPLE_RATE, 48_000.0); resampler.push(&chip); let mut out = Vec::new(); resampler.pull(&mut out); // f32 samples at 48 kHz assert!(!out.is_empty()); ``` ## Getting the writes right The chip is the easy part. The music is only right if the writes reach the chip at the right times: 1. Capture the original's register writes, with the time of each, from the game running in an emulator ([Sound](https://tanepiper.github.io/game-frame/hardware/sound)). 2. Port the game's music driver so it makes the same writes. Compare the two lists write by write. 3. Drive this crate from the port's writes. Generate samples between writes to match the timing. The driver's tempo usually comes from a timer interrupt the game programmed. Measure its rate from the timer programming; a small error changes a song's length. ## Not modelled - The YM3014 DAC and the card's analogue stage. The output is the chip's 16-bit digital sum. - CSM speech mode (register 08h bit 7), which games rarely use. - The OPL3 (YMF262). Its extra channels, waveforms and stereo are not here. ## Tests ```bash cargo test --release -p gameframe-opl2 ``` The tests check the table end points, silence after reset, a sine tone's pitch and peak, the release to silence after key off, timer 1 and the status register, determinism, and the resampler. ## Licence Apache 2.0. --- # gameframe-wma A decoder for Windows Media Audio version 2 (WMA v2, format tag `0x161`), written in Rust. WMA v2 is the codec inside xWMA files, which many Windows and Xbox games of the 2000s and 2010s use for their sounds. It also decodes WMA v2 streams from ASF (`.wma`) files once their packets are extracted. - No dependencies and no `unsafe` code. - Builds for `wasm32-unknown-unknown`, so a browser build can decode a game's sounds itself. - Checked against FFmpeg's `wmav2` decoder: on every sound of Banished (in Un-Banished), and in this crate's tests on tones and noise that FFmpeg encodes. The signal-to-noise ratio must be at least 60 dB; the tests measure about 130 dB. It comes from [Un-Banished](https://github.com/tanepiper/unbanished), where it decodes every sound of the game. ## Use it ```toml [dependencies] gameframe-wma = { git = "https://github.com/tanepiper/game-frame" } ``` ### Decode a whole xWMA stream xWMA carries no codec extra data, so the parameters come from the `WAVEFORMATEX` header of the file: the number of channels, the sample rate, the average bytes per second and the packet size (`block_align`). `Params::xwma` sets the codec flags that xWMA implies. ```rust use gameframe_wma::{decode, Params}; fn to_pcm(packets: &[u8]) -> Result, gameframe_wma::Error> { // 2 channels, 44,100 Hz, 6,000 bytes per second, 2,230-byte packets. let params = Params::xwma(2, 44_100, 6_000, 2_230); decode(¶ms, packets) // interleaved 16-bit samples } ``` `decode` skips a packet that fails to decode, as FFmpeg does. The output ends with one extra frame that holds the final overlap. ### Decode packet by packet For streaming, use a `Decoder`. It writes float samples in the range -1 to 1. ```rust use gameframe_wma::{to_i16, Decoder, Params}; fn stream(params: &Params, packets: &[u8]) -> Result, gameframe_wma::Error> { let mut decoder = Decoder::new(params)?; let mut pcm: Vec = Vec::new(); for packet in packets.chunks(usize::from(params.block_align)) { decoder.decode_packet(packet, &mut pcm)?; } decoder.flush(&mut pcm); // the last overlap Ok(to_i16(&pcm)) } ``` ### Streams from ASF files An ASF (`.wma`) file stores the codec flags in its extra data (bytes 4 and 5). Build `Params` with those flags. Files made by FFmpeg's encoder use `flags: 1`. ## The API | Item | What it is | |---|---| | `Params` | The stream's parameters; `Params::xwma(channels, rate, bytes_per_sec, block_align)` for xWMA | | `decode(&Params, &[u8])` | Decode a whole stream to interleaved `i16` | | `Decoder` | `new`, `decode_packet`, `flush`, `reset`, and `channels`, `sample_rate`, `delay`, `frame_len` | | `to_i16(&[f32])` | Convert float samples to 16-bit, with rounding and clipping | | `Error` | `Unsupported` (a valid stream that uses a feature this decoder lacks) or `Invalid` | | `WMAV2`, `XWMA_FLAGS` | The format tag and the flags that xWMA implies | ## Source files | File | What it does | |---|---| | `src/lib.rs` | `Params`, `decode`, `to_i16`, `Error` | | `src/decoder.rs` | Super-frames, frames and blocks | | `src/mdct.rs` | The inverse MDCT, through a DCT-IV with a half-size complex FFT | | `src/vlc.rs` | The table-driven decoder for prefix codes | | `src/bits.rs` | The bit reader. Reads past the end give zero bits, as WMA frames allow | | `src/tables.rs` | The constant tables that define the bitstream (codewords, band layouts) | ## Tests ```bash cargo test --release -p gameframe-wma ``` `ffmpeg_parity` encodes two tones and pink noise with FFmpeg, decodes them with this crate and with FFmpeg, and requires a signal-to-noise ratio of at least 60 dB. It skips when `ffmpeg` or `ffprobe` is missing. ## Licence Apache 2.0. `src/tables.rs` holds codec tables from the MIT-licensed oxideav-wma crate; see [THIRD_PARTY.md](https://github.com/tanepiper/game-frame/blob/main/crates/wma/THIRD_PARTY.md). --- # gameframe-msvc-crt Parts of the Microsoft C runtime (MSVC CRT) that a game's results can depend on, rebuilt in Rust so a port gives the same numbers as the original program. | Module | What it reproduces | Why it matters | |---|---|---| | `crt` | `sinf`, `cosf`, `acosf`, `atan2f` of the x64 runtime, operation by operation | They are not correctly rounded. Rust's `f32::sin` can differ in the last bit, which can change a simulation | | `qsort` | `qsort`'s algorithm: median-of-three quicksort with a selection sort for 8 or fewer elements | It is unstable. The order of equal elements is part of the game's behaviour | | `rand` | `rand` and `srand`: the linear congruential generator with `RAND_MAX` 32,767 | Many games use it for their random numbers, or a library they link uses it | `powf`, `floorf` and `sqrtf` are correctly rounded in the runtime and in Rust, so Rust's own functions already match them. It comes from [Un-Banished](https://github.com/tanepiper/unbanished). There the float functions match Banished's copies of the runtime on 200,000 calls each. The sort matches the original's order of equal elements on recorded sorts (*measured* with GameFrame's emulator). ## Use it ```toml [dependencies] gameframe-msvc-crt = { git = "https://github.com/tanepiper/game-frame" } ``` ```rust use gameframe_msvc_crt::{crt, qsort, rand::Rand}; let heading = crt::atan2f(1.0, 2.0); let mut r = Rand::new(1); let first = r.rand(); // 41 let mut scores = vec![(3, "a"), (1, "b"), (3, "c")]; qsort::qsort_by(&mut scores, |x, y| x.0 - y.0); assert!(heading > 0.46 && first == 41 && scores[0].0 == 1); ``` ## Check your game uses these Different compilers and runtime versions have different maths functions. Before you rely on this crate, compare it with your game's own copy: 1. Find the game's `sinf` (or the others) in the decompile: the import from `msvcrt.dll` or `ucrtbase.dll`, or a statically linked copy. 2. Run it under GameFrame's emulator on many inputs (`re-emulate`). 3. Compare the results with this crate bit for bit. If they differ, the game uses another version. Trace its algorithm. The 32-bit runtime's x87 versions of these functions are different code with different results. ## Tests ```bash cargo test --release -p gameframe-msvc-crt ``` The tests check plain values, that each function is within one unit in the last place of the true result, the arctangent table, `rand`'s known first values from seed 1, and that the sort sorts and is unstable. ## Licence Apache 2.0. --- # gameframe-turbo-pascal The parts of the Turbo Pascal and Borland Pascal runtime that a DOS game's results depend on, rebuilt in Rust so a port gives the same numbers as the original program. | Item | What it reproduces | Why it matters | |---|---|---| | `Real48` | The 6-byte `Real` and the System unit's routines for it: conversion, `*`, `/`, `+`, `-`, compare, `Int`, `Frac`, `Sin`, `Cos`, `Trunc`, `Round` | Programs built without the 8087 option do all `Real` arithmetic in software, with their own rounding. IEEE doubles give different last bits | | `TurboRandom` | `RandSeed`, `Random`, `Random(range)`, `Randomize` | Games use it for everything random; the order and the values of the draws are behaviour | | `PascalReader`, `PascalWriter`, `PStr` | Pascal data in memory and in `BlockWrite` files: little-endian integers, one-byte `Boolean`, `Real`, `string[N]` | Records in data files and saves are written field by field in this layout | It comes from Tyrian 2026, the clean-room reimplementation of Tyrian 2000. There every `Real` routine here matches the original program on 40,000 random operands, and 90,000 random draws match bit for bit (*measured* under GameFrame's DOS emulator). `div` is measured only for divisors of 2.0. ## Use it ```toml [dependencies] gameframe-turbo-pascal = { git = "https://github.com/tanepiper/game-frame" } ``` ```rust use gameframe_turbo_pascal::{Real48, TurboRandom}; // Round(x * 0.75) as the game computes it. let scaled = Real48::from_i32(37).mul(Real48::from_f64(0.75)).round(); assert_eq!(scaled, 28); // 27.75 // The same draws as the original, from the same seed. let mut rng = TurboRandom::new(0x1234_5678); let roll = rng.random(6); assert!(roll < 6); ``` ## Check your game uses these Turbo Pascal 5.5, 6.0 and 7.0 and Borland Pascal 7 share these algorithms, but check your game: 1. Find the System unit's routines in the decompile. With Turbo Debugger symbols, `tools/dos/tdinfo.py` names them; otherwise look for the `Real` routines that work on `DX:BX:AX` and `CX:SI:DI`. 2. Run them under GameFrame's DOS emulator on many inputs (`re-emulate`, `dos_emu`). 3. Compare with this crate bit for bit. Programs compiled with `{$N+}` use the 8087 (or its emulator) for `Single`, `Double` and `Extended`. Those types follow the x87 FPU. This crate does not cover them. ## Tests ```bash cargo test --release -p gameframe-turbo-pascal ``` The tests check encodings, exact integer arithmetic, `Round` taking halves away from zero, `Int` and `Frac`, `Sin` and `Cos` against doubles, the generator's formula, and a data round trip. The bit-for-bit checks against the original runtime need a program compiled with it, so they live in the game project that has one. ## Licence Apache 2.0. --- ## Ask your agent Run and capture the original. Copy this prompt into your coding agent: ```text In my GameFrame project, get the original game running where we can watch and capture it. 1. Read docs/observe/running-the-original.md and the re-observe skill. 2. Pick the environment for this game (GameFrame's emulators, DOSBox-X, Wine, a console emulator). Tell me what it needs before you install anything. 3. Write a script that starts the game, plays a fixed input and saves frames and state. 4. Record next to each capture the game version, the settings, the seed and the inputs. 5. Keep every capture in the ignored derived/ folder. ``` # Observing the original These pages describe how to run the original game, watch it, drive it and capture what it does, so each claim about the port can cite a measurement. Read them after you have used the [taxonomy](https://tanepiper.github.io/game-frame/games/index.md) to find what kind of game you have. - [Running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md): where to run it, what to capture, how to drive it by script, how to compare. - [Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md): every kind of input from outside, where each is read, and how a port takes them through one entry point. - [Capturing the interface](https://tanepiper.github.io/game-frame/observe/ui.md): why game interfaces are custom, how to inventory every screen, and how to capture and rebuild them. - [A model to query](https://tanepiper.github.io/game-frame/observe/object-model.md): an object model of the game, like a web page's, with entry points for queries, input, time and capture, in both the original and the port. --- # Running the original A run of the original shows what the code does with real inputs. Run the game wherever you can watch it, drive it, and capture what it shows, plays, reads and keeps in memory. Most *measured* evidence (a label in GameFrame) comes from these runs. ## Where to run it | Environment | For | What you can capture | |---|---|---| | GameFrame's emulators (`pe_emu.py`, `dos_emu`, `dos_env`) | One function or the whole program, headless, scripted, in tests | Registers, memory, calls, random draws, frames from video memory, every byte ([re-emulate](https://tanepiper.github.io/game-frame/skills/re-emulate.md)) | | DOSBox-X | DOS games with a window, a debugger and file logging | Breakpoints, memory dumps, file access logs, screenshots, audio and video recording ([DOS games](https://tanepiper.github.io/game-frame/dos.md)) | | DOSBox Staging | DOS games, accurate and simple to configure | Screenshots, audio and video, raw OPL output | | 86Box, PCem | DOS and Windows 9x games that need exact period hardware | The full machine, slowly | | Wine, CrossOver, Proton | Windows games on macOS and Linux | Debug channels (`WINEDEBUG=+relay` shows every API call), screenshots | | A Windows machine or virtual machine | Windows games as players run them | Debuggers (x64dbg, WinDbg), API monitors, frame capture (RenderDoc, PIX), Steam Remote Play to stream it to another machine | | A console emulator with a debugger (Mesen, bsnes, Dolphin, PCSX2, MAME) | Console and arcade games | Breakpoints, memory, input movies, frame advance | | Your own runtime (as Frontier's port did) | An original that runs on the port's own CPU and DOS model | Everything, inside `cargo test`, natively and in the browser | Prefer headless and scripted runs for evidence: they repeat exactly and run in tests. Use the windowed runs to see what happens and to find where to look. ## What to capture | Capture | Why | How | |---|---|---| | **Frames** | Ground truth for drawing and for the interface | Video memory from the emulator; screenshots; `screencapture` of a stream window; frame capture tools for 3D APIs | | **State** | Ground truth for the rules | Memory dumps at chosen points (the data segment, the heap, named variables); the game's own saves | | **Inputs** | To replay the same run in the port | The game's demo files; breakpoints on input reads ([Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md)) | | **Calls** | The order of functions and random draws | `call_trace.py`; debugger traces; API logs (`WINEDEBUG=+relay`, API Monitor) | | **Files** | File formats measured from the original's own reads | DOSBox-X file logs and GameFrame's `fileio_trace.py`; Process Monitor on Windows | | **Sound** | Music and effects compared sample by sample | The emulated chip's register writes (OPL), WAV recordings from the emulator | | **Timing** | Tick rates, music tempo | Timer programming, frame counts, audio recordings measured over a long run | Keep every capture in the project's ignored folders (`derived/`, `game-data/`): captures are game material ([Legal](https://tanepiper.github.io/game-frame/legal.md)). ## Driving the original Drive the original by script, so you can make each capture again: - In GameFrame's emulators, script the inputs: `dos_env` answers the BIOS and DOS key reads from a queue (`env.keys`). You can also write a game's own key table in memory between frames. - In DOSBox-X, script the debugger over its control channel (Tyrian's `dbx_control.py`) or type into the window with an automation tool. - On a Windows machine streamed to your desk, an agent with computer-use access can watch and press buttons. The video is compressed: compare by layout and with blurred image differences, not pixel by pixel. - Use the game's own demo and replay files whenever they exist. Write down, next to each capture, the inputs that made it: the version of the game, the settings, the seed, the commands or the demo, and the frame. ## Comparing | What | How | Tool | |---|---|---| | Numbers and state | bit for bit, with the first difference reported | `tools/verify/bitcompare.py`; tests that load a captured state and run the port | | Sequences (draws, calls, events) | the first point where they differ | `tools/verify/seqdiff.py` | | Frames | pixel for pixel when both are exact; image difference with blur and a threshold when one is a compressed stream | an image-difference tool (Un-Banished's `imgdiff`) | | Sound | sample for sample when both come from the same chip model; by ear and spectrum otherwise | the OPL register log; a WAV difference | ![bitcompare.py and seqdiff.py on the sample game. The port matches the original in 600 of 600 values. Its 746 random draws are identical. A naive port in double precision matches only 7 values. It is 12 ULP off at most.](https://github.com/tanepiper/game-frame/blob/main/docs/assets/captures/bitcompare.svg) On the sample game, the port matches the emulated original bit for bit. Its random draws come in the same order. A port that computes in double precision instead of float is off by a few ULP. `bitcompare.py` reports each difference. ## Further reading - [Capturing the interface](https://tanepiper.github.io/game-frame/observe/ui.md): an inventory of every screen. - [Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md): what to record. - [A model to query](https://tanepiper.github.io/game-frame/observe/object-model.md): reading the original's state the same way as the port's. - The `re-verify` skill: how the checks fit together. --- # Inputs: everything that comes from outside A reimplementation is exact when it gives the same results as the original for the same inputs. To check this, you must know every input. Besides the keys the player presses, that includes the clock, the files, the hardware the game detects and the order the operating system delivers events. This page lists the kinds of input, where you will find each in a game's code, and how to handle each in a port. **Every input enters the port through one entry point**, as a record per frame (real-time games) or per command (games that wait). Then a recording, a demo or a test script can drive the port. Two runs with the same records give the same results. An input that reaches the game's rules any other way makes two runs differ. A recording cannot repeat it. ## Ask your agent List every input. Copy this prompt into your coding agent: ```text In my GameFrame project, list every input the game reads from outside. 1. Read docs/observe/inputs.md. 2. Search the decompile for the signatures it lists: interrupts, ports, API imports, clock reads, file opens and network calls. 3. For each input, write the address that reads it, what it reads and how often, in a table in docs/FINDINGS.md. 4. Propose the port's input record: one record per frame or per command. Mark which inputs change the rules and which only change the presentation. ``` ## How games read input There are two patterns. Many games use both. | Pattern | What it looks like | What to record | |---|---|---| | **Polled state** | An interrupt handler or the message pump writes key and button state into a table; the frame reads the table at fixed points | The table's contents at each read | | **Events** | A handler acts on each key press or click as it arrives (a window procedure, a callback, an event queue the frame drains) | Each event, in order, with the frame or step at which the game handles it | Find every place that reads input. A game may poll in the game loop, drain an event queue in its menus, and react to a key inside the window procedure for the pause key. The port must model each of them. ## The kinds of input ### The player's controls | Input | Where you see it in DOS games | Where you see it in Windows games | How to handle it | |---|---|---|---| | Keyboard | an `INT 9` handler reading port `0x60` (scan codes) into a key table; BIOS `INT 16h` for menus and text | `WM_KEYDOWN`, `WM_KEYUP`, `WM_CHAR` in the window procedure; `GetAsyncKeyState`, `GetKeyboardState`; DirectInput `GetDeviceState` or buffered data; raw input (`WM_INPUT`) | Record the game's own key table (or its events) per frame. Map host keys to the original's scan codes in the platform layer, never in the rules | | Text entry | `INT 16h`, or the key table with the game's own conversion to characters | `WM_CHAR`, IME messages for Asian languages | Record characters, not keys, where the game reads characters | | Mouse | `INT 33h` (the mouse driver): position, buttons, mickeys (motion counters) | `WM_MOUSEMOVE`, `WM_LBUTTONDOWN` and so on; `GetCursorPos`; DirectInput relative motion; `ClipCursor`, `SetCapture` | Record what the game reads: absolute position in its own coordinates, or motion summed since the last read. Port the game's own scale and acceleration | | Joystick | the game port: `IN` from port `0x201`, timing a loop until each axis bit falls (the count is the axis value); BIOS `INT 15h` function `84h` | `joyGetPos`, `joyGetPosEx` (WinMM); DirectInput; XInput (`XInputGetState`) | Record the value after the game's own calibration and dead zones, and port the calibration | | Touch, pens, wheels, pedals | rare | `WM_TOUCH`, `WM_POINTER`, DirectInput force feedback | Same as joystick: the value the game reads | ### Time | Input | Where you see it | How to handle it | |---|---|---| | A timer interrupt | DOS: the PIT reprogrammed through ports `0x40` and `0x43`, a new `INT 8` (or `INT 1Ch`) handler counting ticks | The tick count is an input per frame. Find the rate from the divisor written to the PIT | | The display's refresh | DOS: polling port `0x3DA` for the vertical retrace; Windows: `Present` with v-sync, `WaitForVerticalBlank` | The refresh sets the frame rate. The port counts frames and does not time them | | The wall clock | DOS: `INT 1Ah`, `INT 21h` function `2Ch`, the BIOS tick count at `0040:006C`; Windows: `GetTickCount`, `timeGetTime`, `QueryPerformanceCounter`, `GetSystemTime`, `time`, `clock`; `RDTSC` | Every read is an input. Record its value, or replace it with frame-based time where the original only measured elapsed time | | Speed calibration | DOS: a delay loop timed at start-up to measure the CPU's speed | The measured count is an input: record it, and pin it in the port | ### Randomness | Input | Where you see it | How to handle it | |---|---|---| | A seed from the clock | `srand(time(NULL))`, `Randomize` in Pascal (reads the clock), a seed from the timer tick count | The seed is an input: record it once, at the point the game seeds | | A seed from the player | a seed field in a new-game dialog, a deal number | Part of the command that starts the game | | Hardware randomness | rare: `RDRAND`, `CryptGenRandom` | Record every value | ### Files and the system | Input | Where you see it | How to handle it | |---|---|---| | Game data | `INT 21h` open and read; `CreateFile`, `ReadFile`, `fopen`, memory-mapped files | Read-only and the same every run: not an input to record, but the port must read the same files (versions differ) | | Configuration | `SETUP.CFG`, `.INI` files, the registry (`RegQueryValueEx`) | An input at start: record it, and pass it in with the starting state | | Saves | read when the player loads | An input like a command: "load this file" | | Command line and environment | `PSP` command tail, `GetCommandLine`, `getenv` (`BLASTER` for the Sound Blaster) | An input at start | | Hardware detection | video card, sound card, memory size (EMS, XMS), CPU type | Usually changes only drivers and quality. If the rules read it (detail levels that change the simulation), record it | | CD audio and CD checks | `MSCDEX` (`INT 2Fh` function `15h`), `mciSendCommand` | Usually not rules; breaking copy protection is restricted in many countries ([Legal](https://tanepiper.github.io/game-frame/legal.md)) | ### The operating system and the window | Input | Where you see it | How to handle it | |---|---|---| | Window messages | focus lost and gained (`WM_ACTIVATE`, `WM_ACTIVATEAPP`), minimise, resize, close | Many games pause when focus is lost. Pausing is a command: record it | | Message order | the message pump (`PeekMessage`, `GetMessage`, `DispatchMessage`) runs between frames, or inside a frame | Find where the pump runs relative to the update. Events that arrive between two updates must reach the port at the same point | | Screen size | the window or display mode the player chose | If the rules use it (picking, the camera, what is visible and so updated), it is an input | | Audio timing | a music driver on its own interrupt, a sound callback that advances game time | If the game's time follows the audio clock, the audio clock is an input | ### Other machines | Input | Where you see it | How to handle it | |---|---|---| | Network messages | IPX (`INT 7Ah`), serial ports (`INT 14h`, ports `0x3F8`), Winsock (`recv`, `WSARecv`), DirectPlay | In lockstep games, the other players' commands are inputs per tick. In client-server games, the server's messages are the inputs ([Online games](https://tanepiper.github.io/game-frame/games/online.md)) | ## Things that are easy to miss These apply to many games: - **Two keyboard paths.** Besides the key table the frame reads, a game may ask the BIOS "is a key waiting?" to skip a screen or end a demo. That is a second input, read at a different point. - **Devices that are not there.** With no joystick or mouse installed, the polling code may write nothing, or only clear a flag. Find out what an absent device leaves in memory, and model that state too. - **Demo files leave state in memory.** A demo read through the runtime's file routines (Pascal `Text`, C `FILE`) keeps its buffer, the open DOS handle and the file position in the program's memory. A capture taken mid-demo must save them, or the port cannot carry on from it. - **Counters owned by interrupts.** Timer interrupts keep counting while the frame waits, so their counters differ from run to run. Take the clock value the frame read as an input (from the original's state after the read), and leave the interrupt's own counters out of comparisons, with the reason written next to the check. - **Put the outputs in the record too.** What the frame sends out (the finished picture copied to the display, a song request, sounds queued per channel) goes in the same per-frame record as the input. Then the core can run headless. A test can compare every output. ## Recording inputs from the original | Method | Gives | Tools | |---|---|---| | The game's own demo or replay files | the exact input record the game itself uses | read the format with `re-format`; play it in the original and the port | | Breakpoints on the reads | the value at every read, in order | DOSBox-X debugger (`BPINT 16`, memory breakpoints on the key table); x64dbg; GameFrame's emulators (`call_trace.py`, `dos_env`) | | Hooks on the API | every Windows input call and its result | Frida, API Monitor, a proxy DLL (on your own copy only) | | Recording the host's input | keys and mouse as the player gave them | a capture script driving the original through an emulator or a remote stream ([Running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md)) | When the original has demos, start there. Their format is the game's own input record. ## Designing the port's input record 1. List every input above that the game reads, with the address of each read. 2. Put the ones that change the rules into one record type per frame or per command. Keep inputs that only change the presentation (window size, volume) outside it. 3. Make the platform layer (keyboard, mouse, window) produce that record. The rules read only the record. 4. Write recordings as the start state (seed, configuration, files' versions) plus the list of records. Two runs from the same recording must give identical state, every frame. 5. Tests and tools use the record too. A test script, a fuzzer or an agent driving the game produces records. It never writes the state directly ([A model to query](https://tanepiper.github.io/game-frame/observe/object-model.md)). --- # Capturing the interface A game's interface is usually written for that game alone. Players also notice it first. This page explains why it is hard, how to make an inventory of it, and how to capture enough of the running original to rebuild it. ## Ask your agent Inventory the interface. Copy this prompt into your coding agent: ```text In my GameFrame project, make an inventory of every screen. 1. Read docs/observe/ui.md. 2. Find screens three ways: from the main menu's code, from the layout and string data, and by playing the game (ask me to play, or drive it by script). 3. For each screen, record how to reach it, the code or layout that draws it, its states, its inputs, what it changes, its text and its font. 4. Capture each state with the inputs that reached it, into derived/. ``` ## Why game interfaces are custom Most software builds its interface from a toolkit: the operating system's buttons, lists and dialogs. Games rarely do. They draw everything themselves, because they control the whole screen, want their own look, and in older games had no toolkit that was fast enough. The games people reverse engineer are mostly from before off-the-shelf engines were common. In the DOS and early Windows years, almost every game had its own engine: its own drawing code, fonts, menus, and input handling. Licensed engines (id Software's, Build, Unreal) came in the mid-1990s and mostly for first-person games; Unity and Unreal became the default for most studios only in the 2010s. Expect these in most games you work on: - **Each screen is drawn by code.** Menus, HUDs, shops, inventories and dialogs are drawn by the game's own functions, often straight to video memory, with the game's own font. - **Little is shared between screens.** Two menus may be built by two different routines with different input handling and timing. - **The interface code contains rules.** What can be bought, dropped or built, and in what order screens appear, are often decided inside the interface code. - **Later games use data.** Some games (Banished, Infinity Engine games, many 2000s games) read layouts, text and events from data files: a widget tree with names and handlers. Then the layout comes from data, and only the behaviour behind each control is code. Even a game built on an engine usually has a custom interface on top of it. ## Make an inventory Before porting any screen, list them all. For each screen, write down: | Field | Example | |---|---| | Name and how to reach it | "Ship upgrade shop: from the level-end menu, item 3" | | The code that draws and runs it | the function's address, or the layout resource's name | | Its states | normal, item selected, not enough money, confirming | | Its inputs | keys and clicks it reads, and where it reads them | | What it changes | the variables and commands it produces | | Text and fonts | the string table entries, the font resource | | Captures | the screenshots and state dumps of each state | Find screens three ways: play the game and capture every screen you reach; follow the code from the main menu's loop to every screen it calls; and list the data (layout files, string tables, fonts, cursor images). Each finds screens the other two miss (debug menus, error messages, end screens). ## Capture as much as you can Run the original where you can capture its frames ([Running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md)), and for each screen and state in the inventory: 1. **A screenshot** at the game's own resolution, lossless where possible. 2. **The state that produced it**: the variables the screen reads (from a memory dump or a save), so the port can be put in the same state. 3. **The input that reached it**: the keys or clicks, so the capture can be made again. 4. **Timing**: how many frames a fade, a scroll or a blinking cursor takes, from a frame-by-frame capture. Script the captures, so you can repeat them after a game update or for another language. For a game that draws its own interface, a capture must hold: - **Every surface the screen is drawn on.** The HUD and messages are often drawn straight to the display, not into the off-screen frame the game builds the picture in. Save both. - **Text as pixels.** The game's own font tables, often with shadow or darkened variants: check text pixel by pixel, like sprites. - **What was on screen before.** Status bars often redraw an item only when its value changes, comparing with a "last value" variable. A test that starts in the middle of a run needs the display contents from before, and those variables. - **Off-screen buffers.** Tile maps and effect buffers the frame draws from or into live in memory blocks the program allocated. Save each block with the pointer or selector that names it; for the state after the frame, keep only the blocks that changed. ## Rebuild it - **Data-driven interfaces:** write a reader for the layout format, and an engine that builds the same tree with the same names. Tests and tools then find controls by those names ([A model to query](https://tanepiper.github.io/game-frame/observe/object-model.md)). - **Interfaces in code:** port each screen's drawing and input functions like any other game logic, with the font and shapes read from the game's data. - **Either way:** the interface reads the game's state and produces commands. Keep it out of the rules. Then a test can drive the rules without the interface. The interface can be checked against captures on its own. ## Check it - Put the port in the captured state, draw the screen, and compare with the screenshot: pixel for pixel when both are exact, with an image difference and a threshold when the capture comes from a compressed stream. - Press the same inputs and compare the commands the screen produces. - Check text in every language the game ships: line breaks and widths change with the text. Each project decides how exact the interface must be ([Who drives](https://tanepiper.github.io/game-frame/games/interaction.md#the-four-kinds)). A 1:1 project must match every pixel of every frame of it. --- # A model to query: the game's object model and its entry points A web page has a document object model: a tree you can query ("the button called Save"), read ("its text") and act on ("click it"), from scripts, tests and tools. A game has no such model. Build one for the port, so tests, scripts and agents can query and drive it. If you build it for the original as well, you can ask both programs the same question and compare the answers. ## What the model holds | Part | Examples | In the original | In the port | |---|---|---|---| | **The interface tree** | screens, windows, buttons, lists, text, with names and rectangles | layout resources (for data-driven interfaces), or a list of screens and hotspots you write from the inventory ([Capturing the interface](https://tanepiper.github.io/game-frame/observe/ui.md)) | the port's own interface tree, with the original's names | | **The world** | entities, units, people, buildings, items, tiles, with their fields | named variables and structures in memory, from labels or debug symbols, read from a dump or a save | the port's state, with the same names and units | | **The session** | the current screen or loop, pause, speed, the frame or step, the seed | variables in the main loop and the loop object | the same fields | Use the **original's names** for everything: the names from its debug symbols, exports, resources and string tables, or your labels where it has none. Then one query finds the same thing in both programs. ## The entry points An entry point is a narrow, documented way into the running game. Every tool, test and recording goes through an entry point. No tool reads or writes the game's internals any other way. | Entry point | What it does | Examples from our ports | |---|---|---| | **Query** | Read the model: find elements, read fields, list entities | Un-Banished's probe (`ui`, `town`, `citizens`, `buildings`); Tyrian's `Globals`, every variable of the data segment by name | | **Input** | Act as the player would: press, click, type, choose | the probe's `click`, `type`, `combo`, which go through the same path as a real mouse and keyboard | | **Commands** | Apply a rules-level command at a step | Un-Banished's player commands, which multiplayer and recordings share | | **Time** | Run, pause, step one frame or tick, run *n* steps | headless runs for *n* frames; frame-by-frame fragments in Tyrian's checks | | **State in and out** | Load and save a full state, or one part of it | saves; Tyrian's `Globals::load` and `store` of a 64 KB data segment image | | **Trace** | Report random draws, calls, events, as they happen | random-draw traces compared with `seqdiff.py` | | **Capture** | Frames, sound, the interface tree as data | headless frame capture; OPL register logs | The same entry points in the original are built from GameFrame's tools: memory reads in the emulator or the debugger for **query** and **state**, scripted key presses for **input**, breakpoints for **trace**, video memory for **capture**. ## Rules for the entry points - **Entry points only read, or act as the player would.** A test that writes a field directly can put the game in a state that no player can reach. Inputs and commands go through the paths a player's input takes. - **One query language for every caller.** A test, a script in the browser's console, an agent and a headless run all send the same queries. The answers come back as structured data (JSON). - **Stable names.** Find things by the original's names or by their text. Then tests keep working when the layout or the timing changes. - **Keep the entry points outside the rules.** The model and its entry points are an addition the original does not have: put them behind a build option. Check that the game plays identically without them (see the `re-port` skill, "Additions the original does not have"). ## How to build it 1. **Name the original's state.** Labels from debug symbols (`tdinfo.py` for Borland), exports, RTTI and your own `labels/` files. Generate types for the original's records where you can, so the port and the tools share one definition. 2. **Read the original's state.** A reader that turns a memory dump or a save into the model. This gives you queries against the original before the port exists. 3. **Give the port the same model.** The port's state, exposed with the same names. 4. **Add the entry points one at a time**, as the work needs them: query first, then input, then time, then trace. 5. **Compare by query.** Put both programs in the same state, apply the same inputs, run the same queries and compare the answers. You can then name each difference, such as "citizen 12, field `hunger`". ## Examples | Port | Model | Entry points | |---|---|---| | Un-Banished | the interface tree (from the original's dialog resources) and the town | the probe: in the browser console (`unbanished.click('text:New')`), in headless runs (`--probe`), in test scenarios; player commands; saves; frame capture | | Tyrian 2000 | every data-segment variable, generated from the debug symbols, at its original offset | `Globals` load and store against memory images; per-frame fragments run from captured states; demo input per frame | | Frontier: Elite II | the original's own memory, in the port's built-in 386 runtime | native routines hooked at the original's addresses and compared with the original's results as the game runs | The [case studies](https://tanepiper.github.io/game-frame/case-studies/index.md) describe the tooling each port built. --- # Working on a project These pages describe how to organise the work of reverse engineering a game, for agents and the people who direct them. The skills describe each step of the method. These pages cover the work that spans several steps and several sessions. - [Working well as an agent](https://tanepiper.github.io/game-frame/working/agents.md): writing everything down in the repository, how to start a session, measuring before porting, working in subsystems, splitting work between sessions, what to do when stuck, and how to report. - [Linux, cloud and container environments](https://tanepiper.github.io/game-frame/working/environments.md): what an agent installs on Linux or in a cloud workspace, `scripts/env.sh` for each session, headless runs, REA, and game files on someone else's machine. - [Writing findings and documents](https://tanepiper.github.io/game-frame/working/writing.md): plain English, the patterns to avoid, evidence labels, the structure of a findings document, commit messages. --- # Working well as an agent This page is for AI coding agents doing reverse engineering with GameFrame, and for the people who direct them. It lists habits that save time and mistakes to avoid. The skills describe each step. This page covers work that spans several steps or several sessions. ## Ask your agent Start a working session. Copy this prompt into your coding agent: ```text In my GameFrame project, start a working session on . 1. Read AGENTS.md and docs/CHECKLIST.md. 2. Search docs/ and labels/ for the addresses, names and resources this task touches. 3. Tell me in one sentence what done means and how it will be verified. 4. Load the skill for the step this task needs, and follow it. 5. Write each finding into docs/ with its evidence label as soon as it is confirmed. 6. At the end, report the commit, what was verified with numbers, what was not verified, and the next open item. ``` ## The user decides The agent does not judge the user's intentions. It does not decide which law applies to the user or what that law means. If the user wants to do something, help them do it. Advice is welcome. When you know something that bears on the choice, such as a law, a risk or a project default, say it once, briefly, with its source, and link the [law pages](https://tanepiper.github.io/game-frame/preservation/law/index.md) where they help. Then follow the user's decision. Do not repeat the advice. Do not ask the user to justify their choice. ## Write everything down in the repository An agent forgets everything when its session ends or its context is compacted. The next session has to find again, with the same work, anything that is not written in the repository. - Write each finding into `docs/` as soon as it is confirmed, with its evidence label. - Add every function you identify to `labels/names.tsv`, and every field to `labels/fields.tsv`. The tools read these files, so later searches show the names. - Keep `docs/CHECKLIST.md` current: what is traced, measured, ported and verified, and what is next. A new session reads it first. - Write findings in your own words with addresses and constants. By default, do not paste decompiled code: the repository could then not be published. A reader also learns less from pasted code than from a description ([Writing](https://tanepiper.github.io/game-frame/working/writing.md)). ## Start every session the same way 1. Read the project's `AGENTS.md` and `docs/CHECKLIST.md`. 2. Search `docs/` and `labels/` for the addresses, names and resources you are about to work on. An earlier session may have traced them. 3. State the task in one sentence, with what "done" means and how it will be verified. 4. Load the skill for the step you are on (`re-trace`, `re-emulate`, `re-port` and so on). ## Read the export as text The Ghidra export (`decomp.c`, `functions.tsv`, `strings.txt`) is a set of text files. Agents search text well, so use it before opening Ghidra. - `decomp_query.py fn ADDRESS` shows a function with your labels applied. - `decomp_query.py callers` and `callees` walk the call graph. - `decomp_query.py find TEXT` and `strings` find where a string or constant is used. - `decomp_query.py writers OFFSET` finds every function that writes a field. ![decomp_query.py showing the sample game's simulate function from the Ghidra export. The pseudocode seeds the random number generator, allocates an object with a vtable, and calls through the vtable in a loop.](https://github.com/tanepiper/game-frame/blob/main/docs/assets/captures/decomp-query.svg) - Run one-off decompiles or vtable exports with `scripts/ghidra-run.sh` when the export is not enough. Re-export after labelling many functions, so the next sessions read the names. ## Measure before you port Pseudocode shows roughly what a function does. A port written from it often gets the last bit of a result wrong. For any function with arithmetic, random draws or many branches: 1. Run the original function under the emulator on many inputs and save the results (`re-emulate`). 2. Port it. 3. Compare bit for bit in a test. This order is faster than porting first. A test against measured results shows the mistake in seconds. A difference that shows up later in a whole game run can take hours to trace back. ## Work in subsystems - Choose a subsystem (the player's movement, a menu, the level loader) and take it from tracing to verified in one piece of work. - Verify by risk. Fuzz maths, random draws and code with many branches. Check simple code against recorded frames only. - Commit and push each verified subsystem. Many unverified changes in a row are hard to debug. Work that is not pushed can be lost. - When you find a side need (a format reader, a missing tool), do only what the current subsystem needs, and add the rest to `docs/CHECKLIST.md`. ## Split work between sessions Several agent sessions can work on one project at the same time. | Kind of work | Where it goes | |---|---| | Hard reasoning: maths, timing, the main loop, exactness bugs | The main session, with the most capable model | | Long mechanical work: generated types, text tables, menus, moving code without changing it | A separate session with an exact brief, possibly a faster and cheaper model | | Independent subsystems | One session each, in separate git worktrees, each touching its own files | | Questions that only need reading | A read-only session in the same checkout | Rules for parallel sessions: - Each session stages only its own files (`git add path`). Never `git add -A`. - Two sessions never edit the same file at the same time. When they must, one waits, or one works in a worktree and rebases. - A session that changes shared documents tells the others which sections it changed. - A brief for another session names the files, the addresses, what is already known and what "done" means. The other session does not share your context. ## Run long work in the background - Ghidra exports, long fuzz runs, full test tiers and builds can take minutes. Start them in the background with a time limit. Do other work meanwhile. - Give every long run a timeout. A hung emulator or a game waiting for input must not block the session. - Stop servers, emulators and game processes you started before you finish. ## When you are stuck | Symptom | What to try | |---|---| | The port differs from the original after many frames | Compare state every frame and stop at the first difference; compare random draws with `seqdiff.py` | | A value is right in some runs and wrong in others | Something outside the input record changes it: a clock, an uninitialised variable, iteration over a hash map | | The decompiler's output makes no sense | Read the disassembly; check the calling convention, the segment or bank, and whether the code is data | | A function seems to do nothing | Check for self-modifying code, a jump table, or a function reached through a vtable or a callback | | You cannot find where something happens | Watch the memory in an emulator and break on the write; then follow the writer | | You have tried the same idea three times | Stop, write down what you know and what you tried in the checklist, and ask the person directing the work | ## Report what you did At the end of a piece of work, report: - what changed, with the commit; - what was verified, with the test names and the numbers that passed; - what was not verified, and why; - the next open item. Say "verified" only for checks you ran and looked at. A successful build does not count as a verification. ## Ask before you act outside the repository Ask the person directing the work before you download files, install large tools, publish anything, contact an online service, or change anything outside the project. Never upload the game's files or anything derived from them. --- # Linux, cloud and container environments GameFrame runs on macOS and Linux, on a desktop or headless. This page lists what an agent needs to set up a Linux machine, a container or a cloud agent's workspace, and what to check before work starts. ## Ask your agent Set up a Linux environment. Copy this prompt into your coding agent: ```text Set up GameFrame in this Linux environment. 1. Clone https://github.com/tanepiper/game-frame (or update it) and read docs/working/environments.md and the re-setup skill. 2. Run scripts/setup.sh and show me the table. Tell me what is missing and how large each part is. Wait for my answer before installing anything. 3. Install what I agree to. Verify Ghidra's download against the SHA-256 on its release page. 4. If DOSBox-X is older than 2026.10, tell me, and build a current one with the debugger only if I agree. 5. Run tests/run.sh --ghidra and report the count. Fix the setup until every check passes or is skipped for a reason I accept. 6. Write a short report: the versions installed, the test result, and anything left to do. Keep logs outside the repository. Never put game files, extracted assets or decompiled code inside a git checkout. ``` ## What the toolkit needs | Tool | Version that works | Needed for | |---|---|---| | Python | 3.10 to 3.13, with `.venv` from `requirements.txt` (Unicorn 2.1, pefile, Capstone 5) | every tool | | JDK | 21, the full JDK (with `javac`) | Ghidra | | Ghidra | 11 or 12 (12.1.4 tested) | decompiling | | objdump | GNU binutils or LLVM | reading instructions exactly | | Node.js | 18 or later (24 tested) | REA | | Zig | 0.14 | building the sample game in the tests | | DOSBox-X | 2026.10 or later, with the heavy debugger | DOS games, the debugger tests | | innoextract, 7-Zip | any recent | unpacking installers | | REA | 6.4.0 | the MCP server for agents (optional) | `scripts/setup.sh` checks each one and prints a table. `--install` installs with apt on Debian and Ubuntu. Ghidra is not in apt: download the release zip, check its SHA-256 against the release page, unzip it and set `GHIDRA_INSTALL_DIR`. ## Steps on a Debian or Ubuntu machine 1. Install the system packages: `python3-venv`, `openjdk-21-jdk`, `binutils`, `nodejs`, `innoextract` and `p7zip-full` (or let `scripts/setup.sh --install` do it). 2. Install Ghidra from its release zip, as above. 3. Run `scripts/setup.sh --install` to make `.venv` and write `.env` with the tool paths. 4. Check the DOSBox-X row. Distribution packages are often older than 2026.10. Build a current one with the debugger as the re-setup skill describes, or leave DOS games out. 5. Run `tests/run.sh --ghidra`. Every check should pass, or skip with a reason. ## Each session Agents often start a new shell for each command. Source `scripts/env.sh` at the start of each one: ```bash source ~/code/game-frame/scripts/env.sh ``` It sets `GAMEFRAME_ROOT`, loads `.env`, exports `GHIDRA_INSTALL_DIR` and `JAVA_HOME`, and puts `gameframe` and the `.venv` Python first on `PATH`. The scripts load `.env` themselves, so this matters for commands typed by hand and for agents. ## No display The tests and the tools need no display. `dosbox-run.sh` uses SDL's dummy video and audio drivers unless you pass `--window`. A DOSBox-X built without X11 or OpenGL support runs the tests. Ghidra runs headless. ## REA in a cloud or container session - MCP servers start with an agent session. After you register REA, start a new session to get its tools. - REA's doctor may warn about a host it does not list, such as Debian 13. Test it on the sample game: open `examples/sample-game/build/game.exe` and analyse a function. - Ghidra analysis can take minutes. Raise the client's tool timeout for REA. ## Game material on someone else's machine A cloud machine belongs to its provider. Before you put a game there, check that your licence and the provider's terms allow it. - Keep the game's files, extracted assets, decompiled code and analysis output outside every git checkout. - `scripts/new-project.sh DIR "Name"` without a game path creates the project and copies nothing. Put the game's files in the project's ignored `target/` folder only where you are allowed to. - Each cloud task usually runs in its own copy, so a git worktree is rarely needed. ## Keep the environment repeatable - Save the versions you installed, and the output of `scripts/setup.sh` and `tests/run.sh --ghidra`, outside the repository. - Write a small refresh script for your environment that pulls GameFrame, reruns `scripts/setup.sh --install` and registers REA again. - Report changes you had to make to GameFrame's own files upstream, so the next environment does not need them. --- # Writing findings and documents Everything in GameFrame is written to be read by agents and people, and to be translated. This page sets the style for findings, documents, skills, commit messages and posts. Follow it in every project made with GameFrame. ## Plain English - Write short sentences with one idea each. - Use common words. Prefer "use" to "utilise", "start" to "commence", "find" to "ascertain". - Name the subject of each sentence. Write "the loader reads the header", and avoid "the header is then read". - Give the evidence in the same sentence as the claim: an address, an offset, a value, a file. - Write numbers with their units and their base: `0x44`, 70 Hz, 16.16 fixed point. - Before you finish a paragraph, imagine it translated into another language word by word. If the translation would confuse a reader, rewrite the English. ## Patterns to avoid These patterns make text sound written by a machine. They also hide the facts. Search for each one before you finish. Rewrite what you find. | Pattern | Example | Rewrite | |---|---|---| | A summary that reveals its content after a colon | "Two rules hold it together: every claim rests on evidence, and every number is measured." | "Give the evidence for every claim. Measure every number the port must match." | | A closing contrast "X, not Y" | "Numbers are measured, not guessed." | "Numbers are measured by running the original code." | | Matched pairs and triplets for rhythm | "the same loop and the same discipline"; "bit for bit, draw for draw, frame for frame" | Say the one thing that is true: "the same steps"; "bit for bit" | | Two clauses of the same shape joined by ", and" | "A takeover game must be traced at its register writes, and its port must rebuild what the chips did." | Two sentences: "Trace a takeover game at its register writes. The port must rebuild what the chips did." | | Clauses chained with "and the ..." | "It reads the file and the header and the table." | Separate sentences, or a list | | Signposts | "Here is how it works." "Three things stand out." | Start with the first fact | | Metaphors and figures of speech | "the beating heart of the engine", "a door that opens" | The literal statement: "the main loop", "a function the mod can call" | | Dramatic openings | "Nobody knew why the save broke." | "The save broke because ..." | | Hedging filler | "It is worth noting that", "Essentially", "In order to" | Delete the words | | Abstract statements | "In each of these cases the game that players bought survives longest." | "When a studio loses its source code, the copies players bought are often all that is left." | A contrast is fine when it prevents a real mistake. "Keep the constant as `0.33333331f`, as read from the binary. Do not write `1.0 / 3.0`." That warning names the wrong thing the reader would otherwise do. A colon is fine when it introduces a list or an example. "The header has three fields: the magic, the version and the entry count." ## Evidence labels Every claim carries one of these labels, in the text or in a table column. | Label | Meaning | Example | |---|---|---| | *observed* | Read in the code or the data | "`FUN_1400be680` multiplies by 0.75 (*observed*)" | | *measured* | Produced by running the original, with the inputs named | "3,000 names from seed 1 match (*measured*)" | | *inferred* | A reasoned guess; say what would confirm it | "`+0x44` is probably the age (*inferred*: it rises once a year)" | | *unknown* | An open question | Listed under **Open questions** | Never write that the game "does" something at run time from a static reading alone. ## Structure of a findings document 1. An opening paragraph: what the document covers and what kind of evidence it rests on. 2. Tables keyed by address, offset or file, with a column for the evidence. 3. Prose for the order of events and the reasons, one step per sentence. 4. **Open questions** at the end. Never paste decompiled code, disassembly or the game's data into a document. Describe them in your own words, with addresses and constants ([Legal](https://tanepiper.github.io/game-frame/legal.md)). ## Commit messages - The subject says what changed, in the imperative, at most 72 characters. - The body says what was traced or ported, from which functions, and how it was verified. - The same style rules apply. A commit message is read by the next session. --- # Patterns you will meet in game code Most games are built from the same few parts. When you recognise a part, you know what to look for next and which details an exact reimplementation must copy. Start with the kind of game. Who drives (the game or the player), the families of games and their rule sets, and the systems they share are in [the taxonomy](https://tanepiper.github.io/game-frame/games/index.md) and [Who drives](https://tanepiper.github.io/game-frame/games/interaction.md). This page describes the parts themselves. To capture them from the running original, see [Observing the original](https://tanepiper.github.io/game-frame/observe/index.md). ## The main loop `WinMain` → engine set-up → a loop that, each frame, reads input, updates the game, draws, and presents. Find it from the entry point, or from `PeekMessage`/`GetMessage` (the Windows message pump sits inside it) and the present call (`IDirect3DDevice9::Present`, `IDXGISwapChain::Present`, `SwapBuffers`). - **Fixed step:** the update runs at a constant rate (often 30 or 60 per second) and an accumulator decides how many steps a frame runs. Game speed settings usually multiply the number of steps, not the step size. Reimplement the accumulator exactly, because the number of steps per frame changes the game's results. - **Variable step:** the update takes the frame time (`dt`). Exact replay is then impossible without recording `dt`; tests feed a fixed `dt`. - **Loop objects:** engines often have a stack or state machine of loops (menu, loading, game), each with load, start, update, draw, input and stop methods in a vtable. The order of systems inside one update changes the results, because a later system sees the changes an earlier system made in the same step. Record the order early, in a table, from the update function's callees. ## Random number generators Random number generators are usually small and self-contained. Emulate them first. | Kind | How to recognise it | |---|---| | LCG (MSVC `rand`) | `state = state * 214013 + 2531011; return (state >> 16) & 0x7fff` | | LCG (glibc, Java, numerical recipes) | multipliers 1103515245, 0x5DEECE66D, 1664525 | | xorshift | `x ^= x << 13; x ^= x >> 17; x ^= x << 5` and variants | | Mersenne Twister | a 624-word state, the constant 0x9908B0DF, tempering masks 0x9D2C5680, 0xEFC60000 | | WELL, PCG, SplitMix | constants such as 0x5851F42D4C957F2D (PCG), 0x9E3779B97F4A7C15 (SplitMix) | Games often have several generators (one for the simulation, one for effects or sound) and may seed them from the clock. Find every instance and who draws from each. A draw from the wrong generator changes the results as much as a draw out of order. Helpers built on the generator (a float in [0,1), a range, a "tolerance" around a value) each have their own rounding; emulate them too. ## Entities and components - **Class hierarchies:** `Entity` → `Unit` → `Soldier`, with behaviour in virtual methods. Recover the hierarchy from vtables (shared early slots) and RTTI. - **Entity-component systems:** an entity is an id or a small object holding a list of components; each component type has a type id (often a hash of its name) and a vtable with lifecycle slots (create, attach, update, serialize, destroy). Systems iterate components of one type in a fixed order. Keep that order in the reimplementation. - **Handles:** an index plus a generation counter, packed into 32 or 64 bits. ## Resources and data - **Packages:** an archive (header, entry table, data), possibly compressed per entry. Entry names may be stored as hashes; the hash function is in the lookup code. - **Type ids by hash:** a CRC32 or FNV-1a of the class name, sometimes of the name in UTF-16. Compute candidates from strings in the binary and match them against the constants in the registration table. - **Serializers:** a `Serialize(Stream&)` or `Load(Reader&)` per class, base class first, with version checks. The same function often both reads and writes, depending on a flag in the stream. - **Scripts and tables:** rules may live in data (XML, JSON, Lua, custom binary tables) rather than code. Read the data before reimplementing a rule by hand. ## Time Games keep time as integer ticks (milliseconds or microseconds) or as float seconds, and the calendar of a simulation (days, seasons, years) is derived from it. Float time gathers rounding error. If the original keeps time as a float, the reimplementation must do the same. Use the game's own calendar and its own names. A game's year may have twelve "months" that are seasons (Early Spring to Late Winter), or eight, or none; its tenth month is not October. Read the names from the game's string tables. Use them in docs, tests and examples. ## Messages and text Event logs, notifications and tooltips usually store a message type and its arguments (a person, a building, a number), and format the text from a string table when it is shown. Saves may keep only the formatted text. A reimplementation that keeps the arguments formats the text the same way; one that loads a save shows the stored text as it is. Text in the player's language comes from data, so never hard-code an English string where the original reads a table. ## Saves A save is the serialized world: often the same serializers as the data files, plus the random generator states, the clock, and object references written as indices or ids. Pointers between objects need a fix-up pass on load. Loading a real save is the best test of a reimplementation, because a save holds the state of every system. ## Rendering and UI - Shaders ship compiled (DXBC for Direct3D 9-11, DXIL for 12, SPIR-V for Vulkan): disassemble them (fxc's `/dumpbin`, `dxc -dumpbin`, `spirv-dis`) to read the maths. Check constant buffer layouts against the CPU code that fills them. - UI is often custom code in older games and data-driven in newer ones (layouts in data files, a widget tree, events by id), with screens as loop objects. See [Capturing the interface](https://tanepiper.github.io/game-frame/observe/ui.md). - Frame capture tools (RenderDoc, PIX, apitrace) show the draw calls, their states and the constant values of a real frame. A capture is the fastest way to understand a rendering pass. ## Networking (if any) Messages are usually serialized with the same stream classes as saves. Lockstep games send only inputs and depend on exact determinism; client-server games send state. Never test against a live service without its owner's permission. If you add lockstep multiplayer to a single-player game, look for state that belongs to one player but is read by the simulation: settings, unlocked achievements, difficulty options, the local clock, the window size. Two players with different values then compute different game states. Give every player the same values for a shared game (for example, count achievements from none in multiplayer). Check with digests that two players stay identical. --- # DOS and 16-bit games Most of GameFrame was built on a 64-bit Windows game. DOS games (1985-2000) need different tools at three steps: reading the executable, emulating code, and running the original. This page covers every DOS step. The skills link here. The tools are tested against `examples/dos-sample/` (our own 16-bit code) by `tests/run.sh`. ## 1. Which kind of DOS program is it? ```bash python3 tools/dos/dosinfo.py target/GAME.EXE # format, segments, imports, fingerprints python3 tools/dos/dosinfo.py target/GAME.EXE --pstrings 6 # Pascal strings, with their addresses ``` | dosinfo says | What it is | Ghidra | Emulate one function | Run it all | |---|---|---|---|---| | `MZ` | Real-mode DOS (Turbo Pascal, Turbo C, Microsoft C, QuickBASIC) | "Old-style DOS Executable", loaded at segment `1000` | `dos_emu` (real mode) | DOSBox-X | | `NE` | 16-bit protected mode: Windows 3.x, OS/2 1.x, Borland Pascal 7 or Borland C++ with DPMI16 (`RTM.EXE`, `DPMI16BI.OVL`) | "New Executable", selectors `1000`, `1008`, ... | `dos_emu` (protected mode, same selectors) | DOSBox-X (or Windows 3.1 inside it) | | `LE` / `LX` | 32-bit flat code behind a DOS extender (Watcom + DOS/4GW, PMODE/W) | needs an LE/LX loader extension | not yet in the toolkit | DOSBox-X | | `PE` | A Windows program | use `tools/pe/peinfo.py` | `pe_emu.py` | Windows or Wine | Packed executables (LZEXE, PKLITE, EXEPACK, DIET: dosinfo's fingerprints) must be unpacked first, for example with UNP or by dumping memory from DOSBox-X after the unpacker has run. Data appended after the MZ load image is an overlay (Borland `.OVR` data starts with `FBOV`), a packed payload, or debug symbols. ## 2. Addresses: always Ghidra's numbering Write every address as `seg:off` the way Ghidra prints it, in docs, labels and tests: - **NE**: segment N of the file has selector `0x1000 + 8 * (N - 1)`. Segment 1 is `1000:`, segment 10 is `1048:`. Imports go after the last segment. - **MZ**: the load image starts at segment `1000`, so `1000:0000` is the first byte after the header; a second segment in the image is `1000 + paragraphs`. `dosinfo.py` prints Ghidra's selector for each segment. `readseg.py`, `dos_emu`, `tdinfo.py` and `decomp_query.py` all accept and print these addresses. The *running* program uses other numbers: real-mode segments where DOS loaded it, or the DPMI host's selectors. Only the DOSBox-X debugger shows those (section 7). ## 3. Debug symbols: check before anything else Borland compilers wrote Turbo Debugger symbols into the program (TPC/BPC `/V`, TLINK `/v`), and many shipped games kept them. They give the original names of every procedure and global, the parameters and locals of each procedure, record layouts, and code-to-source-line maps. ```bash python3 tools/dos/tdinfo.py target/GAME.EXE summary # units, counts python3 tools/dos/tdinfo.py target/GAME.EXE procs makeenemy # address, size, parameters, locals python3 tools/dos/tdinfo.py target/GAME.EXE types enemy # record fields with offsets python3 tools/dos/tdinfo.py target/GAME.EXE labels > analysis/td-labels.tsv scripts/ghidra-export.sh --labels analysis/td-labels.tsv target/GAME.EXE ``` With `--labels`, Ghidra names every function and global, and creates the functions it missed. `tdinfo.py` turns the record types into reimplementation code. `tdinfo.py BINARY rust TYPE ...` writes Rust structs with readers in the original's field order, each field commented with its Pascal type and offset, and `tdinfo.py BINARY rust-reader` the `PascalReader` they use (little-endian integers, one-byte Boolean, 6-byte Real kept exact, `string[N]`). Generated names that clash with Rust's prelude get `Rec` (`Optiontype` -> `OptionRec`). Records also get `write`, the inverse of `read`. `tdinfo.py BINARY rust-globals` writes one `Globals` struct holding every variable of the data segment at its original offset, with `load` and `store` to and from a 64 KB image of the segment (Booleans stay `u8`, so `store` writes every byte back unchanged). Ported code then works on `Globals`. A test can load the DS image from before an emulated run of the original, run the port, store, and compare with the DS image from after. One test then checks every variable. `tdinfo.py BINARY rust-locals PROC` does the same for one procedure's local variables and parameters, at their BP offsets in the stack segment: for code nested in a big procedure (a main loop's helpers that use its locals through the static link). The runtime library (`System`, `Crt`, `Dos`) has no symbols of its own. Borland Pascal 7's random generator, for example, is `RandSeed := RandSeed * $08088405 + 1` (a near `NextRand` built from 16-bit multiplies and shifts), `Random(n)` = (new seed * n) >> 32 using all 32 bits, and `Randomize` seeds from INT 21h AH=2Ch (CX low word, DX high word). Find it by the constant `0x8405` and the global `RandSeed`, and measure it before trusting this. Record field offsets are summed from field sizes: tdinfo says when a record's fields do not add up to its size (a variant record), so check one offset in the code before relying on it. ## 4. Decompiling 16-bit code with Ghidra `scripts/ghidra-export.sh` works for MZ and NE as it does for PE. Things that differ: - **Library search crashes the import.** An NE program that imports a module (`RTM`) whose file sits next to it (`RTM.EXE`, a plain DOS program) made Ghidra's loader throw "Invalid index". The export script passes `-loader-libraryLoadDepth 0`, so Ghidra never searches. - **No-return calls.** Ghidra does not know that `INT 21h` with `AH=4Ch` (exit) or Pascal's `Halt`/`RunError` never return: the next procedure's code is joined to the end of the caller. Split it by creating the function at the right address (labels do this). - **Missed functions.** Procedures reached only through relocations or tables are not found. Debug-symbol labels fix this; otherwise use `scripts/ghidra-run.sh BINARY decompile`. - **Pascal calling convention.** Arguments are pushed left to right and the callee removes them (`retf N`). Ghidra shows these as `__stdcall16far` with `param_1` being the *last* Pascal argument. Read parameters from `[bp+6]` upwards: the first at the highest offset. - **Parameter numbers drift.** Ghidra may split one far-pointer parameter into two `param_N` halves, so every later number is off by one; `in_stack_X` and `local_X` names are offsets from a frame base two bytes away from BP. Take each parameter's BP offset from the disassembly, and name it from the caller's push list (`mov di, 0x649e; push ds; push di` is the global at 0x649e), not from how the pseudocode uses it: a "change" counter may really be a speed. - **A big procedure's helpers see its frame.** A nested procedure reads its parent's locals and `var` parameters through the static link (`mov di, [bp+4]`, then `[di+...]`); map those offsets to the parent's names before reading the helper. - **Scans with string instructions.** `repe scasb` over an array finds the next element that differs from AL; the loop then resumes the scan with the saved CX and DI. Port it as a loop over the array. Check how it stops (often `or cl, cl` on the low byte only). - **Which carry counts.** In `add dx, bx; add dx, 1180h; add dx, ax; jae ok`, only the last add sets the carry the jump tests; the sum wraps freely before it. Port the test on the same instruction, not on the whole sum. - **Stale globals are inputs.** A routine may test a temporary that another routine set earlier in the frame (a `tempi2` left by the movement code). Keep the read, because the original's result depends on it. A test catches it only if the port leaves the global as the original does. - **Strings.** Pascal strings (a length byte, then the text) are not found by Ghidra's string search. Use `dosinfo.py --pstrings` and `readseg.py BINARY pstr ADDR`. - **Segment arithmetic.** `DAT_1048_12de` is offset `0x12de` in the data segment; a far pointer is offset then segment in memory (`readseg.py ... ptr`). - **String constants sit just before their procedure.** Borland Pascal emits a procedure's string literals right before its first instruction, so the procedure that starts after a string usually uses it. Code refers to a Pascal string by its *length byte*: search for the address one before the text that `dosinfo.py --pstrings` prints. - **Constants that look like globals.** A 16-bit constant equal to an offset in DS shows up in pseudocode as `&someGlobal` (a `BlockRead` count of 0x138a printed as `&tempdat3`). Check the instruction (`tools/dos/nedis.py`) before believing a pointer. - **Floating point by interrupt.** Borland's FPU emulator replaces each x87 instruction with `INT 34h`-`3Dh` followed by the instruction's bytes; disassemblers show `int 0x3c` and nonsense after it. Read those runs as floating-point code (Real, Double), not as interrupts. - **Calls through placeholders.** Disassembling the file directly shows every far call as `lcall 0:ffff`: the relocation fills it in. `tools/dos/nedis.py BINARY SEG:OFF LEN` applies the relocations and prints label names for call targets and DS globals. - **Text files are buffered in the record.** `ReadChar` takes the next byte of the 128-byte buffer inside the `TextRec` (`BufPos`, `BufEnd`), and refills it with one DOS read of `BufSize` bytes when it is empty. That buffer sits in the data segment, so a port that reads the file another way leaves different bytes there: port the runtime's routines over the record, with a table of open DOS handles and their positions. - **Text files end at ^Z.** On a Turbo Pascal `Text` file, `Eof` is true when the next byte is 0x1A, not only at the real end, and `Read` at the end returns 0x1A. A game that stores binary data in a Text file (demo recordings, scripts) stops early at a 0x1A byte in the right place: model it, and check which positions `Eof` can see. - **Finding the game clock.** Look for writes to ports 43h/40h (the PIT: `mov al, 36h; out 43h, al`, then the divisor's two bytes to 40h) and for `SetIntVec(8, ...)`: the new IRQ 0 handler and the divisor give the tick rate (1,193,182 / divisor Hz), and the loop that waits for a counter the handler decrements gives ticks per frame. A handler that also calls the old vector when a running sum carries keeps the BIOS clock at 18.2 Hz. - **Turbo Pascal types**: `Real` is 6 bytes and not IEEE (`readseg.py ... real48`), `Comp` is a 64-bit integer kept in the FPU, `string[N]` takes N+1 bytes, sets are bit arrays of up to 32 bytes, `Boolean` is one byte. Records are not padded. - **Real arithmetic is in the runtime library, with registers.** Without `{$N+}`, `Real` maths calls the System unit's helpers: the first operand and the result in `DX:BX:AX`, the second in `CX:SI:DI` (the low byte of `AX` or `CX` is the exponent). A typical block is `call int-to-real; mov cx/si/di, constant; call multiply; call round`. Two helpers that differ only by `mov ch, 0` (Trunc) or `mov ch, 1` (Round) before the same near call tell Trunc from Round. Measured in one Borland Pascal 7 program: multiply rounds the 80-bit product to 40 bits, halves up; Round rounds halves away from zero. Do not use floats for this: emulate the helpers over every input the game can give (`emu.call(helper, regs={'ax': ..., 'dx': ...})`) and port the exact algorithm. ## 5. Emulating one function: dos_emu ```python import sys; sys.path.insert(0, 'tools/emu') from dos_emu import DosEmu, Long, FarPtr emu = DosEmu('target/GAME.EXE') # NE: protected mode with Ghidra's selectors emu.call('1000:873a', 5, Long(100000)) # Pascal order; returns AX (call_long: DX:AX) emu.u16(FarPtr(0x1048, 0x12de)) # read a global after the call emu.trace({'1040:0a12': 'Random'}, args=1) # record calls (the random draws) with call sites ``` - Relocations are applied: selector, far-pointer and offset fixups to the program's own segments, chained or additive. Imports become stubs. - DS and SS are the auto data segment, as in a Pascal program. Pass `regs={'ds': ...}` for code that expects something else, and other registers (`regs={'ax': ..., 'cx': ...}`) for runtime helpers that take their arguments in registers. - An import or interrupt without a handler stops the run with its name and the caller's address. Add `stubs={'KERNEL.3': fn}` or `interrupts={0x21: fn}`; a handler reads and sets registers (`emu.reg('ax')`, `emu.set(ax=...)`). An import handler returns the number of argument bytes to remove. - Not emulated: DOS itself, DPMI services, timers, VGA, sound, the x87 (Borland's FPU emulator fixups are left alone). Code that needs them runs in DOSBox-X instead. ## 5b. Running the whole program headless: dos_env `tools/emu/dos_env/` gives `dos_emu` a small DOS, BIOS, DPMI host and PC, so a whole Borland Pascal 7 DPMI16 program can start and run, with no DOSBox and no window: ```python from dos_emu import DosEmu, ProgramStopped from dos_env import DosEnv emu = DosEmu('target/game/GAME.EXE', max_steps=2_000_000_000) env = DosEnv(emu, root='target/game', overlay='derived/run', program='C:\\GAME\\GAME.EXE') emu.run_range(emu.entry, '1000:ca59') # start-up, until the main loop env.save_screen('derived/run/frame.png') # VGA memory + the DAC palette emu.run_to('1000:ca59') # the next frame: carry on as it is ``` - **Carry on with `run_to`, never `run_range`.** `run_range(start, end)` starts afresh: it puts SS:SP back at the start-up stack. If you use it to continue a running program, every call moves the stack above the live frames. Deep calls then overwrite the main procedure's locals and return address (seen as stray file names in a local array, and the program "exiting" after some thousands of frames). `run_to(end)` keeps every register. - Files come from `root` (names without case); writes go to `overlay`. `env.log` lists what the program asked for, in order: compare it with DOSBox-X's file log. - The timer: IRQ 0 reaches the program's INT 8 handler at the rate it programs on the PIT, counted in instructions (`ips`, default 25 million per second), so frame-paced loops run. - Keys: `env.keys` (scan << 8 | ascii). A read with none queued stops the run and says so. - Each stop names what is missing (an interrupt, a DPMI call, an import): add it to dos_env, with a test against the sample. Things it needed for a real game: the NE header's own stack segment, a PSP with an environment, the BIOS data area (CRT port, mode, columns), a BIOS tick that advances, a PIT counter that counts, Borland RTM's memory calls (RTM.5/6/21). - Then capture state frame by frame: `emu.read((0x1048, 0), 0x10000)` is the data segment for `Globals::load`; any segment a pointer names can be dumped the same way. `tools/emu/fragments.py` does this for chosen fragments of the main loop: the state before and after each one, every frame, ready for the port's tests (re-verify). ## 6. Running the original: DOSBox-X A store release usually ships its own DOSBox configuration (GOG: `__support/app/*.conf`). Start from it so runs use the publisher's settings: `scripts/dosbox-run.sh --conf THAT.conf ...` (the script turns fullscreen off and keeps its own logging and autoexec). `scripts/setup.sh --install` installs DOSBox-X (on macOS `brew install --formula dosbox-x`; the cask fails Gatekeeper). Homebrew's build has the heavy debugger (CPU logs, memory breakpoints); `brew install --HEAD --formula dosbox-x` adds the newest debugger commands (`STEPIN`, `SAVESTATE`). ```bash scripts/dosbox-run.sh target/game GAME.EXE # no window; prints the exit code scripts/dosbox-run.sh --fileio --time 20 target/game GAME.EXE python3 tools/dos/fileio_trace.py analysis/dosbox/dosbox.log --game target/game ``` - The game runs on a fresh copy of its folder (`analysis/dosbox/drive`), so saves and settings it writes never change your copy. `--in-place` mounts the folder itself. - `--fileio` logs every open, read, seek and write. `fileio_trace.py` turns that into a map per file: offsets and sizes in reading order, with repeated patterns collapsed (`[2, 1, 4] x 120 (stride 7)` is a table of 120 seven-byte records). When a program reads a file field by field, the read sizes give its record layout (*measured*). - A program that reads through a Pascal **text** file (Assign/Reset on a `Text` variable) is read in 128-byte blocks: the log shows the buffer, not the fields. Read the code instead. - `--cycles N` fixes the CPU speed. Use the same value for every run you compare. - Exit codes come back through `IF ERRORLEVEL` (DOSBox-X's shell does not expand `%ERRORLEVEL%`, and a redirect inside a false `IF` still creates the file). - Windowless runs use `-silent` with SDL's dummy drivers, so there is no window and no sound. ## 7. The debugger, from an agent or a script DOSBox-X can connect its debugger to an external controller (setting `mcp_server=PORT`, the `--mcp PORT` option of dosbox-run.sh). Two controllers: - **For agents**: `dosbox-x-mcp-server` (https://github.com/caiiiycuk/dosbox-x-mcp-server), registered in `.mcp.json` as `dosbox`. Install it with `cargo install --git https://github.com/caiiiycuk/dosbox-x-mcp-server --tag v0.1.2 --locked`. It listens on 58991; ask it with its `dosbox_mcp_port` tool, then start DOSBox-X with `--mcp 58991`. Tools: `debug_break`, `debug_exec` (any debugger command), `debug_snapshot`, `debug_run`. It has no licence file: use it, do not copy its code. - **For scripts and tests**: `tools/dos/dbx_control.py` speaks the same line protocol (`REQ id EXEC command` -> `BEGIN id OK`, output, `END id`). Use another port than the MCP server's. Useful debugger commands (type `HELP` for the build's own list): `BP seg:off` (breakpoint), `BPINT 21 3D` (break on an interrupt and AH), `BPM seg:off` (break when memory changes), `D seg:off` (dump memory), `MEMDUMPBIN seg:off len` (to a file), `SR reg value` (set a register), `LDT`/`GDT` (selectors), `LOGS`/`LOGL` (CPU trace to a file), `STEPIN`, `RUN`. **Finding a selector at run time.** A protected-mode program's selectors are chosen by the DPMI host. To map Ghidra's `1000` to the live one: break anywhere in the program, read `CS` (the segment being executed), list `LDT`, and dump the first bytes at each code selector; compare them with the file's segment bytes (`readseg.py BINARY hex 1000:0000 --count 2`). Bytes at relocations differ; everything else matches. --- # Calling conventions, data layout and floating point This page lists what you need to read a game's machine code correctly and to call its functions under the emulator. The tables list only the details games use. The authoritative sources are linked at the end. ## Calling conventions | Platform | Integer and pointer arguments | Float arguments | Return | `this` | Stack | |---|---|---|---|---|---| | Windows x64 (all 64-bit Windows games) | rcx, rdx, r8, r9, then stack | xmm0-xmm3 (by position: the 2nd argument is rdx *or* xmm1) | rax, xmm0 | rcx | caller reserves 32 bytes of shadow space; stack 16-byte aligned at the call | | System V x64 (Linux, macOS) | rdi, rsi, rdx, rcx, r8, r9 | xmm0-xmm7 (counted separately from integers) | rax (rdx:rax), xmm0 | rdi | 16-byte aligned; 128-byte red zone below rsp | | Windows x86 `__cdecl` | stack, right to left | stack (`float` as 4 bytes, `double` as 8) | eax (edx:eax), x87 st(0) for floats | n/a | caller cleans the stack | | Windows x86 `__stdcall` (WinAPI) | stack, right to left | stack | eax, st(0) | n/a | callee cleans (`ret N`) | | Windows x86 `__thiscall` (MSVC C++ methods) | stack | stack | eax, st(0) | ecx | callee cleans | | Windows x86 `__fastcall` | ecx, edx, then stack | stack | eax, st(0) | n/a | callee cleans | | ARM64 (consoles, Apple silicon, Android) | x0-x7 | v0-v7 | x0, v0 | x0 | 16-byte aligned | Structures larger than 8 bytes are usually passed by hidden pointer on Windows x64, and returned through a hidden pointer passed as the first argument (so `this` moves to rdx). Callee-saved (non-volatile) registers on Windows x64: rbx, rbp, rdi, rsi, r12-r15, xmm6-xmm15. If pseudocode seems to use a value from nowhere, it may be one of these, kept across a call. ## C++ layout (MSVC) - An object with virtual methods starts with a pointer to its vtable (`+0x00`). Multiple inheritance adds one vtable pointer per base with virtual methods. - A vtable is an array of function pointers; MSVC puts a pointer to the RTTI "complete object locator" just before it (at `vtable - 8` on x64), which leads to the class name (`.?AVCitizen@@`). - Virtual destructors take a flag argument (scalar deleting destructor): `FUN(this, 1)` deletes. - `std::string` (MSVC, x64): 32 bytes; up to 15 characters stored inline, otherwise a pointer. `std::vector`: three pointers (begin, end, capacity end). - Member functions are named `?Method@Class@Namespace@@...` when exported; any C++ demangler (`c++filt` understands Itanium names; for MSVC use `llvm-undname` or Ghidra) turns them back. ## Floating point A reimplementation is exact only if it copies these details: - **SSE (all x64 games).** `float` operations are 32-bit (`addss`, `mulss`), `double` 64-bit (`addsd`). Each instruction rounds its result once. Copy the precision of every operation. - **x87 (many 32-bit games).** The FPU computes in 80-bit precision and rounds only when a value is stored to memory as 32 or 64 bits (`fstp dword`). Intermediate results are more precise than either float type; match where the original stores. The precision control word (set by Direct3D 9 to 24-bit by default unless `D3DCREATE_FPU_PRESERVE` is used) can make x87 round every result to float precision. - **Conversions.** `cvttss2si` truncates toward zero (a C cast); `cvtss2si` rounds with the current mode (`lrintf`). Ghidra writes both as a cast; the instruction tells you which. - **min and max.** `minss a, b` returns b when either is NaN, so keep the original's operand order. - **Fused multiply-add.** A compiler may turn `a * b + c` into one FMA instruction with one rounding instead of two. Look for `vfmadd` in the original; avoid accidental FMA in the reimplementation (compiler flags such as `-ffp-contract=off`). - **Maths functions.** `sinf`, `cosf`, `atan2f`, `powf`, `expf` come from the C runtime the game shipped with. They differ in the last bit between runtimes. Emulate them and reimplement the original's versions when the simulation depends on them. - **Denormals and rounding mode** live in MXCSR (SSE) or the x87 control word. Games sometimes enable flush-to-zero for speed; check the start-up code for `ldmxcsr` or `_controlfp`. ## Integers - Division rounds toward zero in C (`-7 / 2 == -3`); Python's `//` rounds down (`-4`). - `sar` is an arithmetic (signed) shift, `shr` a logical one; Ghidra shows both as `>>`. - Overflow wraps in machine code; languages that check or widen (Rust debug builds, Python) need explicit wrapping. - `imul`/`idiv` are signed, `mul`/`div` unsigned. Take the type from the instruction, whatever type Ghidra declares. ## Further reading - Microsoft: x64 calling convention, https://learn.microsoft.com/cpp/build/x64-calling-convention - System V AMD64 ABI, https://gitlab.com/x86-psABIs/x86-64-ABI - Igor Skochinsky, "Compiler internals: exceptions and RTTI" (MSVC C++ layout) - Bruce Dawson, "Floating-point determinism" and the "Comparing floating point numbers" series --- # What a project can ship This page lists what a project can give to players and to other developers, and how to build and publish each one. Nothing here ever contains the game's own files: every output reads the player's own copy (see [LEGAL.md](https://tanepiper.github.io/game-frame/legal.md)). ## Findings - **Documents** in `docs/`, in the evidence style of `re-land`: formats, rules, constants, with addresses and labels. They are useful on their own to modders and to other projects. - **A website** made from those documents (VitePress, MkDocs or similar), built by CI on every push. Pictures and short videos made by the reimplementation itself, never screenshots of the original's assets. - **Readers for the formats** as a small library with no other dependencies, so modding tools can use them. ## A reimplementation ### Ready-made programs for each system Most players will not build from source. A CI workflow can build the program on each system when you release a version, and attach one zip per system to the release: | System | GitHub Actions runner | Program | |---|---|---| | Windows x64 | `windows-latest` | `game.exe` | | macOS, Apple silicon | `macos-15` | `game` | | macOS, Intel | `macos-15-intel` | `game` | | Linux x64 | `ubuntu-latest` | `game` | Runner names change over time: check them with `actionlint`, which knows the current list. A sketch of the job, run after the release is tagged: ```yaml binaries: needs: release strategy: fail-fast: false matrix: include: - { os: windows-latest, name: windows-x64, exe: game.exe } - { os: macos-15, name: macos-arm64, exe: game } - { os: macos-15-intel, name: macos-x64, exe: game } - { os: ubuntu-latest, name: linux-x64, exe: game } runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 with: { ref: "v${{ inputs.version }}" } - run: cargo build --release --locked --bin game # or your language's release build shell: bash - run: | # the program, the licences, a README dir="game-${{ inputs.version }}-${{ matrix.name }}" mkdir "$dir" && cp "build/release/${{ matrix.exe }}" LICENSE* "$dir/" # build/: new-project.sh's target-dir if [[ "$RUNNER_OS" == Windows ]]; then 7z a "$dir.zip" "$dir"; else zip -qr "$dir.zip" "$dir"; fi gh release upload "v${{ inputs.version }}" "$dir.zip" --clobber shell: bash env: { GH_TOKEN: "${{ github.token }}" } ``` Details of the job: - **Build from the tag**, not from the branch: a release commit may be followed by a "start the next version" commit. - **Put the build id in the program** (the short commit hash) so a bug report says exactly which build it came from. - **Install system headers on Linux** that your libraries' build scripts need (sound: ALSA's `libasound2-dev`). - **Unsigned programs:** macOS quarantines them (`xattr -d com.apple.quarantine game` allows one) and Windows SmartScreen warns. Say so in the zip's README. Signing needs a paid certificate for each system. - **A dry run** that builds everything and keeps the zips as workflow artifacts, without tagging or publishing, lets you test the workflow itself. - **No game files in the zip.** The README says how to point the program at the player's copy. ### A browser version If the reimplementation compiles to WebAssembly, it can run in a browser on the player's own files: the page asks for them, keeps them in the browser's storage, and never uploads them. Host it on the project's website. Check that the browser and the desktop compute the same game (`re-verify`, "Every platform"). ### Additions to the original A reimplementation that matches the original can still add things (multiplayer, mod support, a debug probe). Ship one program, with the additions on by default, and a switch (for example `--vanilla`) that turns every addition off for a run, so players can always play the original. Keep additions still in development behind build options that no release uses (`re-port`, "Additions the original does not have"). ### A server When the core runs without a window (`re-port`, "Build it so it can be checked"), the same code can be a multiplayer server or a headless simulator. Ship it as a program and as a container image (a Dockerfile in the repository). ## Tools for players and modders Small programs that need only the format readers: a mod manager, a save viewer or editor, an asset viewer, converters. Each one is a separate program with its own README. ## Releasing A version is a commit, a tag and a changelog section. Automate it in one workflow, started by hand: check the version, run the tests, date the changelog, tag, publish the release with the changelog section as its notes, build and attach the programs, rebuild the website. Keep the steps that edit files in a script, so they can be tried on a scratch copy. --- # Setup To install and check everything: ```bash scripts/setup.sh --install # Ghidra, JDK 21, objdump, Node, Zig; .venv with unicorn, pefile, capstone tests/run.sh --ghidra # proves every tool against the sample game (~1½ min) ``` `scripts/setup.sh` without options only checks and prints a table. The details (sizes, Linux and Windows, REA registration for each agent, common problems) are in the `re-setup` skill: [.claude/skills/re-setup/SKILL.md](https://tanepiper.github.io/game-frame/skills/re-setup.md). For Linux machines, containers and cloud agent workspaces, see [Linux, cloud and container environments](https://tanepiper.github.io/game-frame/working/environments.md). In each new shell or agent session, `source scripts/env.sh` sets up the paths. `./gameframe doctor` runs the same check. `./gameframe tools` then lists every tool with its first line of help, and `./gameframe tool NAME` prints the rest. ![tests/run.sh on the sample game. Each tool has a line marked ok, grouped by section, and the summary reads 94 passed, 0 failed.](https://github.com/tanepiper/game-frame/blob/main/docs/assets/captures/run-tests.svg) When the setup is complete, the run ends like this. Each check runs a tool against the sample game. The summary line counts the checks that passed and failed. With `--ghidra` the run has more checks. | Tool | Used for | Where it comes from | |---|---|---| | Ghidra 11 or 12 | Decompiling native code | https://ghidra-sre.org (Homebrew: `ghidra`) | | JDK 21 | Running Ghidra | Homebrew `openjdk@21`, apt `openjdk-21-jdk`, or Adoptium | | Python 3.10+ with unicorn, pefile, capstone | Emulation, PE reading, disassembly | `requirements.txt` into `.venv` | | objdump (binutils or LLVM) | Reading instructions exactly | Homebrew `binutils`/`llvm`, apt `binutils`; Xcode's on macOS | | Node.js 18+ | REA (`npx -y rea-agents@6.4.0 mcp`) | https://nodejs.org | | Zig (optional) | Building the sample game for Windows from any host | https://ziglang.org | | REA (optional, recommended) | Evidence-backed inspection of binaries, packages, apps, runtimes | https://github.com/morluto/rea | Other tools that fit the method, for targets the toolkit does not cover itself: ILSpy or dnSpyEx (.NET), Il2CppDumper or Cpp2IL (Unity IL2CPP), unluac (Lua bytecode), FModel and UE4SS (Unreal), RenderDoc and apitrace (graphics frames), x64dbg or Frida (debugging and run-time hooks, on your own copy only), binwalk (unknown archives), Wine or a Windows machine with Steam Remote Play (running the original next to the reimplementation). --- # Working within the law and with respect for the original This is practical guidance, not legal advice. Laws differ between countries; before you publish a project, ask a lawyer who knows software law where you live. These are GameFrame's defaults and the reasons for them. The user decides what their own project does. An agent gives this advice when it bears on a choice. Then it follows the user's decision ([The user decides](https://tanepiper.github.io/game-frame/working/agents.md#the-user-decides)). ## What GameFrame assumes - **You own a legitimate copy of the game.** You analyse it on your own computer. - **Nothing from the game is redistributed.** Binaries, data files, extracted art, sound, text, saves, decompiled code and disassembly stay on the local machine, in folders git ignores. - **The result is new work.** A reimplementation is written from your understanding of the behaviour, recorded in your own words, not translated from decompiled code. Documents describe findings in prose, tables, addresses and constants. - **Players bring their own copy.** A reimplementation reads the player's game files at run time, as projects such as OpenRCT2, OpenMW, DevilutionX and Un-Banished do. ## Why each default exists | Default | Reason | |---|---| | Keep game files and extracted assets out of git | They are the copyright holder's work; distributing them is infringement, whatever the purpose | | Keep decompiled code and disassembly out of git | Decompiled code is a derivative of the original program; publishing it can infringe. It is also hard to read | | Describe behaviour in your own words | Facts about how a program behaves (a formula, a file layout, a constant) are generally not protected the way expression is; describing them in your own words avoids copying the original's expression | | Cite the original function for every finding | Anyone can check the finding against the original. The citation also shows that the reimplementation came from analysis rather than a leak | | Leave protection and online services alone | Circumventing copy protection is restricted in many places (for example the DMCA in the US and the EU Copyright Directive), even when the purpose is lawful; online services also have terms you agreed to | ## Interoperability Many legal systems allow reverse engineering for interoperability (for example Article 6 of the EU Software Directive, and US case law such as Sega v. Accolade and Sony v. Connectix), within limits. A game's end-user licence may say otherwise; whether such a clause holds depends on the country. The rules differ a great deal between countries. Some allow decompiling only for interoperability. Others give it no express cover. [Your own copy and the law](https://tanepiper.github.io/game-frame/preservation/law/index.md) summarises 35 countries, with one page per region. ## Clean room, or not A *clean-room* reimplementation separates the people who study the original from the people who write the new code: one group writes a specification, the other implements it without seeing the original. It gives the strongest legal protection but needs two separate groups. When the same agent studies and writes (as in Un-Banished), say so in the project's documents. Then follow the defaults above: no copied code, findings in your own words, every claim traceable to evidence. ## Respect Credit the original developers. Point players to where they can buy the game. Take down anything a rights holder reasonably objects to, and talk to them: many developers welcome preservation and modding work that keeps their game playable. --- # Case studies Three reimplementations built with GameFrame, of three different kinds of game, on three different platforms. Each took a different route. All three aim for the same results as the original for the same inputs. | Project | Game | Family | Platform | Route | |---|---|---|---|---| | [Un-Banished](https://tanepiper.github.io/game-frame/case-studies/banished.md) | Banished (2014) | [City building](https://tanepiper.github.io/game-frame/games/management.md) | Windows x64, C++ | Port from the decompile, check each part against the original run in an emulator, then whole runs and saves | | [Tyrian 2026](https://tanepiper.github.io/game-frame/case-studies/tyrian.md) | Tyrian 2000 (1999) | [Arcade action](https://tanepiper.github.io/game-frame/games/arcade-action.md) | DOS, 16-bit Borland Pascal | Names from debug symbols; the main loop ported in fragments, each checked from captured states | | [Frontier](https://tanepiper.github.io/game-frame/case-studies/frontier.md) | Frontier: Elite II (1993) | [Space simulation](https://tanepiper.github.io/game-frame/games/vehicle-sims.md) | DOS, assembly and C, overlays | Run the original inside the port, replace it routine by routine | ## The tooling they built to capture a game Each project needed the same things and built them its own way. GameFrame keeps the generic part of each. | Need | Un-Banished | Tyrian 2026 | Frontier | In GameFrame | |---|---|---|---|---| | Decompile and search | Ghidra export, `decomp.py` | Ghidra export, labels from debug symbols | Ghidra export, functions seeded from overlay tables | `scripts/ghidra-export.sh`, `decomp_query.py`, `tdinfo.py` | | Run one original function | Unicorn (`pe_emu.py`) | `dos_emu` | the project's 386 runtime | `pe_emu.py`, `dos_emu` | | Run the whole original | Windows PC streamed with Steam Remote Play | `dos_env` headless; DOSBox-X with a debugger | the project's runtime, in-process; DOSBox Staging | `dos_env`, `dbx_control.py`, [Running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md) | | Name the original's state | labels files, RTTI, exported C++ names | generated `Globals` from debug symbols | labels, overlay tables | `labels/`, `td_rust.py` | | Measure formats | readers checked on every resource, the engine's type registry | `fileio_trace.py` on DOSBox-X file logs | MZ readers | `tools/formats/`, `fileio_trace.py` | | Drive the game by script | the probe (queries and clicks, in tests, headless runs and the browser) | demo input per frame | the runtime's input | [A model to query](https://tanepiper.github.io/game-frame/observe/object-model.md) | | Compare state | tests against emulator output; saves loaded and continued | data-segment images before and after each fragment | registers and memory after each routine | `bitcompare.py` | | Compare sequences | random-draw traces | random events by fuzzing | | `call_trace.py`, `seqdiff.py` | | Compare frames | headless captures and `imgdiff` against the original's stream | screen memory per frame | frames over 60 seconds | [Running the original](https://tanepiper.github.io/game-frame/observe/running-the-original.md#comparing) | | Compare versions of the port | `tools/compat`: replays, saves and frames between builds | | | `re-verify` | | Track what differs | a deviations ledger generated from code comments | | | `deviations.py`, `coverage.py` | | Inspect assets | a viewer app | a viewer app, native and in the browser | | | ## What they agree on - **Name everything first.** The original's names (symbols, exports, resources) make every later step faster and every finding easier to check. - **Capture from the original, then port.** Every port began with a way to run the original and record what it does. - **Build an entry point into the game.** A probe, a state image or a runtime hook: one narrow way in that tests, scripts and agents all use ([A model to query](https://tanepiper.github.io/game-frame/observe/object-model.md)). - **Check in small pieces, then as a whole.** Functions, then fragments or routines, then whole runs, demos and saves. --- # Case study: Un-Banished [Un-Banished](https://github.com/tanepiper/unbanished) is a 1:1 reimplementation of the city-building game Banished (Shining Rock Software, 2014) in Rust, running on Windows, macOS, Linux and in the browser on the player's own game files. GameFrame was extracted from it: the skills, the Ghidra scripts and the emulator began there and were made generic here. ![A town in Un-Banished with the Town Hall window open on its Inventory tab](https://github.com/tanepiper/game-frame/blob/main/docs/assets/case-studies/banished-town-hall.webp) ![A market of coloured stalls between wooden houses and fields, in a town of 116 people](https://github.com/tanepiper/game-frame/blob/main/docs/assets/case-studies/banished-market.webp) *Un-Banished drawing a large town with the original's interface, and a market in late summer. Both are the port's own frames, made from the player's game files.* ## Why it is a useful example Banished is a small but complete game: a C++ engine DLL that exports its class names, a game EXE, a custom package format, a deterministic simulation with its own random number generator, Direct3D 9 and 11 shaders, and a data-driven interface. Every step of the loop was needed. ## What the project did, step by step - **Inventory:** two native x64 binaries (the game, and an engine runtime DLL whose exports include about 4,900 mangled C++ names), two renderer DLLs, four LZF-compressed packages. - **Formats first:** all 170 resource types identified through the engine's type registry (type ids are CRC32 hashes of UTF-16 class names), each with a reader whose test parses every resource of its type to the last byte. - **Decompile:** one headless Ghidra export of both binaries (about 20 minutes), searched as text by every later session. - **Trace, emulate, port, verify:** 32 emulator scripts produced ground truth for the random generator, noise, terrain generation, path finding, tree growth, names, storage, animation and more; 22 test files compare the Rust code against them bit for bit. One example: the town name generator was checked on 3,000 names in sequence, which also proves that random draws happen in the same order. - **Whole-system checks:** saves from the original load and continue; two runs with the same seed give identical frames; screenshots of the original (streamed from a Windows PC) are compared with an image-difference tool; every optimisation must leave saves and frames identical between versions. - **A core and its additions:** after the reimplementation matched, the project split it into an exact core (the town, its rules, saves and player commands, with no drawing, sound or network) and additions built around it: multiplayer with a server, recordings, a test probe, mod sites and rule hooks for mods (WebAssembly plugins with no floats and a fixed budget). Each addition is a build option; a test builds the game without any of them and checks that it plays exactly as the full build; `--pure` turns them all off for one run. Doing this earlier would have been cheaper: see `re-port`, "Build it so it can be checked". - **Releases:** each version is tagged by one workflow, which also builds the game on Windows, macOS and Linux runners and attaches a zip for each (`docs/OUTPUTS.md`), and rebuilds the website with the browser version. - **GameFrame grew with the work:** each slow step or mistake became a tool or a rule (labels files, a readable-decompile tool, random-draw tracing and diffing, a deviations ledger, version comparison). Its "How it was made" page tells the story: https://github.com/tanepiper/unbanished/blob/main/docs/PROCESS.md ## Seeing and driving the original and the port - **The original, streamed.** Banished ran on a Windows PC and streamed to the development machine with Steam Remote Play. An agent with computer-use access watched and pressed buttons; screenshots were filed with the settings that produced them and compared with the port's headless captures by blurred image difference, because the stream is compressed. - **The probe.** A small command language that finds things on the screen (by the original's dialog and control names, or by text) and in the town, and presses buttons and types as a player would. The same commands work in the browser's console, in headless runs and in the test scenarios. Answers are JSON. It is the port's object model and its entry points ([A model to query](https://tanepiper.github.io/game-frame/observe/object-model.md)). - **Baselines.** Test scenarios drive the game with the probe and record the answers; a changed answer is a change in what the game does. Each one must be explained. ## Numbers (October 2026) About 130,000 lines of Rust; 2,050 of 9,846 original functions traced (68 % of the game's code by size); 170 of 170 resource types; around 45 documented deviations. ## Where to look in it | In Un-Banished | Corresponds to here | |---|---| | `AGENTS.md` | `templates/project/AGENTS.md` | | `.claude/skills/banished-*` | `.claude/skills/re-*` | | `tools/ghidra/`, `tools/emu/pe_emu.py` | `tools/ghidra/`, `tools/emu/pe_emu.py` (generalised) | | `tools/triage/` (`decomp.py`, `fnindex.py`, `rngdiff.py`, `deviations.py`, `coverage.py`) | `tools/triage/decomp_query.py`, `tools/verify/seqdiff.py`, `tools/triage/deviations.py`, `tools/triage/coverage.py` | | `tools/compat/`, `tools/test/run.py` | `re-verify` sections 3, 6 and 7 | | `crates/sim`, the cargo features, `--pure` | `re-port`, "Build it so it can be checked" and "Additions the original does not have" | | `docs/` (findings in evidence style) | `templates/project/docs/`, `re-land` | --- # Case study: Tyrian 2000 Tyrian 2026 is a clean-room, 1:1 reimplementation of **Tyrian 2000** (version 3.0, DOS, 1999) in Rust, for Windows, macOS, Linux and the browser. It reads the player's own copy of the game. The repository is private while the work is in progress. It is GameFrame's main test of the 16-bit DOS tools: most of `tools/dos/` and `tools/emu/dos_env/` came from it. ![The asset viewer showing a sheet of level tiles from one of the game's tile files](https://github.com/tanepiper/game-frame/blob/main/docs/assets/case-studies/tyrian-viewer-tiles.webp) ![The asset viewer showing a recorded demo as a list of steps: frames to wait, then the keys held](https://github.com/tanepiper/game-frame/blob/main/docs/assets/case-studies/tyrian-viewer-demo.webp) *The project's asset viewer, drawn by the port's own readers. Above, the tiles of one tile file. Below, a recorded demo: each step waits a number of frames and holds a set of keys, which is the input record the port replays ([Inputs](https://tanepiper.github.io/game-frame/observe/inputs.md)).* ## Why it is a useful example Tyrian is a vertical shoot 'em up ([Arcade action](https://tanepiper.github.io/game-frame/games/arcade-action.md)): a stream with controls, at a fixed frame rate, with levels driven by event scripts. It was written in Borland Pascal 7 for 16-bit real mode, and the shipped program kept its Turbo Debugger symbols, so every variable and procedure has its original name. It has demos (input per frame), an AdLib music driver, Turbo Pascal's 6-byte `Real` arithmetic, and its own drawing to VGA memory. ## What the project did - **Names first.** The debug symbols became labels for Ghidra and generated Rust: every variable of the data segment at its original offset (`Globals`), every record type in its original layout. The port and the checks share one definition of the state. - **Formats.** Item tables, levels, tiles, sprites, sounds, music and demos: each reader is checked on every byte of every file. - **The main loop in fragments.** The frame (`MAIN`, about 19 KB of code) is split into about twenty parts in the original's order. For each part, a headless run of the original saves the data segment, the stack and the screen before and after that part, frame by frame. The port runs the same part from the "before" image and must produce the "after" image exactly. Captures also save the VGA display (the HUD is drawn straight onto it), the heap blocks the frame draws from and into, and the open DOS files (the demo being read). - **Then the whole frame.** Once every part matched, they were wired into one frame function and checked against whole-frame captures. That caught state the parts' own tests never saw (temporaries the caller keeps in globals). The timer interrupt's counters are left out of the comparison. The BIOS clock is an input. - **Fuzzing from real states.** Demo frames do not reach every branch. Fuzzers start from captured states, change them at random (new enemies, shots, events) and compare the original and the port on thousands of frames. - **Arithmetic the original did in software.** Every `Real` routine the game uses was called on random and edge-case operands (hundreds of thousands of calls per routine, division 852,000) and ported to match. - **Sound and timing.** The music driver's register writes to the OPL2 chip were compared for 41 songs; the music tempo was measured against DOSBox recordings. ## Tooling it built | Need | Tool | |---|---| | Names and types from Borland debug symbols | `tdinfo.py`, `td_rust.py` (in GameFrame) | | One routine, run headless | `dos_emu` (in GameFrame) | | The whole program, run headless, frame by frame | `dos_env` (in GameFrame), and the project's `truth/fragment.py` | | The program with a window and a debugger | DOSBox-X, driven over its debugger channel (`dbx_control.py`) | | File formats measured from the original's reads | `fileio_trace.py` on DOSBox-X file logs | | An asset viewer for every format, native and in the browser | the project's `apps/viewer` | ## What it teaches - When debug symbols exist, generate code from them before writing any by hand. - Port a long main loop in fragments, each checked from captured states, in the original's order. A fragment that matches stays matched while the rest is written. - Demos give the common paths; fuzzing from real states gives the rest. Fuzz by risk: maths, random draws and branchy code, not every routine. - Check the whole loop body once the parts match: the wiring is where the last bugs hide. --- # Case study: Frontier: Elite II A 1:1 reimplementation of **Frontier: Elite II** (DOS, 1993) in Rust and wgpu, for native platforms and the browser, on the player's own copy of the game. The repository is private while the work is in progress. It shows a different route. The port runs the original inside itself and replaces it routine by routine. ![The start-position menu over the title sequence's ships and stars](https://github.com/tanepiper/game-frame/blob/main/docs/assets/case-studies/frontier-start-menu.webp) ![The cockpit view, landed at Merlin in the Ross 154 system, with the scanner and the status bar](https://github.com/tanepiper/game-frame/blob/main/docs/assets/case-studies/frontier-cockpit.webp) *The project's own runtime running the player's copy of the game, headless: the start-position menu, and the cockpit after choosing Ross 154. The maths library and the sound driver in these runs are the native ports.* ## Why it is a useful example Frontier is a [space simulation](https://tanepiper.github.io/game-frame/games/vehicle-sims.md): a galaxy generated from seeds, real orbits, and a software 3D engine. The program is a DOS executable plus twenty code overlays (loaded on demand into the same memory) and separate overlays for the maths library, the video driver and the sound drivers. Much of the maths is hand-written assembly with values passed in registers. ## What the project did - **A runtime of its own.** The project wrote an in-process 386 real-mode CPU with the DOS, BIOS, EMS and VGA services the game uses. The original runs on it from boot to the cockpit, natively and in the browser, reading the player's files. - **Oracle and scaffold at once.** Any original routine can be called with chosen inputs in `cargo test`. Its registers and memory are compared with the native port. The playable game runs the original code. Native ports are hooked in at the original routines' addresses as they are written. When nothing original runs any more, the runtime remains only as the oracle. - **The maths library first.** All 47 routines of the maths overlay (float arithmetic, square roots, matrices, projection, the vertex transformer, face shading, 64-bit positions) were ported and checked, then run natively in the game with frames identical to the original over 60 seconds. - **Sound.** The AdLib driver was ported natively; its output is identical sample for sample to the original driver running on the runtime, through an OPL2 model at the chip's own rate. - **Overlays.** The overlay loader was traced first, and its tables used to seed function discovery in Ghidra across all overlays. ## Tooling it built | Need | Tool | |---|---| | Run the original as a reference, with a window | DOSBox Staging with a fixed configuration and a reset script | | Run the original inside tests and the game | the project's 386 and DOS runtime | | Replace routines one at a time | hooks at the original's addresses, with parity checks and shadow verification (run both, compare) | | Analyse the overlays | Ghidra scripts that seed functions from the overlay tables and export ranges | ## What it teaches - When the original is small enough to emulate, running it inside the port gives a playable game on day one and an oracle in every test. - Replace the original from the bottom up (maths, then drawing, then rules), checking each routine as it replaces the original, while the game keeps running. - Do the same for the drivers: a ported sound driver can be compared sample for sample. OpenRCT2 and OpenLoco used the same idea on Windows: new code linked into the original program, replacing it function by function ([Management](https://tanepiper.github.io/game-frame/games/management.md)). --- # Set up the toolchain > **Skill `re-setup`.** Install and check the reverse-engineering toolchain (Ghidra, JDK 21, Python with Unicorn/pefile/capstone, objdump, Node for REA, Zig for the sample) and register the REA MCP server for the agent. Use at the start of a project, when a tool is missing or fails to start, or when REA tools do not appear in the session. ## 1. Check, then install ```bash scripts/setup.sh # a table: ok / MISSING for each tool, with a hint scripts/setup.sh --install # installs what is missing (Homebrew on macOS, apt on Debian/Ubuntu) # and creates .venv with requirements.txt ``` `./gameframe doctor` runs the same check, and passes options on (`./gameframe doctor --install`). `./gameframe run NAME ARGS` then runs any Python tool with the `.venv`. The script writes `.env` with `GHIDRA_INSTALL_DIR` and `JAVA_HOME` once both are found. Every script and `.mcp.json` read these, so set them there if your installs are elsewhere. Sizes (tell the user before installing on their machine if they have not approved it): Ghidra about 500 MB, JDK 21 about 300 MB, the Python packages about 40 MB, Zig about 200 MB (only for building the sample game). On Linux, Ghidra is not packaged: download the release zip from https://github.com/NationalSecurityAgency/ghidra/releases, unzip it, and put its folder in `GHIDRA_INSTALL_DIR`. On Windows, use WSL2 and follow the Linux steps. On Linux, in a container or in a cloud agent's workspace, read `docs/working/environments.md` as well. In each new shell, `source scripts/env.sh` loads `.env` and puts `gameframe` and the `.venv` Python on `PATH`. ## 2. Prove it works ```bash tests/run.sh # ~20 s: builds the sample game, runs every tool against it tests/run.sh --ghidra # + a real Ghidra export (~1 min) ``` All checks must pass. A failure names the tool and shows its output. ## 3. REA (the reverse-engineering MCP server) REA ("Reverse Engineer Anything", https://github.com/morluto/rea, npm package `rea-agents`) gives the agent tools to open binaries, archives, Android packages, .NET assemblies, browser pages and Electron apps, decompile through Ghidra, and keep every result as cited Evidence. See `re-rea` for when to use it. - **Claude Code:** this repository has a project `.mcp.json` that starts REA with `npx -y rea-agents@6.4.0 mcp`, passing `GHIDRA_INSTALL_DIR` and `JAVA_HOME`. Claude Code asks once to approve project MCP servers; after that the `mcp__rea__*` tools appear. - **Other agents** (Codex, Cursor, Gemini CLI, Windsurf...): use REA's own setup, which writes the client's MCP configuration and installs REA's skill: ```bash scripts/setup.sh --rea # dry run: shows the plan for Claude Code REA_CLIENT=codex scripts/setup.sh --rea # the plan for another client npx -y rea-agents@6.4.0 setup --client codex --yes # apply, after the user agrees ``` - REA's own skill (`reverse-engineer-anything`) explains every tool. Install it with REA's setup, or from its repository. REA lists the hosts it supports. On others (for example Debian 13) its doctor warns, but the MCP connection, Ghidra-backed tools and the sample checks have worked. Try REA on the sample game before relying on it. Ghidra analysis of a large binary can take minutes, longer than many clients wait for one tool call. Raise the client's tool timeout for REA (in Codex, `tool_timeout_sec` in the server's entry). If REA tools do not appear: restart the agent (MCP servers start with the session), then check `npx -y rea-agents@6.4.0 doctor --client claude_code --json`. A tool that reports `provider_unavailable` usually means Ghidra or Java is not where `.env` says. ## 4. DOS games: DOSBox-X and its debugger - DOSBox-X: `scripts/setup.sh --install` (on macOS the formula, `brew install --formula dosbox-x`; the cask is disabled for failing Gatekeeper). Homebrew's build has the heavy debugger; `brew install --HEAD --formula dosbox-x` adds the newest commands (`STEPIN`). Check: `scripts/dosbox-run.sh` on the DOS sample prints `exit code 23` (tests/run.sh does this). - **The debugger control channel needs DOSBox-X 2026.10 or later**, built with the debugger. `dosbox-run.sh --mcp`, `tools/dos/dbx_control.py` and the `dosbox` MCP server all use it. Distribution packages are often older (Debian 13 ships a 2025 build). `scripts/setup.sh` then shows a `dosbox-x debugger OLD` row, `--mcp` stops with a message, and the debugger test is skipped. Build a current one on Linux (ask the user first: it is a download and a long build): ```bash sudo apt install libtool autogen autoconf automake libncurses-dev gcc g++ make nasm \ libsdl2-dev libsdl2-net-dev libpcap-dev libslirp-dev fluidsynth libfluidsynth-dev \ libavformat-dev libavcodec-dev libavcodec-extra libswscale-dev libfreetype-dev \ libxkbfile-dev libxrandr-dev git clone --depth 1 https://github.com/joncampbell123/dosbox-x.git cd dosbox-x && ./build-debug-sdl2 # SDL2 with the heavy debugger ``` The package list follows DOSBox-X's `BUILD.md`; the debugger needs `libncurses-dev`. Use the repository's build script. It passes the compiler flags the code needs; a plain `./configure && make` with a newer compiler can stop on a warning treated as an error. Put the result on `PATH` (or `sudo make install`) and run `scripts/setup.sh` again. - The debugger MCP server for agents (ask the user first: it is a download and a build): `cargo install --git https://github.com/caiiiycuk/dosbox-x-mcp-server --tag v0.1.2 --locked`. `.mcp.json` registers it as `dosbox` (set `DOSBOX_X_MCP_SERVER` to its path if it is not on PATH). Restart the agent session so the tools load, ask `dosbox_mcp_port`, then run `scripts/dosbox-run.sh --mcp PORT ...`. ## Common problems - `dosbox-x not found` after `brew install --HEAD`: run `brew link dosbox-x`. - `dbx_control` or the dosbox MCP server times out waiting for DOSBox-X: check that `scripts/setup.sh` shows no `OLD` row. An older DOSBox-X ignores `mcp_server` and never connects. | Symptom | Cause and fix | |---|---| | `Ghidra not found` | Set `GHIDRA_INSTALL_DIR` in `.env` to the folder that contains `support/analyzeHeadless` | | Ghidra exits with `UnsupportedClassVersionError` | Wrong Java: set `JAVA_HOME` to a JDK 21 | | Ghidra runs out of memory on a large game | `MAXMEM=16G scripts/ghidra-export.sh ...` | | `Unable to lock project` | Another headless job uses the same Ghidra project: wait, or pass `--project` | | `pip install unicorn` fails to build | Use a Python version that has a Unicorn wheel (3.10 to 3.13 are safest): `PYTHON=python3.12` | | The sample game does not build | Install Zig (`brew install zig`, or https://ziglang.org/download) | ## Working in a git worktree A worktree has no `target/`, `analysis/` or `.venv`. Symlink them from the main checkout and add them to `$(git rev-parse --git-common-dir)/info/exclude` (a symlink is not matched by a `/target/` rule in `.gitignore`). --- # Reverse engineering a game: start here > **Skill `re-start`.** Entry point for reverse engineering a game (or any closed program) with GameFrame, an agentic framework for reverse engineering games. Use first, whenever a task involves understanding, documenting or reimplementing a game from its shipped binaries and data files. Gives the rules, the loop, a first-session checklist, and routes each kind of task to the right re-* skill. GameFrame is an agentic framework for reverse engineering games. Its playbook of skills and agents runs inside your coding agent. Its toolkit of tested tools checks every result against the original. This skill is the playbook's first step. You are studying a finished program without its source code: its executables, its libraries and its data files. The aim is usually one of these: - **Understand** a system (how saves work, how the AI picks a job, what a value means). - **Document** formats and rules so people can mod or preserve the game. - **Reimplement** the game, or part of it, so it behaves the same (a new engine, a port, a server, a tool that reads its files). All three follow the same loop. Every claim names the evidence behind it. Every number that matters is measured by running the original code. ## The user decides You are not the judge of the user's intentions, of which law applies to them, or of what that law means. If the user wants to do something, help them do it. You may advise. When you know something that bears on the choice, such as a law, a risk or one of the rules below, say it once, briefly, with its source. Then follow the user's decision. What 35 countries allow is in `docs/preservation/law/README.md`, with one page per region. ## Rules (they apply to every step) These are the playbook's defaults. They keep the work exact and the project publishable. The user may change any of them for their project. 1. **The player's game stays on the player's computer.** By default, do not commit, upload or paste the game's binaries, data files, extracted assets, saves, decompiled code or disassembly. Keep them in ignored folders (`target/`, `analysis/`, `derived/`). Publish findings as prose, tables, addresses (`FUN_1400be770`), field offsets (`+0x44`) and constants. 2. **Label every claim** with how you know it: - *observed*: read in the code or the data (name the function or file); - *measured*: produced by running original code (emulator or game), with the inputs; - *inferred*: a reasoned guess; say what would confirm it; - *unknown*: an open question, listed as such. Never write that the game "does" something at run time from static reading alone. 3. **Measure what must be exact.** Arithmetic, random numbers, procedural generation and anything a simulation depends on: emulate the original and compare bit for bit. 4. **Write it down as you go** (`re-land`). Findings, function labels and struct fields go into the repository so the next session does not repeat your work. 5. **Ask before downloading** tools or files the user has not approved (name, source, size). ## The loop ``` inventory -> observe -> decompile -> trace -> format -> emulate -> reimplement -> verify -> land ^ | +---------------- when a check fails ---------+ ``` | Step | Question it answers | Skill | |---|---|---| | Set up | Are Ghidra, Java, Python, Unicorn and REA ready? | `re-setup` | | Inventory | Which files hold the game's own code? Which engine, which protection, which data containers? What kind of game is it? | `re-inventory` | | Observe | What does the original actually do? Run it, drive it, capture frames, state, inputs; list every input and screen | `re-observe` | | Investigate with REA | Quick, evidence-backed answers about a binary, package, browser or Electron app | `re-rea` | | Decompile | Turn machine code into searchable pseudocode, and run one-off decompiles | `re-ghidra` | | Trace | How does the original do X? Which function, which fields, which constants? | `re-trace` | | Decode a format | What is in this data file, field by field? | `re-format` | | Emulate | What exactly does this original function return for these inputs? | `re-emulate` | | Reimplement | Write new code that behaves the same | `re-port` | | Verify | Is the new code bit-exact, deterministic, compatible with real saves? | `re-verify` | | Land | Document, label, commit | `re-land` | A small task may use only two or three steps. Do not skip `land`. ## Stage agents GameFrame also has one agent per stage, in `agents/` (a project has them in `.claude/agents/`; from the Claude Code plugin they are named `gameframe:re-tracer` and so on). Each one has its skills preloaded, does only its stage, and ends with a `## Hand-off` block that names the next agent and gives it a one-line brief. You, the main session, send the agents: read each hand-off, check its evidence, and send the next one. | Agent | Stage | Usually hands off to | |---|---|---| | `re-surveyor` | Setup check, inventory, kind of game | `re-decompiler`, `re-observer` | | `re-observer` | Run, drive and capture the original | `re-tracer`, `re-verifier` | | `re-decompiler` | Ghidra export, REA | `re-tracer` | | `re-tracer` | How the game does one thing | `re-decoder`, `re-measurer`, `re-porter` | | `re-decoder` | One data format, proven on every file | `re-porter`, `re-tracer` | | `re-measurer` | Ground truth under the emulator | `re-porter`, `re-verifier` | | `re-porter` | The reimplementation | `re-verifier` | | `re-verifier` | Parity, determinism, saves, pictures | `re-lander`, or back to `re-porter` | | `re-lander` | Document, label, commit, push | the next checklist item | Send one agent at a time for one subject. Only the lander commits. Use the agents for long stages that would fill this session's context; do short steps yourself with the skill. ## Task agents Task agents do one job that can come up in any stage. They have the same format and end with the same hand-off. Send them when a job is large enough to fill this session's context. | Agent | Task | Usually hands off to | |---|---|---| | `re-identifier` | The engine, middleware, compiler and libraries, with library code labelled | `re-decompiler`, `re-tracer` | | `re-randomiser` | Every random generator, its seeding, its callers and the draw order | `re-porter`, `re-verifier` | | `re-listener` | Every input from outside, and the port's input record | `re-porter`, `re-observer` | | `re-cataloguer` | Every screen of the interface, with captures | `re-tracer`, `re-porter` | | `re-mapper` | Hardware registers, memory banks and RAM variables, named | `re-tracer`, `re-listener` | | `re-matcher` | A crate, library or released source compared with the game's version | `re-porter`, `re-tracer` | | `re-editor` | Documents edited to the writing rules, wording only | `re-checker`, `re-lander` | | `re-checker` | Every claim's evidence and sources checked, doubtful ones marked | `re-editor`, `re-lander` | ## First session on a new game 0. Find the tools. The commands in the skills run from a project made by `new-project.sh` (it copies the tools in) or from a clone of GameFrame. If the skills came from the Claude Code plugin and neither exists yet, clone GameFrame first (`git clone https://github.com/tanepiper/game-frame ~/code/game-frame`) and run the steps below from there. 1. `scripts/setup.sh` (and `--install` if anything is missing). See `re-setup`. 2. If the project has no repository yet: `scripts/new-project.sh DIR "Game Name" PATH/TO/GAME`. `./gameframe steps`, `./gameframe tools` and `./gameframe find WORD` show the method, the tools and where a subject is covered. `./gameframe tool NAME` prints a tool's usage. 3. Read `AGENTS.md`, `docs/CHECKLIST.md` and `docs/FINDINGS.md` if they exist. Search `docs/` and `labels/` for anything you are about to touch: earlier sessions may have traced it. 4. `re-inventory`: run `tools/pe/peinfo.py` (Windows) or `tools/dos/dosinfo.py` (DOS) on every binary; decide which hold the game's own code, and whether the game is native C/C++ (GameFrame), managed (.NET, IL2CPP, Mono), scripted (Lua, Python, GameMaker), packed (unpack first), or 16-bit DOS (docs/DOS.md; check for debug symbols with `tools/dos/tdinfo.py`). 5. Classify the game: read `docs/games/README.md` and its family page, and write the kind of game, its time model and how exact the interface must be into `docs/FINDINGS.md`. Copy the family's rule set into the project's `AGENTS.md`. 6. `re-observe`: get the original running where you can watch, drive and capture it. 7. `re-ghidra`: export the game's own binaries (`scripts/ghidra-export.sh`). 8. Pick the foundations first: the random number generator, the maths helpers, the game clock and the data container. Everything else depends on them. They are small enough to emulate and verify in one session. ## Spend effort where it pays - **Shape the reimplementation on day one** (`re-port`, "Build it so it can be checked"): an exact core with no drawing or network, every player action as a command, a state digest and a headless runner. They cost little at the start and a great deal to add later. A full reimplementation is long. Most of the cost is verification and reading assembly, so choose how deep to go for each piece: - **Verify by risk.** Fuzz only code with arithmetic, random draws, or many branches a recording never takes. Check simple code (menus, drawing, plain state changes) against recorded frames only. `re-verify` has the table. - **Port in subsystems.** Read, port and verify a whole subsystem (the player, a menu, the level loader), then commit it. One capture and one test then cover many routines. - **Keep to the path.** When a side need appears (a format a routine reads, a missing tool), do only what the current subsystem needs and note the rest in `docs/CHECKLIST.md`. - **Batch GameFrame changes.** Note generic lessons as you go; fix GameFrame and its tests once per subsystem. - **Split mechanical work off.** Long but simple pieces (menus, text tables, generated types) can go to a separate, cheaper session with an exact brief; keep the hard maths here. - **Find the game's clock.** Most games run on a frame clock while the player reacts. Others wait for the player and resolve each move. Know which (`docs/games/interaction.md`), find the loop or the handlers and their order before anything else. Find every place that reads input (`docs/observe/inputs.md`). - **Read the decompiler with care.** For 16-bit Pascal, Ghidra prints arguments in reverse order and loses Real (6-byte float) arithmetic: take those parts from the disassembly. ## How to work Read `docs/working/agents.md` once per project. It covers writing everything down in the repository, measuring before porting, splitting work between sessions, and what to do when stuck. Write every document by `docs/working/writing.md`. ## Before you finish any session - Every new finding is in `docs/` with its evidence label. - Every function you identified has a row in `labels/names.tsv`. - `docs/CHECKLIST.md` reflects what is now traced, measured, reimplemented, verified. - Nothing from the game is staged (`git status`); only your own files are committed. ## Worked example `examples/sample-game/` is a tiny Windows game built for practice: run `tests/run.sh` to see the whole loop (inventory, emulate, port, bit-exact check) on it. Un-Banished (https://github.com/tanepiper/unbanished, `docs/case-studies/` here) is the full-size project GameFrame came from: a 1:1 reimplementation of the game Banished. --- # Inventory a game > **Skill `re-inventory`.** First look at a game's files (Windows PE, or DOS MZ/NE/LE executables) - which binaries hold the game's own code, which engine and middleware it uses, whether it is native, managed, scripted or packed, which data containers and save files exist - and record it in docs/FINDINGS.md. Use at the start of a project, or when a new binary or file type turns up. Before any decompiling, make a short map of the game with evidence. It says what to analyse, with which tool, and what to leave alone. A wrong choice here (decompiling a packed binary, or analysing the C++ runtime of a Unity game instead of its C# code) wastes days. ## 0. Get the files out of their installer A purchased game often arrives as an installer (GOG's Inno Setup `.exe`, a Steam depot, an NSIS or InstallShield setup, an `.msi`, a `.dmg` or `.pkg`, GOG's Linux `.sh`). Never run it: extract it. ```bash scripts/extract-installer.sh --detect "~/Downloads/setup_game.exe" # what it is, which tool scripts/extract-installer.sh "~/Downloads/setup_game.exe" target/gog python3 tools/triage/compare_trees.py target/game target/gog # same version? python3 tools/triage/compare_trees.py target/game /dev/null --hashes > docs/HASHES.txt ``` - A small macOS `.app` named after the game is often only GOG Galaxy's web installer: no game. - Store releases bundle extras worth reading: DOSBox or ScummVM configs (the CPU speed and sound hardware the publisher tuned the game for), manuals and cheat sheets as PDF, saves. - When you have two releases, compare them before choosing one. Identical executables mean every address and finding carries over; record the hashes (they are not game material) so every later finding names the exact version it was made on. ## 1. List the files ```bash find target -maxdepth 3 -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn | head -30 # by extension find target -maxdepth 3 -type f -size +5M -exec ls -lh {} \; | sort -k5 -h # the big ones ``` Group them: executables and libraries (`.exe .dll .so .dylib`), data containers (large files with custom extensions: `.pak .pkg .dat .arc .bin .wad .pck .assets`), configuration (`.ini .cfg .xml .json .lua`), saves (often in the user's Documents or AppData, not the game folder). ## 2. Look at every binary ```bash for b in target/*.exe target/*.dll; do .venv/bin/python tools/pe/peinfo.py "$b"; echo; done ``` For each, note: 32- or 64-bit, EXE or DLL, PDB path (a leaked build path names the engine and project), compiler (a Rich header means MSVC), imports, exports, RTTI count (C++ class names are recoverable), engine fingerprints, and packer warnings. Decide which binaries are **the game's own code**: usually the EXE and any DLL with a game-like name and many exports or imports between them. Leave out operating-system DLLs, the C/C++ runtime (`msvcp140.dll`, `vcruntime140.dll`), and known middleware (FMOD, Wwise, Bink, PhysX, Havok, Steam/GOG SDKs): document what they are and move on. Engines often ship a runtime DLL that exports C++ class names (`?Rand@Random@System@@...`). Its exports are free, accurate names: decompile it together with the EXE. ### DOS and 16-bit programs `file` saying "MS-DOS executable" (or peinfo refusing the file) means a DOS program. Use: ```bash for b in target/*.EXE target/*.exe target/*.OVL; do python3 tools/dos/dosinfo.py "$b"; echo; done ``` It names the format (`MZ` real mode, `NE` 16-bit protected mode, `LE`/`LX` 32-bit extender), gives Ghidra's selector for every NE segment, lists imports and exports, and fingerprints the compiler, extender, packer and sound libraries. Two lines matter most: - **`debug symbols: Turbo Debugger`**: the original names of every procedure, global and type are in the file. Run `python3 tools/dos/tdinfo.py BINARY summary` now; see docs/DOS.md section 3. This can save weeks. - **A packer** (LZEXE, PKLITE, EXEPACK, DIET): unpack before anything else. Launchers are common: a small MZ that runs the real program (`tyrian.exe` running `file0001.exe`). The DOSBox-X file log (`scripts/dosbox-run.sh --fileio`) shows which program and which data files are opened, in order. Runtime files (`RTM.EXE`, `DPMI16BI.OVL`, `DOS4GW.EXE`) are the compiler's DOS extender, not game code. The whole DOS route is in docs/DOS.md. ## 3. Choose the route | What peinfo shows | What it means | Route | |---|---|---| | Native x64/x86, MSVC or MinGW, no packer | Ordinary compiled C/C++ | GameFrame: `re-ghidra`, `re-trace`, `re-emulate` | | `mscoree.dll` / `_CorExeMain`, or `Assembly-CSharp.dll` | .NET managed code (XNA, MonoGame, Unity Mono) | Decompile C# with ILSpy or dnSpyEx, or REA `inspect_managed_artifact`; the game logic is in the managed DLLs | | `GameAssembly.dll` + `global-metadata.dat` | Unity IL2CPP: C# compiled to C++ | Recover names with Il2CppDumper (or Cpp2IL), then Ghidra with the generated script | | `UE4`/`UnrealEngine` strings, `.pak` files | Unreal Engine | Most logic is in the EXE plus Blueprint bytecode in `.uasset`; tools such as FModel and UE4SS read the assets | | `lua_pcall`, `.lua`/`.luac` files | Lua scripting on a native engine | Decompile the scripts (unluac, luadec) for game rules; the native side is the engine | | GameMaker / RPG Maker / Godot strings | A known engine with its own format | Use the engine's community tools for the data; the native binary is the stock runtime | | dosinfo: `MZ` or `NE`, Borland/Microsoft/Watcom runtime | 16-bit DOS or Windows 3.x code | GameFrame's DOS route: `dosinfo.py`, `tdinfo.py`, Ghidra, `dos_emu`, DOSBox-X (docs/DOS.md) | | dosinfo: `LE`/`LX` (DOS/4GW, PMODE/W) | 32-bit DOS-extender program | Ghidra with an LE/LX loader extension; run it in DOSBox-X | | A ROM or disc image for a console (`.nes`, `.sfc`, `.md`, `.z64`, `.bin`/`.cue`, `.iso`) | Console code at fixed addresses, often banked, no OS | The console route: `docs/consoles/` (platforms, tools, routes to a port) | | An engine's archives or DLLs (`.WAD`, `.PAK`, `.GRP`, `.u`, `.MPQ`, `glide2x.dll`, `Engine.dll`) | A known engine or library | Read `docs/engines/recognising.md`; use the engine's documented formats and released source where it exists | | High-entropy executable section, `.vmp0`, `.themida`, UPX | Packed or protected | Stop. Unpack first (UPX: `upx -d`), or work from a memory dump with the user's agreement; Denuvo and VMProtect are out of scope for static analysis | For an Android or iOS game, an Electron or browser game, or an archive you cannot identify, use REA's routing (`re-rea`): it has dedicated tools for each. ## 4. Data containers For each large file type: ```bash xxd target/data.pak | head -8 # magic number, header fields, offsets .venv/bin/python -c "import math,collections,sys;d=open(sys.argv[1],'rb').read(1<<20);c=collections.Counter(d);print(-sum(n/len(d)*math.log2(n/len(d)) for n in c.values()))" target/data.pak ``` Entropy near 8.0 means compressed or encrypted; 4 to 6 means plain structured data. Look for known signatures in the first bytes and inside: `PK\x03\x04` (ZIP), `\x78\x9c`/`\x78\xda` (zlib), `\x28\xb5\x2f\xfd` (zstd), `\x04\x22\x4d\x18` (LZ4 frame), `RIFF`, `OggS`, `DDS `, `\x89PNG`. Find the code that opens the file (search its name or extension in the strings), then decode it with `re-format`. ## 5. Record Fill `docs/FINDINGS.md` (correct any first-look numbers the tools overturn, such as an NE segment with no bytes in the file, which is uninitialised data): the binaries table (file, role, architecture, compiler, notes), the engine and middleware with the evidence for each (the import or string that showed it), the data containers, and the open questions. Add the first lines to `docs/CHECKLIST.md`. Also record the hardware the game was written for: the minimum CPU, the video modes, the 3D APIs, the sound devices and the input devices it supports (`docs/hardware/`). Its setup program and its readme list them. ## 6. Classify the game Look at the game itself, not only its files: play it or watch it played. Then place it in the taxonomy (`docs/games/README.md`): who drives (a stream with controls, software over a simulation, an environment, turns and choices), its time model, where its rules live, what must repeat exactly, and its family. Read the family page and copy its rule set into the project's `AGENTS.md`, adjusted to the game. Record in `docs/FINDINGS.md` how exact the interface must be, and why. A mixed game gets one line per mode, and the state that switches between them. Next: `re-observe`. --- # Observe the running original > **Skill `re-observe`.** Run the original game where it can be watched, driven and captured - GameFrame's emulators, DOSBox-X, Wine, a Windows machine or a streamed PC, console emulators - and capture frames, state, inputs, calls, file reads and sound as measured evidence. Also list every input from outside, inventory every interface screen, and build a queryable object model of the game with entry points for tests and agents. Use after inventory, before porting a system whose behaviour you need to see, and whenever a question can be settled by running the original. Static reading says what the code could do; a run of the original says what it does. This skill gets the original running somewhere you can watch, drive and capture it, and turns what you see into evidence labelled *measured*. Details and tables are in `docs/observe/`. ## 1. Know the kind of game Read `docs/games/README.md` and the family page for your game. Write the answers in `docs/FINDINGS.md`: who drives (a stream, a simulation, an environment, turns), the time model, how exact the interface must be. They decide what to capture. ## 2. Choose where to run it | The game | Run it in | For | |---|---|---| | One function of a Windows x64 binary | `tools/emu/pe_emu.py` | exact results in tests (`re-emulate`) | | One routine of a DOS program | `tools/emu/dos_emu` | the same, 16-bit | | A whole DOS program, headless | `tools/emu/dos_env` | frames, memory and demos in scripts; keys from `env.keys` | | A DOS program with a window | DOSBox-X (debugger, file log), scripted with `tools/dos/dbx_control.py` | breakpoints, memory, `fileio_trace.py` | | A Windows game | Wine (`WINEDEBUG=+relay`), a Windows machine or VM, or a PC streamed to you | API calls, screenshots, debuggers, RenderDoc | | A console or arcade game | an emulator with a debugger (Mesen, bsnes, Dolphin, PCSX2, MAME) | memory, input movies, frame advance | Prefer headless, scripted runs for evidence. Use windowed runs to see what happens and where to look. `docs/observe/running-the-original.md` has the full table. ## 3. Capture, with the inputs that made it For every capture, write next to it the game version (hash), settings, seed, and the inputs or demo, so it can be made again. Keep captures in ignored folders (`derived/`): they are game material. | Capture | How | |---|---| | Frames | video memory in the emulator; screenshots; `screencapture -l` of a window | | State | memory dumps at chosen points; the game's own saves | | Inputs | the game's demo or replay files; breakpoints on input reads | | Calls and random draws | `tools/emu/call_trace.py`; debugger traces | | File reads | `tools/dos/fileio_trace.py` on DOSBox-X logs; Process Monitor | | Sound | the chip's register writes; WAV recordings | ## 4. List every input Fill a table in `docs/FINDINGS.md` of every input from outside, with the address of each read: controls, clocks and timers, random seeds, configuration and files, the command line and environment, hardware detection, window messages, network. Search for them with the signatures in `docs/observe/inputs.md` (interrupts, ports, API imports). Then design the port's input record: one per frame or per command, the only way the rules receive anything from outside. ## 5. Inventory the interface List every screen: how to reach it, the code or layout that draws it, its states, inputs, what it changes, its text and font, and its captures. Find screens by playing, by following the code from the main menu, and by listing layout and string data. Capture each state with the inputs that reached it. `docs/observe/ui.md` has the fields and how to rebuild data-driven and coded interfaces. ## 6. Build the object model and its entry points Give the original and the port the same model, named with the original's names: the interface tree, the world's entities and fields, and the session (loop, frame, pause, seed). Add entry points as the work needs them: query, input (as the player), commands, time (step, run *n*), state in and out, trace, capture. Entry points only read or act as a player would; tests, scripts and agents use nothing else. `docs/observe/object-model.md` has the rules and examples from three ports. ## Evidence to produce - `docs/FINDINGS.md`: the kind of game, the inputs table, the screen inventory. - Capture scripts in the project (not one-off commands), so every capture can be repeated. - Labels (`labels/`) for every variable and function the captures name. ## Pitfalls - A capture without its inputs cannot be repeated or trusted. Record the inputs first. - A compressed video stream is not pixel-exact: compare layout. Use blurred image differences with a threshold. - The clock and the random seed are inputs too. A second run that differs usually read one. - A live online service belongs to someone else. Its terms usually forbid testing against it. Breaking copy protection is restricted in many countries. If the user's plan touches either, say so once with the source (`docs/LEGAL.md`, `docs/preservation/law/`), then follow the user's decision. --- # Investigate with REA > **Skill `re-rea`.** When and how to use REA (Reverse Engineer Anything, the rea-agents MCP server) alongside GameFrame - quick evidence-backed inspection of binaries, archives, Android/iOS/.NET/Electron/browser targets, focused decompilation and call graphs through Ghidra, and runtime observation. Use for targets the text export does not cover, for a fast first answer about one function, or when a claim needs a citable Evidence record. REA (https://github.com/morluto/rea) is an MCP server that gives the agent reverse-engineering tools and records every result as Evidence with an ID, coverage and limits. Its own skill, `reverse-engineer-anything`, documents each tool in detail: install it with REA's setup (`re-setup`) and read it before a large REA investigation. This skill says how REA fits into the playbook's loop. ## REA or the text export? | Use REA (`mcp__rea__*`) when | Use GameFrame's Ghidra export (`re-ghidra`) when | |---|---| | The target is not a plain native binary: an APK/IPA, a .NET assembly, an Electron app or ASAR, a browser page, firmware, an archive | You need to search the whole program as text, many times (grep is instant) | | You want a quick, cited answer about one function: `open_binary` then `analyze_function`, `procedure_callers`, `xrefs` | You trace a system across hundreds of functions over several sessions | | You need a call path between two functions (`build_call_path`, `trace_call_path`) | You need `decomp_query.py` with your labels applied | | You need runtime evidence from a running browser or Electron app (`observe_web_session`, `capture_browser_scenario`) | You emulate functions (`re-emulate`): REA does not replace the Unicorn harness | | You compare two versions of an application (`compare_application_versions`, `find_changed_behavior`) | | | A finding must carry a citable Evidence ID in a report | | They agree because both read the same binary through Ghidra; addresses match. Use both: REA for targeted questions and evidence, the export for broad search. ## The workflow 1. **Route by target** (from REA's skill): a native EXE/DLL → `open_binary` with the path; an archive or package → `open_binary` then `inspect_artifact`; .NET → `inspect_managed_artifact`; Android → `inspect_android_package`; extracted JavaScript or ASAR → `analyze_javascript_application`; Windows resources and icons → `inspect_pe_resources`. 2. **Start with a summary** (`binary_overview` when available), then ask focused questions: `search_strings`, `search_procedures`, `analyze_function`, `procedure_callers`, `procedure_callees`, `xrefs`, `procedure_pseudo_code`. 3. **Name what you learn in the session**: `set_address_name` and `set_comment` keep labels in REA's database; also copy each label into `labels/names.tsv` so GameFrame's tools see it. 4. **Keep the Evidence IDs** in your notes; cite them in the finding's document next to the address ("observed: FUN_1400be770 writes +0x44 (REA evidence ev_...)"). 5. **Close** native sessions with `close_binary` when done. ## Rules that carry over - REA's evidence labels match ours: observations, inferences and unknowns stay separate; static analysis never proves run-time behaviour. - Do not repeat an identical REA call; page large results with `inspect_analysis_view` instead. - REA's runtime tools run the target: run them on the target the user chose, with their agreement. A game's online service belongs to someone else; if the user points REA at one, mention its terms once, then follow the user's decision. - Decompiled output from REA is still decompiled code: it stays out of tracked files. ## When REA is not connected No `mcp__rea__*` tools in the session means the MCP server is not registered or did not start. Follow `re-setup` section 3. Meanwhile, continue with GameFrame's own tools: Ghidra export, `decomp_query.py`, `ghidra-run.sh` and the emulator cover native binaries completely. --- # Decompile with Ghidra > **Skill `re-ghidra`.** Decompile a game's binaries with Ghidra in headless mode and work with the result as text - the full export (decomp.c, functions, strings, imports, exports), queries with decomp_query.py, one-off decompiles of functions Ghidra missed, vtable and reference listings, constants read from the binary, and checks against the disassembly. Use whenever you need pseudocode for a native binary, including 16-bit DOS (MZ) and NE programs. Ghidra turns machine code into C-like *pseudocode*. It has no original names: a function is `FUN_1400be770` (the function at address 0x1400be770), a field is `*(int *)(param_1 + 0x44)` (the int 0x44 bytes into the first argument). Pseudocode helps you read the code, but it can be wrong. Check anything exact against the disassembly or the emulator. ## 1. Export once Import the game's own binaries **together**, so calls between the EXE and its DLLs resolve: ```bash scripts/ghidra-export.sh target/Game.exe target/GameEngine.dll ``` If you have names for functions and globals (debug symbols, a map file, earlier labels), give them to the export: Ghidra names everything and creates the functions it missed. ```bash python3 tools/dos/tdinfo.py target/GAME.EXE labels > analysis/td-labels.tsv # DOS: Borland symbols scripts/ghidra-export.sh --labels analysis/td-labels.tsv target/GAME.EXE scripts/ghidra-run.sh GAME.EXE labels analysis/notes/labels.txt labels/names.tsv # an existing project ``` It writes, per binary, into `analysis/export//` (ignored by git): | File | Contents | |---|---| | `decomp.c` | Every function's pseudocode, each headed `// ====
` | | `functions.tsv` | address, size in bytes, name, `thunk` for thunks | | `strings.txt` | address and value of every defined string | | `imports.txt`, `exports.txt` | library!function; address and name of each export | Time: about a minute for a small program, 10 to 30 minutes for a large game. Run it in the background and keep working. It needs memory: `MAXMEM=16G` for games over 50 MB of code. ## 2. Query the export ```bash T=tools/triage/decomp_query.py; P=.venv/bin/python $P $T stats # functions, decompiled, labelled share $P $T fn 1400be770 # one function, with your labels applied $P $T callers FUN_1400be770 # who calls it (or refers to it through a table) $P $T callees 1400be770 # what it calls, labelled and largest first $P $T find 'Saves/' # functions whose pseudocode contains a string or constant $P $T strings savegame # strings containing a word, and the functions that use them $P $T writers 0x44 # functions that store to +0x44 (who sets this field?) $P $T biggest 40 # the largest unlabelled functions (where the work is) ``` Plain `grep` and `awk` on `decomp.c` work too: `awk '/^\/\/ ==== 1400be770 /{p=1;print;next} /^\/\/ ==== /{p=0} p' analysis/export/Game.exe/decomp.c` prints one function. Every time you identify a function, add `addresslabelevidence` to `labels/names.tsv`; `decomp_query.py` and `tools/pe/vtable.py` show labels everywhere from then on. Fields go in `labels/fields.tsv`. This shared vocabulary is what makes later sessions fast. ## 3. One-off work in the analysed project Reuse the project the export made (no re-analysis, about a minute of start-up, so batch): ```bash scripts/ghidra-run.sh Game.exe decompile analysis/notes/ai.txt 0x1400be770 0x1400be680 scripts/ghidra-run.sh Game.exe named analysis/notes/rand.txt Random Rand scripts/ghidra-run.sh Game.exe refs analysis/notes/refs.txt 0x140178000 scripts/ghidra-run.sh Game.exe vtable analysis/notes/vt.txt Citizen 0x140165120 40 ``` - `decompile` also **creates** functions Ghidra missed: callbacks reached only through tables of pointers are often not functions in the bulk export. - `named` searches by name: best for a DLL that exports C++ names (`Random`, `Serialize`). - `refs` lists every reference to an address (a global, a table, a function) with the referring function decompiled: the fastest way to find who uses a constant or a singleton. - `vtable` lists a table of function pointers. `tools/pe/vtable.py BINARY 0xTABLE --names analysis/export/BINARY/functions.tsv` does the same without Ghidra. Ghidra locks a project: run one job per project at a time. ## 4. Constants and data Pseudocode often shows a float constant as a global (`DAT_14016a2b0`). Read the real value: ```bash .venv/bin/python tools/pe/readdata.py target/Game.exe f32 DAT_14016a2b0 .venv/bin/python tools/pe/readdata.py target/Game.exe f32 0x14016a2c0 --count 12 # a table .venv/bin/python tools/pe/readdata.py target/Game.exe cstr 0x140180010 ``` ## 5. Check against the disassembly Before you reimplement arithmetic, loops or bit tricks, read the instructions; Ghidra drops loop bounds, mis-types floats, merges stack slots and loses register arguments: ```bash objdump -d --no-show-raw-insn --start-address=0x1400be680 --stop-address=0x1400be770 \ target/Game.exe > analysis/notes/be680.s ``` What to look for: `cvttss2si` (float to int, truncating) against `cvtss2si` (rounding by the current mode); `minss`/`maxss` operand order (it decides which value wins with NaN); `movss` (32-bit float) against `movsd` (64-bit double); x87 instructions (`fld`, `fstp`) in 32-bit games, which compute in 80-bit precision until stored; `imul`/`idiv` (signed) against `mul`/`div` (unsigned); `sar` (keeps the sign) against `shr`. ## Pitfalls - **Inlining.** A small function may exist only inside its callers. If `callers` finds none but the code clearly runs, search for its body's distinctive constants instead. - **Magic numbers as integers.** A check for the bytes "SMPL" compiles to `== 0x4c504d53` (little-endian). Search for the hex value as well as the text. - **Decimal offsets.** Ghidra prints some offsets in decimal: `param_1 + 100` is `+0x64`. `writers` matches both. - **Thunks.** A `FUN_` that only jumps elsewhere is a thunk: follow it (functions.tsv marks them). - **Wrong signatures.** Ghidra guesses argument counts and types; a call through a register it did not track shows fewer arguments than the callee uses. The callee's own pseudocode and the disassembly at the call site settle it. - **32-bit games** pass arguments on the stack (cdecl, stdcall) and `this` in ecx (thiscall); see `docs/ABI.md`. - **Paths with a dot folder.** Ghidra refuses a project path with a folder that starts with "." (a git worktree in `.claude/worktrees/`). The scripts handle it with a link in `$TMPDIR` (`ghidra_project_dir` in `scripts/lib.sh`); call `analyzeHeadless` yourself the same way. ## 16-bit programs (DOS MZ, NE) The same export works. Addresses are `seg:off`: NE segment N is `1000 + 8 * (N - 1)` (`1000:873a`, `FUN_1000_873a`, `DAT_1048_12de`); an MZ image starts at `1000:0000`. `decomp_query.py` accepts these addresses and labels. Pitfalls (details in docs/DOS.md): - The export passes `-loader-libraryLoadDepth 0`: Ghidra's library search crashed on an NE program whose imported module (`RTM`) had a plain DOS file of that name next to it. - Ghidra does not know `INT 21h AH=4Ch` or Pascal's `Halt` never return: the next function is glued onto the caller. Create the function at the right address (labels, or `decompile`). - Pascal procedures pop their own arguments and take them left to right: Ghidra's `param_1` is the *last* source argument. Count from `[bp+6]` upwards, the first argument highest. - Pascal strings (length byte first) are missing from `strings.txt`: use `tools/dos/dosinfo.py BINARY --pstrings 6` and `tools/dos/readseg.py BINARY pstr ADDR`. - Disassembling the file directly shows far calls as `lcall 0:ffff` (unrelocated). Use `tools/dos/nedis.py BINARY SEG:OFF LEN`: relocations applied, calls and globals named. - A Pascal string is referenced by its length byte (one before the text). A procedure's strings sit just before its code. A constant equal to a DS offset may print as `&global`. - `INT 34h`-`3Dh` in Borland code are floating-point instructions (the FPU emulator). - Constants: `tools/dos/readseg.py BINARY TYPE ADDR` reads typed values at `seg:off` (`u16`, `i32`, `ptr`, `real48` for Turbo Pascal's 6-byte Real, `pstr`, `hex`). --- # Trace a behaviour > **Skill `re-trace`.** Find and understand how the original game implements something - from a string, a resource name, an import, an export, a C++ class name or a known caller to the function, its inputs, the fields it reads and writes, the constants it uses and the loop that calls it - and write the result down as evidence. Use for any "how does the game do X?" question before reimplementing or documenting it. Tracing means following the program from something concrete to the code you need, and leaving a written trail. Static reading only: label conclusions *observed*, *inferred* or *unknown* (`re-start`). When the exact numbers matter, finish with `re-emulate`. ## 0. Has it been done? ```bash grep -rn "1400be770\|SaveGame" docs/ labels/ | head ``` ## 1. Find an entry point For a DOS game, look for debug symbols first (`tools/dos/tdinfo.py BINARY summary`): with them, the entry point is a name (`tdinfo.py BINARY procs enemy`), with its parameters, locals and the source line of each address (`tdinfo.py BINARY lines UNIT.PAS`). Work outward from something you can see. In rough order of usefulness: 1. **Strings.** File paths (`"Saves/"`, `"data/units.xml"`), format strings (`"%d citizens"`), UI text, error messages, debug and log text, script function names. `decomp_query.py strings WORD` lists them with the functions that use them. Developers leave log messages that name the function they are in. 2. **Exports with real names.** An engine DLL that exports C++ symbols (`?Serialize@Stream@IO@@...`) names hundreds of functions for free: `re-ghidra` `named`. 3. **Imports.** The OS and library calls show what a function does: `CreateFileW`/`ReadFile` (loading), `QueryPerformanceCounter` (timing), `XInputGetState` (input), `send`/`recv` (network), `D3D11CreateDevice` (rendering). Find their callers. 4. **RTTI.** MSVC binaries with RTTI contain `.?AVClassName@@` strings; each points to a type descriptor, which leads to the class's vtable and so to all its methods. 5. **Type registries and hashes.** Engines often register classes or resources under a hash of their name (CRC32, FNV-1a, murmur). If a table pairs constants with factories, hash candidate names and look the constants up. 6. **Data.** A value you can see in the game (a price, a duration) may appear as a constant (`readdata.py`) or in a data file (`re-format`). Its readers lead to the rules. 7. **The main loop.** From `WinMain`/`main` (or the entry point) find the per-frame update; the systems hang off it in a fixed order, which you will need anyway (see `docs/GAME-PATTERNS.md`). ## 2. Read the function - Note its inputs: what `param_1` is (often `this`), the globals it reads (`DAT_...` pointers to singletons), and the callees that touch the OS, threads, files or the GPU. - Name what you understand: a row in `labels/names.tsv` for the function and its callees, a row in `labels/fields.tsv` per field (`Citizen 0x44 hunger f32 written by FUN_1400be770`). - Follow the callers up until you reach a loop, a vtable slot or an event handler: a code path is not proof that it runs. ## 3. Reconstruct structures - **Serializers first.** A `Save`/`Load`/`Serialize` function reads or writes every field in order with its type: the cleanest field list you will get. - **Who writes a field:** `decomp_query.py writers 0x44`, then skip constructors and copies. - **Nested offsets** (`array + i * 0x1f0 + 0x90`): find getters that return sub-objects, subtract, and continue inside the sub-object. - **vtables:** a constructor's first store is `*this = &vtable`. List it (`re-ghidra` `vtable`); slot offsets are what call sites use: `(**(code **)(*obj + 0x18))(obj)` calls slot +0x18. Base classes share early slots; compare tables to find the hierarchy. - **Which argument is which:** a call's name does not tell you its argument order. Read the callee before trusting a guess written in a document. ## 4. Constants Read every `DAT_` constant and table the function uses with `tools/pe/readdata.py`, and record each with its address. Never round or "tidy" them: `0.33333331` is not `1/3` in a 32-bit float simulation. ## 5. Write it down (`re-land`) A topic document in `docs/` (extend one if it exists): an opening paragraph (what, which binary and version, which evidence), then prose and tables keyed by address and offset: function → role, fields, constants, the call order, and **Open questions**. Never paste pseudocode or disassembly: describe it. Update `docs/CHECKLIST.md`. ## Pitfalls - Pseudocode drops and mis-types things (see `re-ghidra` section 5). For arithmetic, read the instructions; for exact values, emulate. - A function may be inlined into each caller; a "missing" function is often inside its caller. - Functions reached only through tables are often absent from the bulk export: create them with `scripts/ghidra-run.sh BINARY decompile OUT 0xADDRESS`. - Two subsystems may share a helper with different meanings for its arguments. Name helpers by what they do, not by the first place you saw them. --- # Decode a data format > **Skill `re-format`.** Decode a game's data files - package/archive containers, compressed entries, resource and asset formats, save games - by finding the code that reads them, prototyping a decoder with tools/formats/reader.py, proving it against every file of its type with validate.py (all bytes consumed), then porting it. Use when a file type is not yet understood or a decoder fails on some files. A decoder is right only when it reads **every file** of its type to the **last byte**. One sample proves little: optional fields, versions and rare cases show up in a few files only. ## 1. Look at the bytes ```bash xxd target/data/units.dat | head -20 ``` Look for: a magic number at the start; counts and sizes (small integers that match the file's length or the number of something visible); offsets (values that point inside the file); length-prefixed strings (a small integer followed by that many printable bytes); repeated records of a fixed stride; float values (`0000803f` is 1.0, `0000003f` is 0.5). Measure entropy (`re-inventory` section 4): near 8.0 means compressed or encrypted. ## 2. Find the reader in the code Guessing a format from bytes alone is slow and fragile. Find the code that reads it: - the file's name or extension in the strings (`decomp_query.py strings .dat`), then the function that opens it, then the function that parses the opened bytes; - the magic number as an integer constant (`decomp_query.py find 0x4c504d53`); - for engines with a serializer pattern (`Serialize(Stream&)`), the class's serializer: it reads each field in order, with its type, and its version checks (`if (version > 2)`). Write down each read in order: offset, type, meaning, and the condition under which it is read. ### Measure the reads (DOS games) Run the original under DOSBox-X with the file log, then map the reads per file: ```bash scripts/dosbox-run.sh --fileio --time 30 target/game GAME.EXE python3 tools/dos/fileio_trace.py analysis/dosbox/dosbox.log --game target/game --file LEVELS.DAT ``` A program that reads a file field by field shows its record layout in the read sizes: `[2, 1, 4] x 120 (stride 7)` is 120 records of a word, a byte and a longint. A 1-byte read followed by a read of varying length is a Pascal string. This is *measured* evidence; name the run (program, inputs, time). With Borland debug symbols, `tools/dos/tdinfo.py BINARY types NAME` gives the record type the reader fills, field by field. ## 3. Containers and compression Most games pack files into an archive: a header, a table of entries (name or hash, offset, size, compressed size, flags), then the data. Find the table reader first; then each entry is an ordinary file. Common compression: zlib/deflate (`78 9c`), LZ4, zstd (`28 b5 2f fd`), LZO, LZF, LZMA, Oodle (Kraken/Mermaid: a proprietary library, look for `oo2core` DLLs). If the bytes are encrypted, the key and the algorithm are in the reader: trace them, do not brute-force. Extract entries into `derived/extracted/` (ignored by git), never into tracked folders. ## 4. Prototype the decoder in Python ```python # decoders/units.py def decode(r): # r is a tools/formats/reader.py Reader r.magic(b'UNIT') version = r.u32() units = [] for _ in range(r.u32()): u = {'id': r.u32(), 'name': r.pstr32(), 'speed': r.f32()} if version >= 2: u['armour'] = r.u16() units.append(u) return units ``` `reader.py` gives bounds-checked reads (`u8 u16 u32 i32 u64 f32 f64 bool bytes cstr pstr8 pstr32 wstr`), `magic()`, and `block(size)` for a length-prefixed section that must be consumed exactly. Then run it over every file: ```bash .venv/bin/python tools/formats/validate.py decoders/units.py 'derived/extracted/**/*.unit' --show 1 ``` Every file must report complete. A failure says where it stopped and shows the next bytes: a missed field, a wrong type or a version branch. `examples/sample-game/decode_sample.py` is a complete small example. ## 5. Port and test Port the decoder to the reimplementation's language as a plain reader with no dependencies, one function per type, documenting each field's meaning and naming the original reader function. Its test opens the real files from the player's game, **skips with a message when they are not there** (they are never committed), parses every file and spot-checks a known value. ## 6. Save games Saves are the strictest test of a format: they hold the whole state of a running game. Find the save function (strings: the save folder, the extension), trace it like a serializer, and decode real saves of different sizes and ages. Saves from an older game version may use older branches: keep them in the corpus. Later, a reimplementation that writes saves must produce files the original game loads. ## Pitfalls - Files in one folder can be different types: select by magic or type id, not by folder. - Two empty strings in a row look like a zero integer: confirm with a file that has text. - Booleans are often one byte; enums four; colours four floats or one packed integer: the reader code says which. - Alignment and padding: some formats align records to 4 or 16 bytes. - Never commit sample game files as test fixtures; generate synthetic ones if a test needs them. --- # Emulate original code for ground truth > **Skill `re-emulate`.** Run individual functions from a game's Windows x64 binaries (tools/emu/pe_emu.py) or 16-bit DOS/NE programs (tools/emu/dos_emu/) under the Unicorn emulator to capture exact ground truth for chosen inputs, and record the calls they make (random draws) with call_trace.py. Use for anything a reimplementation must match bit for bit - random number generators, maths, procedural generation, simulation steps, pathfinding, parsers - instead of trusting the pseudocode. Reading code tells you the shape of a computation; only running it tells you the exact result. In a simulation, one bit of difference in a float or one random draw out of order grows until the reimplementation's world no longer matches. The emulator runs a single original function, many times, with inputs you choose, without starting the game. ## How the harness works ```python import sys; sys.path.insert(0, 'tools/emu') from pe_emu import Emu, F32 emu = Emu('target/Game.exe', ['target/Engine.dll']) # loads, relocates, links imports by name ``` - Every module loads at its preferred base (relocated if two overlap). Imports between loaded modules are linked; any other import becomes a stub. Common C runtime and kernel32 calls have working stand-ins (`CRT_STUBS`: malloc, free, memset, memcpy, strlen, critical sections...). An import with no stand-in stops the run with its name: add one (`stubs={'Name': handler}`). - `emu.call(addr, *args)` uses the Windows x64 convention: integers and pointers in rcx, rdx, r8, r9, floats wrapped in `F32(...)` or `F64(...)` in xmm0-3, the rest on the stack. It returns rax; `call_f32` returns xmm0, `call_i32` a signed int. - Memory: `malloc(size)` (zeroed), `write(addr, bytes)`, `read(addr, n)`, `u8/u32/i32/u64`, `f32s(addr, n)`, `cstr(addr)`, `bytes_in(data)` (copy bytes in, get the address). - Addresses: `export(module, name)`, or `va(module, 0x1400be770)` for a Ghidra address (it follows a relocated module). - `hook_function(addr, name, handler)` replaces any function, original or imported, with Python: use it for callees that touch files, threads, the GPU, or that you want to observe. ## Steps 1. **Trace first** (`re-trace`): know the function's arguments, the layout of the objects it reads (offsets and types), the globals it needs (a singleton pointer at `DAT_...`), and its callees that must be stubbed. 2. **Build the inputs** in emulated memory: `malloc` each object, `struct.pack` the fields at their offsets, link pointers. Point globals at your objects with `write(va(...), pack('.py` in the project, modelled on `examples/sample-game/ground_truth.py`): a docstring with usage and outputs; a fresh `Emu` per independent case; many inputs (every cell of a small map, seeds 1 to 50, edge cases such as zero, negatives and borders); raw little-endian outputs into `derived/emu//` (ignored by git, because ground truth is derived from the game). 4. **Record the draws** when the function consumes the game's random number generator: ```python from call_trace import CallTrace trace = CallTrace(emu, {emu.va('engine.dll', 0x180012340): 'Rand', emu.va('engine.dll', 0x180012380): 'RandFloat'}, args=1) emu.call(target, ...) trace.write('derived/emu/ai/seed-1.trace') # "Rand(10)game.exe+0x9be55" per draw ``` Only outermost calls are logged, so a float draw that calls the integer draw counts once. 5. **Compare** the reimplementation's output with `re-verify` (`bitcompare.py`, `seqdiff.py`). Not exact? Emulate intermediate functions separately and compare stage by stage, until the first stage that differs is small enough to read instruction by instruction. ## Pitfalls - **Invalid memory access**: a pointer field you did not fill. Find which offset of which object from the reported code position and the disassembly there. - **Thread-local storage**: the harness maps a TEB at GS; code that uses `__declspec(thread)` variables also needs the TLS slot array (`TEB+0x58`) pointing at a block for the module. - **Exceptions**: SEH and C++ exceptions do not unwind in the emulator; inputs that make the original throw will stop the run. - **Long functions** (a whole map generator): pass `timeout_insns` and emulate them whole rather than porting blind; dump intermediate state with hooks to compare in stages. - **DllMain is not run**: if a function depends on globals a DLL sets up at load, initialise them yourself, or call the module's initialiser explicitly. - **32-bit games** (x86) need a variant of the harness: `UC_MODE_32`, arguments on the stack, `this` in ecx, a TEB at FS. `docs/ABI.md` lists the conventions. Linux ELF and macOS Mach-O binaries need a different loader; the calling convention is System V (rdi, rsi, rdx, rcx, r8, r9; floats in xmm0-7). - **Floating point state**: the emulator starts with the default MXCSR (round to nearest). If the game changes the rounding mode or enables flush-to-zero, set it the same way. ## 16-bit DOS and NE programs: dos_emu ```python import sys; sys.path.insert(0, 'tools/emu') from dos_emu import DosEmu, Long, FarPtr emu = DosEmu('target/GAME.EXE') # NE: 16-bit protected mode; MZ: real mode emu.call('1000:873a', 5, Long(100000)) # Ghidra's address or an export name; AX emu.call_long('RANDOM_LONG', 1000) # DX:AX as one 32-bit value p = emu.bytes_in(open('target/LEVEL.DAT', 'rb').read(512)); emu.call('LOADLEVEL', p, 512) emu.u16(FarPtr(0x1048, 0x12de)) # read a global afterwards emu.trace({'1040:0a12': 'Random'}, args=1); emu.write_trace('derived/rng.trace') ``` - Addresses are Ghidra's: NE segment N is selector `1000 + 8 * (N - 1)`, MZ loads at `1000`. The emulator uses the same numbers, so pseudocode addresses work unchanged. - Arguments go in the **Pascal** order by default (left to right, callee pops). An int is a word, `Long(v)` two words, `FarPtr`/(seg, off) a far pointer. `conv='cdecl'` for C code. - A Pascal `var` parameter is a far pointer pushed segment first: pass the variable's address, as in `emu.call('1008:2026', 1, 0x1048, 0xa2f4, 0x1048, 0xa2ee)` for `P(n: Byte; var A, B)`. When the real callers always pass the same globals, port it with a choice between them (player 1 or 2), not a pointer. - A procedure nested in another (it reads its parent's frame through `[bp + 4]`, the static link) cannot be called alone: call the parent with states that reach it. - DS = SS = the auto data segment, as in a Pascal program; override with `regs={'ds': ...}`. - An import or interrupt with no handler stops with its name and caller: add `stubs={'KERNEL.3': fn}` (handler returns the bytes to pop) or `interrupts={0x21: fn}`. - **Drawing routines** (DOS games write straight into video memory): give the routine a screen with `screen = emu.new_segment()`, put its selector in the global the code loads into ES (`VgaScreenSeg`), fill the screen with a sentinel byte (0xEE) so skipped transparent pixels show, set the globals, call, and compare all 64 KB with the port's output. Load the data blocks it reads (`megadata`, sprite banks) the same way, built by the port's own loader, so one test checks the loader's layout and the renderer together. - **Inline code inside a big procedure** (a main loop draws a layer itself): `emu.run_range('1000:cab1', '1000:ccac')` runs from one address until execution reaches the other. Pick a fragment that leaves the stack balanced and uses globals, not the enclosing procedure's locals (or set `bp` in `regs`). Near procedures end in `ret`: stop at its address. `run_range` starts on the start-up stack; to carry on a program that is running (the next frame), use `emu.run_to(address)`, which keeps every register. - **The whole program, headless**: `tools/emu/dos_env/` adds DOS files, the BIOS, a DPMI host, timer interrupts and a key queue, so a DOS game starts and runs in dos_emu, frame by frame, with its memory open to you (docs/DOS.md section 5b). When it stops on something missing, add that to dos_env with a test, not a workaround in the game project. To save the state before and after chosen fragments of the main loop, frame after frame, use `tools/emu/fragments.py` (re-verify, "Capturing fragments of a main loop"). - For a function that needs DOS, timers, VGA or sound, run the whole program in DOSBox-X (`scripts/dosbox-run.sh`) and read memory with the debugger (`tools/dos/dbx_control.py`): docs/DOS.md sections 6 and 7. --- # Reimplement, exactly > **Skill `re-port`.** Reimplement traced game behaviour in new code that behaves exactly like the original - mirroring its structure, numeric types and order of operations, random draw order, iteration order and integer semantics, citing the original function on every item and marking every intentional difference as a Deviation. Also how to shape the reimplementation (an exact core, commands, digests, a headless runner) and keep additions such as multiplayer or mod code apart from it. Use when writing the reimplementation of anything traced (re-trace) and measured (re-emulate). Precondition: the behaviour is traced and documented. Anything numeric has ground truth. If you find yourself inventing a rule while writing code, stop and trace it. ## Write new code Read the pseudocode to understand the behaviour, then write the new code from your notes, with clear names and the target language's own types and structure. By default, do not paste decompiled code into the new code base: it is the original author's work. It is also unreadable. This is the line between a reimplementation and a copy; keep it visible (see `docs/LEGAL.md`). If the user chooses otherwise, say this once and follow their decision. Before you write a sound chip, a codec or a language runtime yourself, check `docs/crates/`: GameFrame may already have a checked implementation (OPL2, WMA v2, the MSVC maths functions, Turbo Pascal's `Real`). Compare it with your game's version before you rely on it. ## Mirror the original's shape - One module or type per original class or system, named after it where the name is known (from exports, RTTI or strings) and after its role otherwise. - Keep the data flow: if the original builds a menu from a data file, build it from the same data file; do not hand-code what the game ships as data. - Keep constants with their source address, exactly as read: `0.33333331f`, not `1.0 / 3.0`. - Every reimplemented item names its original in a comment or doc comment, in one consistent form the tools can search, for example ``Original: `FUN_1400be680` ``. Mark guesses *inferred*. ## Build it so it can be checked Decide the shape of the reimplementation early: these are cheap on day one and expensive to add after a year. - **An exact core with nothing else in it.** Put everything that decides what happens (the world, its rules, the clock, the random generators, loading and writing saves) in one module or library that does not draw, play sound, open windows or use the network. It then runs headless, in tests, on a server and on every platform. Add a test that fails if the core starts to depend on a drawing, window, sound or network library. - **Every player action is a command.** Input becomes a small value ("place building X at Y", "set speed 3") that the core carries out at the exact point where the original reacts to the input, so the random draws stay in order. One command type gives you recording and replay, scripted tests and lockstep multiplayer from the same code. - **A digest of the state.** A function that hashes the core's whole state (or the bytes of a save) lets two runs, two builds or two computers compare themselves cheaply at every checkpoint, and find the first step where they differ. - **A headless runner.** A command-line mode that loads a save or starts a seeded game, runs a fixed number of updates or seconds with scripted input at fixed times, then prints digests or writes a save and a picture. Every whole-system check in `re-verify` is built on it. - **A query layer for tests.** A way to ask the running program what it shows (interface elements by name or text, the world's counts) and to press things by name. Tests that find a button by its name survive layout changes; tests that click pixel positions or wait fixed times do not. ## Additions the original does not have Most reimplementations add things: multiplayer, a server, mod support, a debug probe, a new camera, a browser version. Keep each one apart from the core: - it lives in its own files, behind a build option, and the program builds and plays exactly the same without it (a test builds with every addition off and compares results with the full build); - it reaches the core only through a few small, named entry points (commands, events after each step, read-only queries), never by editing the core's code paths; - a run-time switch (for example `--pure`) turns every addition off for one run, so a player can always get the original game; - anything that changes results is marked `Deviation:` and happens only when that addition is in use. **Mod code that changes rules** (scripts or plugins run inside the simulation) must give the same result on every computer, or replays and multiplayer break. Run it in a sandbox: integers only (no floating point), no access to files, the clock or the network, random numbers only from the game's own generator, a fixed instruction budget per call (a plugin that runs out stops at the same instruction everywhere), a fixed order between plugins, and a fingerprint of the loaded plugins that multiplayer players must share. A WebAssembly interpreter with fuel metering and floats switched off does all of this. ## Generated record types (Borland programs with debug symbols) `python3 tools/dos/tdinfo.py BINARY rust TYPE ...` writes Rust structs and readers for Pascal record types in the original's field order (with `rust-reader` for the `PascalReader` they use). Generate the code instead of copying it by hand. Keep the generated file unedited: regenerate it when the toolkit improves. Rust projects made by new-project.sh build into `build/`: `target/` holds the game. `cargo clean` would delete it. ## Exactness checklist These are the details that make a reimplementation drift, in order of how often they bite: 1. **Float precision.** Use the original's types: 32-bit floats stay 32-bit. Languages that compute in doubles by default (Python, JavaScript, Lua) need explicit rounding after every operation (`Math.fround`, `struct` round trip). 32-bit x87 games computed in 80-bit precision until a value was stored: match where it was stored. 2. **Order of operations.** Floating-point addition and multiplication are not associative: `(a + b) + c` differs from `a + (b + c)`. Keep the original's order, from the disassembly. Watch for fused multiply-add: compilers may fuse `a * b + c` on one side and not the other. 3. **Maths library.** `sin`, `cos`, `atan2`, `pow`, `exp` differ in the last bit between C runtimes. If the simulation depends on them, reimplement the original's runtime functions (emulate them for ground truth) instead of calling your platform's. 4. **Random draws.** Same generator, same seed, same number of draws, same order. A draw moved inside a condition, or skipped when a value is zero, changes everything after it. 5. **Iteration order.** Lists, arrays, hash maps: iterate in the original's order. Never let a hash map's order reach anything observable. 6. **Sorting.** Unstable sorts (C `qsort`) order equal elements in a way that depends on the algorithm; reproduce the original's algorithm when equal keys are possible. 7. **Integers.** Width, signedness, wrap-around on overflow, division rounding toward zero, arithmetic against logical shifts, float-to-int truncation (`cvttss2si`). 8. **Time.** Fixed step against variable step; integer ticks against float seconds; the exact step size and when the accumulated time is consumed. 9. **Uninitialised and stale state.** The original may read a field before writing it, or keep a value from the last frame. That is behaviour too. ## Deviations Any intentional difference (a platform difference, a fix the project decided on, an addition) gets a comment containing `Deviation:` and the reason; a piece left out gets `not ported`. `tools/triage/deviations.py --out docs/DEVIATIONS.md src/` lists them all in one ledger, and `--check` fails a test when the ledger is out of date. A deviation is a decision; an accidental difference is a bug. `tools/triage/coverage.py SOURCE_DIR` reads the `Original:` citations and says how much of the original's code (by count and by size) the reimplementation has ported from, with the largest functions nobody cites yet. ## Workflow 1. List the functions to reimplement, from the topic document. 2. Do the smallest end-to-end slice first, and test it against ground truth before the rest. 3. After that, port a whole subsystem at a time (all of a routine's callees too), then verify it with one capture and one test (`re-verify` section 0 says how deep). 4. Build and test often; commit each verified subsystem (`re-land`). `examples/sample-game/port.py` is a complete small example: a reimplementation that matches the original bit for bit, and a `--naive` mode that makes mistake 1 to show what the checks catch. --- # Verify against the original > **Skill `re-verify`.** Prove that a reimplementation matches the original - bit-exact parity tests against emulator ground truth (bitcompare.py), random-draw order (seqdiff.py), determinism of two identical runs with digests at checkpoints and on every platform, recordings and replays, round trips of real save files, picture comparison against the original game, comparison between two versions of the reimplementation, and test tiers. Use after every reimplemented slice and before committing it. Report only what you ran and looked at: test names with pass and skip counts, files compared, pictures opened. "It builds" is not verification. ## 0. How much to verify Match the effort to the risk. Every slice gets at least the first row. | Code | Check | |---|---| | Any ported routine | Recorded frames (a demo or a session) through the port, compared with the original's state after | | Arithmetic, float or fixed-point maths, random draws, physics | Also direct calls on random inputs (`re-emulate`), thousands of them | | Many branches, modes or record types that a recording misses | Also fuzz from real states (below) | | Menus, text, plain drawing | Recorded frames and pictures only; no fuzz | Run a big fuzz once when the slice is done, not after each change: a few dozen cases are enough while you work. ## 1. Bit-exact parity with ground truth Write the reimplementation's output in the same raw format as the emulator's (`re-emulate`), then: ```bash .venv/bin/python tools/verify/bitcompare.py f32 derived/emu/growth/seed-7.f32 out/growth/seed-7.f32 ``` It compares by bits (0.0 and -0.0 differ), prints the match count, the first mismatches and the largest difference in ULP (units in the last place). Reading the result: | Result | Usual cause | |---|---| | All match | Done for these inputs; widen the inputs | | Off by 1 to a few ULP from some point | Order of operations, a fused multiply-add, a maths library function, double against float | | Large differences from the first value | Wrong formula, wrong constant, wrong input layout | | Matches, then diverges for good | A random draw out of order, or state carried between steps | Turn this into a test in the reimplementation's test suite: it loads the ground-truth files, **skips with a clear message when `derived/` is missing** (other machines and CI have no game files), compares exactly, and on failure reports the first mismatching index and the count. For a DOS game, also compare against the whole original running in DOSBox-X: its exit code (`scripts/dosbox-run.sh`), its file reads (`--fileio`), or memory read with the debugger at a breakpoint (`tools/dos/dbx_control.py`). Fix the CPU speed (`--cycles N`) for any comparison that depends on timing. Recorded demos (a file of inputs the game replays) are the best end-to-end check: the reimplementation must replay them to the same frames. ### Reach the code a recording never reaches: fuzz from real states A demo or a recorded session only covers some branches (one game mode, one difficulty, a few event types). To cover the rest: 1. Capture real states at the start of the fragment, as the whole program runs (for a DOS game, memory images of the data and stack segments at a breakpoint). 2. For each case, take one state and change only the inputs the fragment reads: plant a random record (an event, a command), set random mode flags. Draw values the code tests for (sentinels such as -99, 0, boundaries, the ends of ranges) as often as random ones. 3. Load the changed state into the emulator and run the original fragment (`dos_emu` `run_range`). Heap data it reads goes into a new segment, with the pointers in the state set to it. Drop cases where the original never finishes, and say how many. 4. Save before and after, and replay them through the same test as the recorded cases. Report which inputs the passing cases covered (for example, the event types planted). This finds what reading misses: loops that start at 0 instead of 1, writes one past an array, branches only one mode takes. ### Capturing fragments of a main loop A capture saves the state when execution reaches a fragment's start and again at its end, frame after frame. For a DOS program, `tools/emu/fragments.py` does it in one headless run (dos_emu + dos_env): ```bash .venv/bin/python tools/emu/fragments.py target/game/GAME.EXE derived/fragments 2000 \ 'events=1000:2a14-1000:2a40,changed=0c20' 'draw=1000:2b00-1000:2b9e|1000:2bc4,every=5,max=200,heap=1' \ --frame-top 1000:2a10 --screen 0a40 --display 0a42 --heap 0a50,0a54 ``` - `--frame-top` is the top of the main loop (it counts frames). `--screen` and `--display` are the DS offsets of the words that hold the selectors of the screen being drawn and of the VGA display; `--heap` lists the words that hold heap blocks' selectors. `--ds` sets the data segment when it is not the program's start-up DS. `--start` starts at a routine instead of the entry; `--clock` and `--date` fix the time DOS gives. - A SPEC is `NAME=START-END`. Join several ENDs with `|`. `changed=OFFSET` keeps only runs that changed that DS word; `every=N` keeps one in N; `max=K` stops at K; `heap=1` saves the heap blocks. - It writes `derived/fragments/NAME/NNNNN.before.*` and `.after.*`: `.ds`, `.ss`, `.bp`, `.scr`, `.vga`, `.files` (handle, position, name) and `.heap` (selector + 64 KB each; `after` holds only the blocks that changed). The port's test loads `before`, runs the fragment and compares with `after`. - Two fragments cannot share a START. A fragment's END may be the next one's START. What a capture must hold: - **Every way out.** A loop body may end in more than one place (the normal jump back, a shortcut, a jump to the level's end). Stop at any of them. A capture that waits for one end that a frame never reaches runs on into later frames and misses every other fragment. - **The memory it reads.** Besides the data and stack segments and the screens, save the heap blocks the fragment reads or draws into (tile maps, off-screen buffers), as selector plus contents; for `after`, only the blocks that changed. - **Open files.** Save each open DOS handle's name and position. Code that reads input from a file (a recorded demo) then replays from the same place in the port. - **Pieces and the whole.** Capture each new piece of a loop body and the whole body too. The pieces say where a port goes wrong; the whole proves the wiring between them (state a piece's own test never saw, such as temporaries the caller keeps in globals). - **State an interrupt owns.** A timer or music interrupt runs while the frame waits for its time: its counters after a frame depend on the host, not on the frame. Leave them out of the comparison with the reason written down. A clock the frame reads (the BIOS tick count) is an input: give the port the value the original read. - **What it covered.** Before trusting a pass, count what the captured cases changed (the score, the armour, the enemies removed, the screen). A recording may never take a path; say which paths passed and which were never reached. ### Heap addresses and bytes that are not the game's - **Addresses from the allocator.** Where the original stores heap pointers in variables (a loaded bank's selector, copied into each enemy), the port cannot rebuild the allocator's history from a snapshot. Give the port a heap whose allocator a test can script: before the run, queue the addresses the original returned (read them from the `after` state); with nothing queued it picks its own. Every other byte stays comparable. - **Compare the named variables, list the rest.** The data segment also holds the runtime library's own state (its heap and file bookkeeping), padding, and blocks without symbols (a driver's data that an interrupt changes mid-frame). Build a mask of the bytes the port's variable model covers (store a model twice, over two different fills: the bytes that differ are not covered), compare only those, and write down what is left out and why. Never mask a byte you have not explained. ## 2. Random draw order ```bash .venv/bin/python tools/verify/seqdiff.py derived/emu/ai/seed-1.trace out/ai/seed-1.trace ``` Make the reimplementation log each draw as `labelfile:line`. seqdiff compares the labels (and arguments) and shows the first divergence with the original's call site on one side and your line on the other: usually it points straight at the bug. ## 3. Determinism Run the reimplementation twice with the same seed and inputs and compare everything it outputs (state dumps, saves, frames). Any difference means hidden nondeterminism: wall-clock time, hash map order, uninitialised memory, threads, or a random generator seeded from the clock. This check needs no original at all, so run it on every change. Compare **digests at checkpoints**, not only the end: print a hash of the whole state every N updates (`re-port`, "Build it so it can be checked"). The first checkpoint that differs narrows the search to N updates; halve N until you have the step, then compare the state field by field. **Every platform the reimplementation runs on** must give the same digests: native builds on different CPUs, a 32-bit build, the browser (WebAssembly). Run one seeded game on each and compare. Differences usually come from the platform's maths library, from fused multiply-add on one target only, or from a different iteration order of a hash map. **Recordings.** Record a game's commands with the digest at each checkpoint, then replay the recording and check every digest. A recording made today is a regression test for every later change. It is also how a lockstep multiplayer game is checked: two players are two replays of the same commands. ## 4. Real saves Load saves made by the original game (several sizes and ages) and check every value you decode; write them back and compare bytes when the reimplementation writes saves. Continuing a real save for a while in the reimplementation, then comparing with the original continuing the same save, is the strongest whole-system test you have. ## 5. Pictures To compare rendering or interface with the original, capture the same view in both (same save, camera, time, resolution) and measure the difference with an image tool (mean error, the share of differing pixels, where they are), not by eye. A heat map of differences shows which part is wrong. If the original only runs on another machine, stream it (Steam Remote Play, Parsec) and capture there. Make the reimplementation's pictures with its headless runner (a save, scripted input at fixed times, a capture at a fixed time), never by hand, so the same picture can be made again after every change. The same runner makes the screenshots and short videos for documentation. ## 6. Version against version When you optimise or restructure the reimplementation, the results must not change: run fixed scenarios with the old and the new build, compare saves and frames, and also run the old build against itself to know what "identical" looks like on this machine. Any difference needs an exact cause before the change goes in. **Frames that differ while the state does not.** GPU drivers are not always deterministic: a large scene can differ by a level or two on a few pixels between two runs of the same build. When saves are identical and only a frame differs slightly, run that scenario again a few times with both builds. If each build also differs from itself, it is the driver; record the case (how often, how large) so the next person does not chase it. If the difference repeats every time, it is real. ## 7. Test tiers Group the checks by cost and run them by tier: a quick tier on every change (unit tests, formats, a few headless scenarios against recorded baselines), a standard tier before a commit that touches the game (determinism, pictures, builds without additions), and a long tier before a release (replays, multiplayer, version against version). A baseline that changes is a change in behaviour: find the cause, and record a new baseline only when the change is intended. ## 8. The toolkit itself `tests/run.sh` proves GameFrame's tools against the sample game; run it after changing a tool. --- # Document, label, commit > **Skill `re-land`.** Finish a slice of reverse-engineering work - write findings into docs/ in the evidence style, add function and field labels, update the checklist, then commit only your own files (never game material or decompiled code) and push, safely alongside other sessions. Use at the end of every trace, format, emulate, port or verify slice. Undocumented findings are re-derived by the next session, at full cost. Landing is part of the work. ## 1. Document - **Where:** extend the topic document if one exists (`grep -rln ADDRESS docs/`); otherwise create `docs/.md` and link it from `docs/FINDINGS.md`. - **Style:** - an opening paragraph: what it covers, which binary and version, which kinds of evidence (static reading of Ghidra pseudocode, disassembly, emulation, decoded files, observation of the running game) and what was not checked; - prose and tables keyed by function address and field offset; - every claim *observed*, *measured* (with the script and inputs), *inferred* (with what would confirm it), or under **Open questions**; - no copied pseudocode, disassembly or data dumps: describe them. - plain English with no rhetorical patterns (colon reveals, "X, not Y" endings, matched pairs, two clauses of the same shape joined by ", and", "and the ..." chains): see `docs/working/writing.md`. - **Labels:** a row in `labels/names.tsv` for every function you identified (`1400be770Citizen::ChooseJobcalled from the AI tick, reads the job table`), a row in `labels/fields.tsv` for every field. - **Checklist:** tick or add the items in `docs/CHECKLIST.md` and update its date. - **Ledgers:** if the change adds or removes a `Deviation:` or `not ported` comment, regenerate the ledger (`tools/triage/deviations.py --out docs/DEVIATIONS.md SOURCE_DIR`) and commit it with the change. `tools/triage/coverage.py SOURCE_DIR` gives the progress figures for the checklist. ## 2. Check what you are about to commit ```bash git status --short git diff --stat ``` Never stage anything from `target/`, `analysis/` or `derived/`, game files (`.exe .dll .pak .sav` and the game's own extensions), decompiled code, disassembly, emulator output, extracted assets or screenshots of the original game. `.gitignore` covers the folders: never force-add. ## 3. Stage only your own work Other sessions may edit the repository at the same time: - stage files by name: `git add docs/AI.md labels/names.tsv` (never `git add -A`, `git add .`, `git commit -a`); - in a shared file that also holds someone else's edits, stage only your lines (build the version with only your change and write it to the index with `git hash-object -w` and `git update-index --cacheinfo`), or ask; - never revert or reformat changes you did not make. ### Mistakes that cost time - **A scripted edit that silently does nothing.** A search-and-replace whose text does not match the file changes nothing and reports nothing. In scripts, check that each replacement matched (count it, or assert it). Look at `git diff` before you commit. - **`git add` with a moved file.** After a file is moved, naming its old or new path may stage only part of the move. Then a commit that does not build goes in. After staging, check that nothing you meant to commit is still unstaged (`git status --short`, look for lines that do not start with a letter in the first column) and read `git diff --cached --stat`. - **Processes left running.** Games, servers and emulators started for a test keep running after it fails. Stop them before you finish. Give long runs a time limit. ## 4. Commit and push A subject in the imperative, at most 72 characters (`Trace the citizen job choice`), and a body that says what was traced, measured or reimplemented and how it was checked. Then: ```bash git show --stat HEAD # only your files? git pull --rebase && git push ``` If the rebase conflicts in files you did not touch, stop and report. ## 5. Ship When a version is ready for players, `docs/OUTPUTS.md` lists what to release and how: programs for each system built by CI from the tag, a browser version, a website. ## 6. Report Two or three lines: the commit, what landed, what is verified, and the next open item. --- # DOS sample A tiny 16-bit program for testing GameFrame's DOS tools. It is our own code: no game is involved. - `sample.s`: routines in the style of a Borland Pascal program (FAR, Pascal calling convention, DS = data segment): a Turbo-Pascal-style random generator, a loop that draws numbers, a checksum over a far pointer, a selector fixup, an imported call (`KERNEL.3`) and a DOS call (`INT 21h`), and `main_loop`: eight frames of a tiny main loop for `tools/emu/fragments.py` (DPMI blocks, the VGA display, a file read each frame). - `make_exe.py OUT`: assembles it with `zig cc` (LLVM, `.code16`) and links it with Python into `sample.exe` (NE, 16-bit protected mode), `sample-td.exe` (the same with Turbo Debugger symbols appended) and `sample-mz.exe` (real-mode MZ). `tests/run.sh` builds it and checks dosinfo, readseg, tdinfo, dos_emu, Ghidra and DOSBox-X against it. The first random draw from the built-in seed `0x1234` with range 100 is 23: the emulator, the program's exit code under DOSBox-X and the debugger channel must all agree. Pitfall found while writing it: LLVM's Intel syntax assembles `retf 2` in `.code16` as a 32-bit return (`66 CA 02 00`). The source uses AT&T syntax (`lretw $2`). --- # The sample game A tiny Windows x64 game, written in C for GameFrame, to practise on and to test the tools. It has the parts a real game has: an engine DLL with a random number generator and an allocator, a fixed-step simulation, a constant table, an object with a table of functions (a vtable), and a data file loader. ```bash ./build.sh # build/game.exe, build/engine.dll, build/sample.dat (needs zig) ../../.venv/bin/python ground_truth.py # emulate the original: build/truth/seed-N.f32 and .trace ../../.venv/bin/python port.py # the reimplementation: build/port/ ../../.venv/bin/python ../../tools/verify/bitcompare.py f32 build/truth/seed-1.f32 build/port/seed-1.f32 ../../.venv/bin/python port.py --naive build/naive # doubles instead of floats: watch the checks fail ``` ![bitcompare.py and seqdiff.py on this sample. The port matches the original in 600 of 600 values, and its 746 random draws are identical. The naive port matches only 7 values.](https://github.com/tanepiper/game-frame/blob/main/docs/assets/captures/bitcompare.svg) Try the whole method on it as an exercise: run `scripts/ghidra-export.sh` on the two binaries, find `simulate` without looking at `game.c`, label what you find, read the growth table with `tools/pe/readdata.py`, and write your own port. The compiler inlined `tree_new` and the vtable calls into `simulate`, and the data loader compares its magic as one integer: both are lessons from real games. | File | What | |---|---| | `engine.c`, `game.c` | The original's source (in a real project you never have this) | | `build.sh`, `make_data.py` | Build for Windows x64 from any host with `zig cc` | | `ground_truth.py` | Runs `simulate` under the emulator for 20 seeds, with every random draw traced | | `port.py` | A reimplementation that matches bit for bit; `--naive` shows a common mistake | | `decode_sample.py` | A decoder for the data file, for `tools/formats/validate.py` |