Status: active Last updated: 2026-10-22 Summary: a byte-matching decompilation of Battle Engine Aquila's PC game code, the tools that judge the match, and the knowledge behind it; bring your own game and compiler.
A byte-matching decompilation of Battle Engine Aquila (Lost Toys, 2003) against the retail PC executable:
BEA.exe, SHA-256 74154bfae14ddc8ecb87a0766f5bc381c7b7f1ab334ed7a753040eda1e1e7750. The C++ in src/ starts
from Stuart Gillam's GPL release of the game's source (stuart73/Onslaught)
and is completed, function by function, until Microsoft Visual C++ 6.0 SP5 compiles each game function to exactly the
bytes the retail executable holds at its address.
From this tree, the check matches 5,449 of the retail game's 5,482 functions (1,359,136 of 1,367,287 bytes), and every function this tree annotates matches. The other 33 are defined in four files of Microsoft's DirectX SDK sample code that this tree leaves out (see "What is left out"); docs/SCORE.md lists them under "Not in this tree". The record of the check that measured this is in docs/check/.
- onslaught-ce: Onslaught CE, the native Linux and Windows port built from this decompilation; play the game here.
- onslaught-godot: the Godot rebuild of the game.
- onslaught-preservation: documentation of the game's formats, saves, mechanics, levels and engine.
- Onslaught-Career-Editor: the companion app for careers, saves, settings and lore (to be renamed onslaught-companion).
- stuart73/Onslaught: Stuart Gillam's GPL release of the game's source,
which
src/starts from.
| Path | What it holds |
|---|---|
src/ |
the game's C++: Stuart's release, the PC build's changes, and the reconstructed rest. Each function carries a // FUNCTION: 0xADDRESS annotation naming its retail address. |
linklib/ |
the link's provisional, link-only pieces, labelled as such; never part of the score |
config/ |
the compiler flags, the SHA-256 pins of every input, the function table, the link order (config/README.md) |
tools/ |
the matching check, the linker and link comparison, the whole-executable inventory and data tools (tools/README.md) |
notes/ |
the matching knowledge: the toolchain, compiler behaviour, techniques, divergences from Stuart's source and the link (notes/README.md) |
docs/SCORE.md |
the score, the matched functions and the functions this tree leaves out, rewritten by tools/decomp.py score |
docs/check/ |
the check this tree's score comes from: its log and its matched-symbol table (docs/check/README.md) |
THIRD-PARTY.md |
the third-party material in src/, and what is left out |
- It is source that compiles to the retail bytes. A function "matches" when its whole compiled section equals the
retail bytes at its address, relocation fields aside, and every relocation lands where the rest of the evidence says
(
tools/decomp.py's docstring gives the rules). - It is not the original source text and not a clean-room implementation. Where the source takes a form only the
compiler's output justifies, it says so in a
STAND-IN,RECONSTRUCTEDorCONCESSIONcomment with the evidence. One function matches through a build concession, bytes written into its compiled object after compiling (config/object-patches.tsv). - It contains no part of the game: no executable, data, text dump or byte listing beyond short signatures. The tools
read your own copy of the game at run time and check it by its hash first. The PC build's hard-coded strings
(
src/PCText.cpp, the credits and front-end text) are in the source because the match needs them. - It contains no part of the toolchain: no Visual C++ binaries, headers or libraries and no DirectX SDK binaries, headers or libraries, only the SHA-256 pins that say which ones the match needs.
- Of Microsoft's DirectX sample code, it keeps exactly one part:
src/d3dapp.cppandsrc/d3dapp.h, the sample application framework as Stuart Gillam's GPL release of the game's source includes it (their header keeps Microsoft's 1998-2000 notice). They are carried because his release includes them; Microsoft has not licensed them under the GPL, as THIRD-PARTY.md and REUSE.toml record. Every other sample file the game linked is left out (below), and so is the SDK's own sample framework, whose headers the build reads from your copy of the SDK.
src/wavread.cpp,src/dxutil.cpp,src/d3dutil.cppandsrc/DXFont.cppare Microsoft's DirectX SDK sample code as the game changed it. Stuart's release does not include them, so they are not published. Their 33 functions are the difference between 5,449 and 5,482.src/WavRead.h, the sample's header, is replaced by a declaration of the same interface written for this project (GPL-3.0), so the sound code that includes it compiles unchanged.src/splinebasis.c, an unreferenced 872-byte data block typed out from the retail executable, has no functions and matters only to the link's data layout; it is left out.config/link-objects.txtlists all five of those files in retail's link order, each under a comment line that says it is not in this tree.config/objects.tsvhas a row only for an object whose code names its own source file (a__FILE__string), so of the five it listswavread.cppandDXFont.cpp, each annotated in itsnotecolumn. The check skips a source that is not present.- The link (
tools/link.py) needs further inputs that are not freely available, among them the prebuilt NvTriStrip library retail linked and import libraries made from the game's DLLs (config/link-inputs.sha256pins them all). Reproducing the linked executable is therefore not yet possible from this tree and public inputs alone; the function match is. - Per-function notes (what each retail function does, its callers and data) are not in this first release. They will follow once they have been rewritten and reviewed one by one.
You need, all from your own copies:
- The retail executable:
BEA.exewith SHA-25674154bfa…(above), and the game folder it came from (two mission-script files are compiled in;config/game-data.sha256pins them). - Visual C++ 6.0 SP5 Professional: CL 12.00.8804 driving C1 12.00.8867, C1XX 12.00.8964 and C2 12.00.8966 (the
c2.dllSP5 installs frommsvcep.dll), LINK 6.00.8447. The binaries must matchconfig/toolchain.sha256, and theVC98/Includetree the manifestconfig/vc6-include.sha256(993 files). - The DirectX 9.0 SDK (
dx9sdk.exe, SHA-2563825dec8…): itsInclude/folder (config/dx9sdk-include.sha256) and its sample framework'sSamples/C++/Common/Include/(config/dx9samples-include.sha256). - Wine able to run 32-bit programs, Python 3, and GNU binutils'
objdump(fordiffanddis).
The match depends on the Windows paths at which the compiler sees itself and the SDK: the precompiled header records
the paths of the headers it read, and code compiled with a header made under other paths differs
(CHud::RenderWorldTargetSprites by 672 bytes). The tools therefore never run the compiler from where your copies
are. Inside the Wine prefix they link
C:\Program Files\Microsoft Visual Studioto your Visual C++ 6 install (BEA_VC6),C:\DXSDK\IncludeandC:\DXSDK\Samples\C++\Common\Includeto your two SDK folders,C:\dev\ONSLAUGHT2to this checkout'ssrc/(the retail build compiled from there: its__FILE__strings say so),
and run C:\Program Files\Microsoft Visual Studio\VC98\Bin\CL.EXE. These are VC6's and the SDK's own default install
folders. The score in docs/SCORE.md was measured at exactly these paths, with the compiler and SDK
folders anywhere on the Linux side. BEA_VC6_AT and BEA_DXSDK_AT change the first two; the match is measured only
at the defaults. The precompiled header (build/pch/, made from src/common.h on first use) is keyed on the paths, so
changing them makes a new one.
The tools read every input location from an environment variable and have no built-in path for any of them: an unset input stops a command with the variable's name. The only default location is the Wine prefix's (below). These are all the variables the tools read:
| Variable | What it names |
|---|---|
BEA_PRISTINE |
required: your retail BEA.exe (read only) |
BEA_VC6 |
required: the Visual C++ 6 install, the folder holding VC98/, Common/MSDev98/ and os/system/ |
BEA_DX9_INCLUDE |
required: the DirectX 9.0 SDK's Include folder |
BEA_DX9_SAMPLES_INCLUDE |
required: the SDK's Samples/C++/Common/Include folder |
BEA_GAME |
the game folder holding data/MissionScripts/ (default: the folder holding BEA_PRISTINE) |
BEA_WINEPREFIX |
the Wine prefix the compiler runs in (recommended; create it once, below) |
BEA_WINE_BASE |
used only when BEA_WINEPREFIX is unset: a base prefix, default $XDG_CACHE_HOME/onslaught-decomp/wine (~/.cache/onslaught-decomp/wine without XDG_CACHE_HOME). A Git clone compiles in the base prefix itself; any other copy of the tree compiles in a copy of it named after the copy's folder, made on first use |
BEA_VC6_AT, BEA_DXSDK_AT |
optional: the Windows paths the compiler and the SDK are linked at (defaults above); - runs the compiler from your folders' own location through Wine's Z: drive, which makes the code depend on where they are |
BEA_LAB |
only for tools/link.py: the folder of the link's further inputs (the paths config/link-inputs.sha256 marks lab are relative to it). The check does not read it |
BEA_TMP |
optional, for tools/icfsim.py: where it keeps its scratch files (default the system's temporary folder) |
BEA_MAIN |
optional, for tools/equiv.py: the checkout whose build/matched.tsv it reads for symbol evidence (default this one) |
BEA_NAME_TABLE |
optional, for tools/equiv.py: an address/name table for callees no source annotates (default config/functions.tsv) |
tools/decomp.py also imports an optional site_paths.py beside it, which this tree does not have and Git ignores: a
Python file you may add whose attributes stand in for unset variables (PRISTINE, VC6, DX9_INCLUDE,
DX9_SAMPLES_INCLUDE, LAB, WINE_BASE, NAME_TABLE, VC6_AT, DXSDK_AT). While that file exists, VC6_AT and
DXSDK_AT default to - unless it sets them, which moves the compile off the paths the match is measured at.
Then:
export BEA_PRISTINE=/path/to/BEA.exe BEA_VC6=/path/to/vc6 \
BEA_DX9_INCLUDE=/path/to/DXSDK/Include BEA_DX9_SAMPLES_INCLUDE=/path/to/DXSDK/Samples/C++/Common/Include
export BEA_WINEPREFIX="$HOME/.cache/onslaught-decomp/wine"
WINEPREFIX="$BEA_WINEPREFIX" wineboot -i # once: create the prefix (64-bit Wine with 32-bit support works)
python3 tools/decomp.py check # compile every source with VC6 and compare it with retail
python3 tools/decomp.py check src/Hud.cpp -v # one file, every function's result
python3 tools/decomp.py diff 0x004a2a20 # side-by-side disassembly of one function against retail
python3 tools/decomp.py score # full check; rewrites docs/SCORE.md and build/matched.tsvEvery input is checked against its pin before anything compiles; a changed or missing file stops the tools with the
file's name. The tools write only under this checkout's ignored build/, src/data/ and local-data/ folders, in
the Wine prefix (including the links above) and in the temporary folder.
See CONTRIBUTING.md: changes come in as issues and pull requests, are checked against the full
byte comparison in the working tree, and arrive here with the next export. src/ files keep their line counts.
Code (src/, linklib/, config/, tools/): GPL-3.0-only (COPYING). Stuart Gillam's source release
ships the GPL version 3 text and never offers "any later version", so the source seeded from it, and this tree as a
whole, is GPL version 3 only. Notes and documentation: CC BY-SA 4.0; notes that quote Stuart's source keep its GPL
alongside. REUSE.toml maps every path, and THIRD-PARTY.md names the third-party
material. Battle Engine Aquila and its data belong to their owners; this repository contains none of the game's
files.