Skip to content

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.

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

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).
  • 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.