Skip to content

Case studies ​

Five projects built with GameFrame, of five different kinds of game, on four different platforms. Each took a different route. All five aim for the same results as the original for the same inputs.

ProjectGameFamilyPlatformRoute
Un-BanishedBanished (2014)City buildingWindows x64, C++Port from the decompile, check each part against the original run in an emulator, then whole runs and saves
Tyrian 2026Tyrian 2000 (1999)Arcade actionDOS, 16-bit Borland PascalNames from debug symbols; the main loop ported in fragments, each checked from captured states
FrontierFrontier: Elite II (1993)Space simulationDOS, assembly and C, overlaysRun the original inside the port, replace it routine by routine
PODPOD: Planet of Death (1997)RacingWindows 95, Watcom C, DirectDraw, five builds for CPUs and 3D cardsFormats with writers first; the original under Wine, then in an emulated PC; races recorded from memory; the rules ported before the physics
HardwarHardwar (1998)Vehicle simulation over an economyWindows 95, Watcom C, four renderers, one resource packThe story read first for what each system is for; a viewer and reader crates for every format before the game; the foundations traced by parallel sessions

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.

NeedUn-BanishedTyrian 2026FrontierIn GameFrame
Decompile and searchGhidra export, decomp.pyGhidra export, labels from debug symbolsGhidra export, functions seeded from overlay tablesscripts/ghidra-export.sh, decomp_query.py, tdinfo.py
Run one original functionUnicorn (pe_emu.py)dos_emuthe project's 386 runtimepe_emu.py, dos_emu
Run the whole originalWindows PC streamed with Steam Remote Playdos_env headless; DOSBox-X with a debuggerthe project's runtime, in-process; DOSBox Stagingdos_env, dbx_control.py, Running the original
Name the original's statelabels files, RTTI, exported C++ namesgenerated Globals from debug symbolslabels, overlay tableslabels/, td_rust.py
Measure formatsreaders checked on every resource, the engine's type registryfileio_trace.py on DOSBox-X file logsMZ readerstools/formats/, fileio_trace.py
Drive the game by scriptthe probe (queries and clicks, in tests, headless runs and the browser)demo input per framethe runtime's inputA model to query
Compare statetests against emulator output; saves loaded and continueddata-segment images before and after each fragmentregisters and memory after each routinebitcompare.py
Compare sequencesrandom-draw tracesrandom events by fuzzingcall_trace.py, seqdiff.py
Compare framesheadless captures and imgdiff against the original's streamscreen memory per frameframes over 60 secondsRunning the original
Compare versions of the porttools/compat: replays, saves and frames between buildsre-verify
Track what differsa deviations ledger generated from code commentsdeviations.py, coverage.py
Inspect assetsa viewer appa 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).
  • Read the story for what each system is for. The manual and the program's own text name the systems the designers cared about, and so which must be exact (Reading the story, from Hardwar).
  • Check in small pieces, then as a whole. Functions, then fragments or routines, then whole runs, demos and saves.

Code: Apache 2.0. Content: CC0 1.0. No game material: bring your own copy.