Fighters Codex
| Description | Reverse-engineering documentation for Jane's Fighters Anthology (1998) — format specifications, engine architecture notes, and modding guides, validated by byte-identical codecs. |
| Repository | https://github.com/jomkz/fighters-codex |
| Copyright | MIT licensed. The formats documented here were determined independently by reverse engineering for interoperability. Jane's Fighters Anthology and related titles are trademarks of their respective owners; no copyrighted game content is included. |
Table of Contents
Documentation¶
The primary output of this project is the reverse-engineering documentation: format specifications, engine architecture notes, and modding guides for Jane's Fighters Anthology and related titles, built up from binary analysis of the game's assets and executable. The toolkit software exists to exercise and validate that documentation — a working codec is the proof of understanding.
These pages are published as a searchable site at https://fighterscodex.com/, built from this same docs/ tree.
This is a work in progress and will be updated as more is discovered. If something here is wrong or needs more detail, please submit an issue.
Fighters Anthology¶
Reverse-engineering notes, format specifications, and modding guides for Jane's Fighters Anthology (1998).
| Document | Description |
|---|---|
| fa/start-here.md | Start here — the learning path from game concepts to internals, for players and modders |
| fa/README.md | FA knowledge base index |
| fa/architecture.md | Runtime environment, asset system, and subsystem architecture |
| fa/modding.md | Step-by-step modding recipes |
| fa/formats/README.md | All 44 file format specifications, categorized |
| fa/formats/STATUS.md | Generated per-format status matrix — spec completeness, codec direction, tests, fuzzing (CI-enforced) |
| fa/reconstruction.md | Generated FA.EXE reconstruction matrix — per-subsystem naming + documentation progress (CI-enforced) |
Outstanding research is tracked on the roadmap and the issue tracker — there is no standalone TODO file.
Toolkit¶
Reference documentation for the validation tools.
| Document | Description |
|---|---|
| roadmap.md | Phased roadmap to 1.0 — gates, epics, and the 1.0 definition |
| cli.md | Full CLI command reference |
| gui.md | fxs graphical editor feature reference |
| api.md | C++ library API reference |
| development.md | Building, IDE setup, and project structure |
| spec-authoring.md | Format-spec template, front-matter schema, and vocabularies (CI-enforced) |
| adr/README.md | Architecture decision records for the toolkit |
Start Here
Start Here¶
If you flew U.S. Navy Fighters, ATF, or Fighters Anthology in the 90s — and especially if you ever swapped a skin, edited a mission, or poked at the Pro editors — you already understand this game. What you're missing is the layer underneath: where each thing you remember actually lives on disk, what its bytes mean, and which part of the engine brings it to life. That's what this knowledge base documents, and this page is the map.
Everything here was recovered by reverse engineering and is validated by working code: for every format, the fx toolkit can decode and re-encode the real game files byte-identically — a round-trip is the proof that a spec is right, and the status matrix tracks exactly how far that proof extends for every format.
The 30-second map¶
Almost every file the game owns is packed into a handful of .LIB archives (in the
install directory and on the CDs). The engine unpacks entries through its resource
manager and hands each format to the subsystem that consumes it. Modding, at its core,
has always been: unpack a LIB, change a file, put it back. The
LIB spec makes the container itself editable; the rest of the specs
make the contents editable.
What you already know, in file terms¶
| In the game | On disk | Start with |
|---|---|---|
| An aircraft's 3D model (and its damage states) | .SH shape |
SH |
| Its flight model, thrust, hardpoints | .PT |
PT |
| The reference-section spec sheet | .INF |
INF |
| A mission — placements, waypoints, triggers | .M |
M |
| Briefing and debrief text | .MT |
MT |
| A campaign | .CAM |
CAM |
| A theater — terrain and the planning map | .T2 + .MM |
T2, MM |
| Skins, cockpit art, menu screens | .PIC + .PAL |
PIC, PAL |
| Sound effects | .11K / .8K / .5K |
11K |
| Music | .XMI + .MUS |
XMI, MUS |
| Intro movies and video briefings | .CB8 / .VDO |
CB8, VDO |
| The Pro editors' object definitions | the BRF text DSL family |
BRF |
| Enemy behavior | .AI scripts (+ compiled .BI) |
AI, BI |
The full catalog — all formats, grouped the same way — is the format reference.
How the game is put together¶
Three layers, each documented at its own depth:
- The archives.
.LIBcontainers hold the assets; the architecture page explains the runtime environment, the resource manager, and how the game finds and prefers files (which is why dropping a modified LIB into the install directory works). - The executable. The engine is documented as named subsystems — the game loop dispatches objects, physics, the AI interpreter, weapons, the renderer, the HUD, and the rest. Every function and every referenced global in the executable has been named; each subsystem page gives you the recovered symbols, struct layouts, and a flow diagram.
- The overlay binaries. Audio (WAIL32), the serial/modem comms stack (comms), and the other companion binaries the game ships get the same treatment.
How to read a spec page¶
Format specs follow one template (spec-authoring.md), so once you can read one, you can read all of them:
- Front matter states the spec's completeness and which codec/tests validate it — claims are checked in CI, so what a page says matches what the tools do.
- Facts carry a confidence vocabulary — confirmed (proven by round-trip or engine disassembly) vs inferred (consistent with observations, not yet proven).
- File Layout is the byte-level truth; Engine Notes connect the format to the subsystem that consumes it; Open Questions are tracked as issues, not left to rot.
Pick your track¶
The modder. You want to change the game.
Read the modding guide first — step-by-step recipes with fx commands.
Then go deeper per asset type: LIB → PIC/PAL
for art, PT and the BRF family for stats and loadouts,
M/MT for missions. The status matrix
tells you which formats round-trip cleanly today.
The tool writer. You want to build your own extractor, converter, or editor. Start with LIB (everything else is inside it), then the format specs you care about; the CLI reference and C++ API show how the validation toolkit is put together, and spec-authoring.md defines the vocabulary the specs use. Fixture and fuzzing conventions are in development.md.
The engine archaeologist. You want to know how it works. Architecture → game loop → objects, then follow the subsystem pages wherever curiosity leads. The reconstruction matrix shows the naming/documentation state of every subsystem across all binaries; symbols, globals, and structs are the recovered-fact registries behind them.
Where the ground truth lives¶
Specs are prose, but the facts underneath are mechanical: recovered names and types live in the symbol database, codecs prove the formats, and CI regenerates and checks the matrices so the docs can't drift from reality. When a page says confirmed, something executable stands behind it.
Fighters Anthology¶
Jane's Fighters Anthology (1998, Electronic Arts) is a combat flight simulator covering over 80 aircraft across multiple theaters. This directory contains reverse-engineering notes, format specifications, and modding guides built up from binary analysis of the game's assets and executable.
The RE effort has documented all 46 known binary and text formats and the game's runtime as a set of
named, diagrammed subsystems. Two reconstruction programs are complete: the game executable (all
20 subsystems named and documented, epic #209) and its overlay binaries (7 binaries — audio,
comms, and the MSAPI matchmaking client — epics #247 / #272). Per-format completeness and codec
direction are tracked in the CI-enforced status matrix; per-subsystem naming/doc
progress in the reconstruction matrix; remaining open questions as issues on the
roadmap. The fx_lib codecs and fx CLI validate the format specs — a byte-identical
round-trip is the proof a format is understood; the fxe source port is the same kind of proof for
the executable itself.
Contents¶
| Document | Description |
|---|---|
| start-here.md | Start here — the learning path from game concepts to internals, for players and modders |
| formats/ | Binary and text format specifications — all 46 formats, categorized |
| formats/STATUS.md | Generated per-format status matrix (spec, codec, tests, fuzzing) |
| reconstruction.md | Generated reconstruction matrix — per-subsystem naming/doc progress across all 7 binaries (epics #209 / #247) |
| architecture.md | Runtime environment, asset system, overlay architecture, and the subsystem map |
| game-loop.md | Main loop, initialization, per-frame dispatch, frame timing, and shutdown |
| structs.md | Recovered runtime struct reference with per-field confidence |
| globals.md | Named game-executable global variables, organized by subsystem |
| symbols.md | All 3,829 FA.SMS symbols, organized by subsystem |
| modding.md | Step-by-step modding recipes — textures, stats, missions, audio, and more |
Game-executable subsystems (each with recovered symbols, a struct/field map, and a theme-aware SVG; full index in the reconstruction matrix): objects · shape-selection · ai-interpreter · wingman · physics · collision · weapons · renderer · render-core · terrain · hud · network · sound · input · memory-resource · campaign · shell-ui · startup · seq · video-decode · view.
Overlay binaries & boundary: wail32 (audio) · comms / comms-modem / comms-screen / comms-transfer (serial/modem) · ip-tool (EA tech-support tool) · external-imports (the MS / third-party redistributable boundary).
Game Files
File Format Reference¶
All formats used by Jane's Fighters Anthology, organized by subsystem.
Archive¶
The container format that holds all game assets; almost every file in the game is packed into one of several .LIB archives.
| Format | Spec | Description |
|---|---|---|
| LIB | LIB.md | Main asset archive container with LZSS, PXPK, and DCL compression |
Graphics & Images¶
Paletted image formats used for aircraft skins, cockpit art, icons, and in-game screenshots.
| Format | Spec | Description |
|---|---|---|
| PIC | PIC.md | Compressed image codec — dense, sparse, and JPEG sub-formats |
| PAL | PAL.md | VGA 256-color 6-bit palette |
| RAW | RAW.md | Uncompressed in-game screenshot capture |
Terrain & Maps¶
Theater-level tile maps and the top-down mini-map used during mission selection and in-flight navigation.
| Format | Spec | Description |
|---|---|---|
| T2 | T2.md | Terrain tile map with height and texture data |
| MM | MM.md | Theater map and mini-map layout |
3D & Scene¶
Aircraft and object geometry, briefing room backgrounds, and the atmosphere/sky lookup tables loaded as Win32 overlay DLLs.
| Format | Spec | Description |
|---|---|---|
| SH | SH.md | 3D aircraft and object shape meshes (Phar Lap PE) |
| INF | INF.md | Aircraft technical info sheet with briefing room scene data |
| HGR | HGR.md | Hangar 3D background scene (Win32 DLL overlay) |
| LAY | LAY.md | Sky and atmosphere rendering lookup tables (Win32 DLL overlay) |
Audio¶
Raw PCM sound effects and MIDI-based music, plus the in-flight music sequencer bytecode.
| Format | Spec | Description |
|---|---|---|
| 11K / 5K / 8K | 11K.md | Raw PCM audio clips at 11 kHz, 5 kHz, or 8 kHz mono |
| XMI | XMI.md | Extended MIDI music sequences |
| MUS | MUS.md | In-flight music sequencer bytecode (Win32 DLL overlay) |
Video & Cutscenes¶
Full-motion video frames for intros and per-aircraft clips, mission briefing video streams, and the scripted cutscene timeline format.
| Format | Spec | Description |
|---|---|---|
| CB8 | CB8.md | FMV video frame decoder for intros, cutscenes, and per-aircraft clips |
| VDO | VDO.md | Streaming mission briefing video frames |
| FBC | FBC.md | Per-frame byte-size index for paired .VDO briefing files |
| SEQ | SEQ.md | Cutscene and animation event timeline |
Mission & Campaign¶
Mission definitions, briefing text, campaign state, AI scripts, and their compiled runtime companions — most loaded as Win32 PE DLLs.
| Format | Spec | Description |
|---|---|---|
| M | M.md | Mission definition — bracketed text format with object placement and waypoints |
| MT | MT.md | Mission briefing and debrief text with section/markup directives |
| CAM | CAM.md | Campaign PE DLL embedding mission lists, weapon tables, and state data |
| MC | MC.md | Per-mission condition evaluator (PE DLL) |
| AI | AI.md | AI script bytecode — plain-text DSL per object category |
| BI | BI.md | Compiled AI binary runtime companion to .AI script files |
Type Definitions (BRF DSL)¶
Seven file types share a plain-text assembly-like DSL that defines aircraft, weapons, objects, and sensors. The BRF spec below is the format overview.
| Format | Spec | Description |
|---|---|---|
| BRF | BRF.md | Overview of the text-based type definition DSL |
| OT | OT.md | Object type definitions |
| NT | NT.md | Nation and country type definitions |
| PT | PT.md | Pilot and aircraft performance type definitions |
| JT | JT.md | Jet engine and weapon type definitions |
| SEE | SEE.md | Sensor and electronics type definitions |
| ECM | ECM.md | Electronic countermeasures type definitions |
| GAS | GAS.md | Guided armament system type definitions |
UI & Win32 Overlays¶
The FA menu system is built from Win32 PE DLLs; each dialog, menu screen, font, and HUD element is a separate overlay loaded at runtime.
| Format | Spec | Description |
|---|---|---|
| HUD | HUD.md | Heads-up display overlay DLL |
| DLG | DLG.md | Dialog box overlay DLL |
| MNU | MNU.md | Menu screen overlay DLL |
| FNT | FNT.md | Bitmap font overlay DLL |
| PTS | PTS.md | Points and scoring overlay DLL |
System & Config¶
Game configuration, multiplayer network settings, the recovered C++ symbol map, and pilot save files.
| Format | Spec | Description |
|---|---|---|
| CFG | CFG.md | Binary game configuration — graphics, controls, audio, pilot slot |
| DAT | DAT.md | Binary multiplayer network settings (NET.DAT / MODEM.DAT / SERIAL.DAT) |
| EFFECT | EFFECT.md | GRAPHIC effect-spawn data — the in-executable effect-parameter and network spawn records |
| SMS | SMS.md | the game executable symbol map — 3,829 MSVC-mangled C++ symbols with virtual addresses |
| P | P.md | Pilot save file — career stats, callsign, and campaign progress |
| BIN | BIN.md | Lookup tables and palette subsets (INSIGMAP.BIN, VFONTPAL.BIN) |
Installer¶
Files used by the EA disc-based setup program — the install script and splash screen click-zone maps.
| Format | Spec | Description |
|---|---|---|
| ESA | ESA.md | EA installer archive — the Disc 1 container holding FA.EXE, the LIBs, and the drivers |
| RTP | RTP.md | Pocket Soft .RTPatch payload — the 1.00F→1.02F updater's binary diff (0xB59C codec + opcode diff) |
| SSF | SSF.md | EA installer script — plain-text keywords driving the setup UI |
| RGN | RGN.md | Installer UI region maps — splash screen click zones and button sprite atlas |
Text¶
Plain-text files embedded in the LIB archives for menu labels and UI screens, sharing the same directive engine as mission briefing files.
| Format | Spec | Description |
|---|---|---|
| TXT | TXT.md | Plain text files used in FA menus and UI screens; shares the .MT directive engine |
Archive
LIB — EALIB Asset Archive (.LIB)¶
All game assets are packed into .LIB files using the EALIB container: a flat
directory of named entries followed by their data blocks, with optional per-entry
compression. The archives live in the install directory and on both game discs
(see the inventory below); almost every other format documented
in this directory is stored inside one of them.
Tools¶
fx¶
fx lib ls <file.LIB> # list entries with flags and sizes
fx lib unpack <file.LIB> [-o output_dir] # extract all entries, decompressing
fx lib extract <file.LIB> <ENTRY> [-o dir] # extract one entry
fx lib pack <input_dir> <file.LIB> # build an archive (byte-identical)
fx lib repack <file.LIB> <out.LIB> # rebuild container from its own directory
fx lib patch <file.LIB> <ENTRY> <new_file> # replace one entry in place
fx lib repack keeps payloads raw (still compressed) and entry metadata
verbatim while recomputing every offset — byte-identical output for
well-formed archives. The fa_repack_roundtrip integration test (FX_FA_ROOT
mode) runs it against every archive in a real install.
fx lib unpack decompresses flags=0 and flags=4 automatically. Flags=1 (LZSS)
and flags=3 (PXPK) are surfaced as unsupported, not silently handed back
still-compressed: ealib_extract returns an empty payload and sets its
unsupported out-param (the CLI prints SKIP … (flags=N, unsupported)), so a
consumer walking a whole install never mistakes compressed bytes for the
decoded payload. Their decoders are tracked in #54; both are absent from the
Anthology install (100% flags 0/4). Container operations that do not decode —
fx lib repack and fx lib pack — still preserve such entries byte-identically
(raw bytes and flags copied verbatim).
Other Tools¶
- FATK — free (abandonware, 1998); original GUI tool with project-based LIB editing; requires a compatibility layer on 64-bit Windows
File Layout¶
All multi-byte integers are little-endian.
| Offset | Size | Type | Description |
|---|---|---|---|
0x00 |
5 | char[5] | Magic EALIB (ASCII, no null terminator) |
0x05 |
2 | u16 | Number of directory entries (N) |
0x07 |
18 × (N+1) | Directory entries (see below), then one terminator entry | |
| — | File data blocks, contiguous and in directory order, immediately after the terminator |
The directory carries one extra 18-byte terminator entry after the N real
entries: name and flags bytes all zero, offset field = total file size. File
sizes are therefore not stored — each entry's size, the last included, is
the next entry's offset minus its own. Every archive in the FA install
follows this layout exactly, with no padding or gaps: fx lib repack
rebuilds each of the ten shipped .LIBs byte-identically from the parsed
directory alone (offsets recomputed from scratch), which is the proof the
layout claims here rest on. fx_lib's reader also tolerates a missing
terminator (archives written by fx before the terminator was understood) by
falling back to end-of-file for the last entry's size.
Directory Entry (18 bytes)¶
| Offset | Size | Type | Description |
|---|---|---|---|
+0x00 |
13 | char[13] | Filename, null-padded, 8.3 DOS format (max 12 chars + null) |
+0x0D |
1 | u8 | Flags byte (see table) |
+0x0E |
4 | u32 | Absolute byte offset of file data in the .LIB |
Flags¶
| Value | Name | Description |
|---|---|---|
| 0 | raw | Uncompressed — data stored verbatim |
| 1 | lzss | LZSS compressed (4-byte decompressed-size prefix) — decoder tracked in #54; extraction surfaces it as unsupported |
| 3 | pxpk | Raw with a 4-byte PXPK inline header — decoder tracked in #54; extraction surfaces it as unsupported |
| 4 | dcl | PKWare DCL ("Blast") with 6-byte EA prefix |
Filename Conventions¶
Certain filename prefixes are engine conventions that apply to files of any type
stored in a .LIB. On extraction, fx lib unpack maps the characters
& * ? " < > | / \ : to _ (via ealib_safe_name) so output filenames are
legal and byte-identical on every platform; of the prefixes below, only & is
affected — ^, $, and _ extract as-is. The original names are preserved in
memory for patching operations.
| Prefix | Convention | Applies to |
|---|---|---|
& |
Looping ambient / cockpit sound | *.11K, *.5K, *.8K |
^ |
Voice / radio callout (one-shot) | *.11K, *.5K, *.8K |
$ |
2D weapon / ordnance cockpit icon | *.PIC |
_ |
Aircraft skin / texture | *.PIC |
Example: &AFTB2.11K in the archive extracts to _AFTB2.11K on disk.
EA Compression Wrapper (flags=4)¶
Compressed entries prepend a 6-byte header to a standard PKWare DCL stream:
| Offset | Size | Type | Description |
|---|---|---|---|
+0x00 |
4 | u32 | Decompressed size |
+0x04 |
1 | u8 | litmode: 0x00 = binary (only mode used in FA) |
+0x05 |
1 | u8 | dictbits: 4=1024-byte window, 5=2048, 6=4096 |
+0x06 |
— | PKWare DCL bitstream (LSB-first) |
The decompressed-size field is written correctly by the game's tooling, but a
crafted archive can claim anything up to 4 GiB. fx_lib treats claims above
64 MiB as malformed and rejects the entry — the largest real FA-era entry
decompresses to a few MiB (#168). A zero claim extracts to an empty payload
(#169); a claim larger than the stream's actual output is tolerated (the
output is sized by what the bitstream produces, up to the claim).
PKWare DCL Algorithm¶
Based on blast.c by Mark Adler (zlib project). See lib/src/blast.cpp.
Bit reading: bits are consumed LSB-first from a 32-bit buffer loaded one byte at a time.
Fixed Huffman trees (not stored in stream):
Length symbols (16 symbols):
RLE table: { 0x02, 0x23, 0x24, 0x35, 0x26, 0x17 }
Code lengths: [2,3,3,3,4,4,4,5,5,5,5,6,6,6,7,7]
Distance symbols (64 symbols):
RLE table: { 0x02, 0x14, 0x35, 0xE6, 0xF7, 0x97, 0xF8 }
Each RLE byte: high nibble = reps-1, low nibble = code_length.
Decode loop:
flag = read_bits(1);
if (flag == 0) {
emit read_bits(8); // literal byte
} else {
len = lenbase[decode(len_tree)] + read_bits(lenextra[sym]);
if (len == 519) break; // END marker
dsym = decode(dist_tree);
dist = (len == 2) ? (dsym << 2) + read_bits(2) + 1
: (dsym << dictbits) + read_bits(dictbits) + 1;
copy(len, from = output_tail - dist); // overlapping ok; out-of-window = 0
}
Critical: bit inversion. The Huffman decode loop inverts each raw bit:
code |= (bitbuf & 1) ^ 1; // ^1 is required -- matches blast.c exactly
Without this inversion many files silently truncate to a wrong shorter output.
Tables:
static const int lenbase[] = {3,2,4,5,6,7,8,9,10,12,16,24,40,72,136,264};
static const int lenextra[] = {0,0,0,0,0,0,0,0, 1, 2, 3, 4, 5, 6, 7, 8};
File Inventory¶
| File | TOOLKIT ID | Location | Key Contents |
|---|---|---|---|
| FA_1.LIB | "1 " |
Install dir | .FNT ×15, .PIC ×1986 |
| FA_2.LIB | "2 " |
Install dir | Main asset archive — see extension inventory below |
| FA_3.LIB | — | Disk 2 (Red) | .PIC ×822 (aircraft skin textures, raw), .INF ×269 (aircraft tech sheets, dcl) |
| FA_4B.LIB | — | Install dir | .11K ×77, .5K ×9 |
| FA_4C.LIB | "4C" |
Disk 1 (Blue) | .11K ×44, .PIC ×43, .CB8 ×4 |
| FA_4D.LIB | — | Install dir | .CB8 + .11K FMV footage |
| FA_7.LIB | "7 " |
Disk 1 (Blue) | .FBC ×355, .VDO ×355, .11K ×105, .5K ×1 |
| FA_10.LIB | "10" |
Disk 2 (Red) | .CB8 ×9, .11K ×9 |
| FA_10B.LIB | "AB" |
Disk 2 (Red) | .CB8 ×10, .11K ×10 |
| FA_11.LIB | "41" |
Disk 2 (Red) | .CB8 ×10, .11K ×10 |
| FA_11B.LIB | — | Disk 2 (Red) | .CB8 ×8, .11K ×8 |
TOOLKIT ID is the 2-character identifier the FA TOOLKIT uses internally in its
CACHE/LIBPTR.* index files to record which .LIB a given asset lives in. Note that
FA_10B.LIB maps to ID "AB" and FA_11.LIB to "41" — these do not match the
filename suffix, so the IDs appear to be opaque tokens rather than derived from the name.
The retail game does not read these CACHE/LIBPTR.* files at runtime: it rebuilds its
own in-memory name index each launch by scanning the install directory
(LibStartUp — memory-resource.md § LIB name resolution),
which also fixes the mount order and duplicate-name precedence.
FA_2.LIB Extension Inventory¶
Full enumeration via fx lib ls (FA_3.LIB excluded — Disk 2 not mounted):
| Extension | Count | Notes |
|---|---|---|
.AI |
9 | Artificial intelligence for objects |
.BI |
9 | Supplementary AI for objects |
.BIN |
6 | Lookup tables and palette data |
.CAM |
6 | Campaign definitions |
.DLG |
92 | In-game menu dialog layouts |
.ECM |
30 | Electronic counter-measures |
.GAS |
4 | Fuel definitions |
.HGR |
2 | Hangar screen (Win32 PE DLL) |
.HUD |
46 | Heads-up displays |
.JT |
135 | Weapon (ordnance) definitions |
.LAY |
24 | Cloud layers |
.M |
517 | Missions |
.MC |
21 | Campaign data |
.MM |
75 | Theater/map layouts |
.MNU |
12 | In-game menu layouts |
.MT |
363 | Mission briefing text |
.MUS |
9 | Music playlist / sequencer (Win32 PE DLL) |
.NT |
84 | Vehicle definitions |
.OT |
170 | Object definitions |
.PAL |
1 | Color palette |
.PIC |
1158 | 8-bit indexed bitmaps |
.PT |
145 | Aircraft flight models |
.PTS |
37 | Aircraft icon lookup (Win32 PE DLL) |
.SEE |
51 | Seeker definitions |
.SEQ |
126 | Cutscene sequencer |
.SH |
1275 | 3D object shapes |
.T2 |
16 | Terrain height/color/type maps |
.TXT |
8 | Campaign description text |
.XMI |
78 | MIDI audio (Extended MIDI) |
.5K |
781 | 5 kHz PCM audio |
.8K |
1 | 8 kHz PCM audio |
.11K |
114 | 11 kHz PCM audio |
Round-Trip Notes¶
fx lib pack rebuilds an archive byte-identically from an unpacked directory,
and fx lib patch replaces a single entry while leaving every other byte of the
archive untouched; both are asserted by tests/test_ealib.cpp and exercised
end-to-end against a real install by the fa_extract_manifest integration test
(FX_FA_ROOT mode). Extraction-side filename sanitisation (see
Filename Conventions) is reversed from the in-memory
directory, not the on-disk names, so packing survives the & → _ mapping.
Open Questions¶
1. LZSS (flags=1) and PXPK (flags=3) payload formats¶
Both flag values are identified, and ealib_extract now surfaces such entries
as unsupported (empty payload + unsupported flag) rather than returning them
still-compressed (#159). The LZSS bitstream parameters and the PXPK inline
header still have no written spec — no FA archive contains either flavour, so
writing the decoders needs samples from the wider Fighters family (ATF/USNF).
Status: open — re-static (#54)
Related¶
Formats: PIC and 11K document the filename-prefix
conventions from the consumer side; every format spec in this directory
describes content stored in these archives. ESA is the installer
container that ships four of these .LIB archives (FA_1/2/4B/4D) stored,
and whose PKWA streams are raw DCL — without the 6-byte EA wrapper the
flags=4 entries here use.
Engine: architecture.md — Asset System (EALIB Archives) covers how the game executable mounts and reads them at runtime.
Graphics & Images
PIC — Palettized Image (.PIC)¶
Custom image format used for aircraft skins, HUD overlays, instruments, and
backgrounds. Three sub-formats share the same 64-byte header, identified by the
format field. Stored throughout the .LIB archives — FA_2.LIB carries the
in-game art, FA_3.LIB the encyclopedia photographs.
Tools¶
fx¶
fx pic info <file.PIC> # header and sub-format summary
fx pic unpack <file.PIC> [-o out.png] # decode to PNG
fx pic pack <file.png> [-o out.PIC] # re-encode (see Round-Trip Notes)
Other Tools¶
- GIMP — free, cross-platform; handles indexed-color and palette-aware editing well
- Paint.NET — free, Windows; simple and fast for texture touch-ups
- Photoshop
$— industry standard; use 8-bit indexed mode to stay within palette - Affinity Photo
$— one-time purchase alternative to Photoshop
File Layout¶
All multi-byte integers are little-endian.
Header (64 bytes)¶
| Offset | Size | Type | Description |
|---|---|---|---|
0x00 |
2 | u16 | format: 0=dense, 1=sparse, 0xD8FF=JPEG |
0x02 |
4 | u32 | width in pixels |
0x06 |
4 | u32 | height in pixels |
0x0A |
4 | u32 | pixels_offset (absolute file offset of pixel data) |
0x0E |
4 | u32 | pixels_size |
0x12 |
4 | u32 | palette_offset (absolute file offset of inline palette, 0 if none) |
0x16 |
4 | u32 | palette_size (0 if using system PALETTE.PAL) |
0x1A |
4 | u32 | spans_offset (format=1 only) |
0x1E |
4 | u32 | spans_size (format=1 only) |
0x22 |
4 | u32 | rowheads_offset (format=0 only) |
0x26 |
4 | u32 | rowheads_size (format=0 only; must equal 4 × height) |
0x2A |
4 | u32 | font_data_offset — offset of a trailing 1,536-byte glyph-metrics block (256 × 6-byte records); nonzero only in the *FNT/video-title PICs. Not read by FA.EXE's PIC path (offline/FNT-DLL metadata) — see Round-Trip Notes |
0x2E |
18 | u8[18] | Padding, zeroed |
Pixel Data¶
One byte per pixel — each byte is a palette index (0–255). Index 0xFF = transparent.
Palette¶
The inline palette at palette_offset (if palette_size > 0) is raw VGA 6-bit
data: palette_size / 3 RGB triplets, each channel in range 0–63.
Scale to 8-bit: actual = (stored << 2) | (stored >> 6) (rotate-left-2).
- If
palette_size == 0: the PIC uses the systemPALETTE.PAL. - A partial palette (
palette_size < 768) overrides only the firstpalette_size/3entries.
Format 0 — Dense / Texture¶
Pixel data is sequential, row-major (top to bottom, left to right):
width × height bytes.
A row-head table at rowheads_offset contains height u32 values, each the
absolute file offset of the start of that row. Must be reconstructed correctly
when encoding:
rowheads[y] = pixels_offset + y * width;
Used for aircraft skins, terrain tiles, full-screen images.
Format 1 — Sparse / Image¶
Used for HUD overlays and UI elements where most pixels are transparent.
spans_offset points to an array of 10-byte span records, terminated by
row = 0xFFFF:
| Offset | Size | Type | Description |
|---|---|---|---|
+0x00 |
2 | u16 | row index |
+0x02 |
2 | u16 | start column (inclusive) |
+0x04 |
2 | u16 | end column (inclusive) |
+0x06 |
4 | u32 | byte offset into pixels_data for this span's pixels |
Pixels per span: end - start + 1.
Format 0xD8FF — JPEG¶
The entire .PIC file content is a standard JPEG — pass it directly to a JPEG
decoder. All PIC files in FA_3.LIB are this format. These are encyclopedia
reference images (photographs, diagrams), not the 3D aircraft skin textures
(which use format 0 and carry the _ prefix in FA_2.LIB).
File Inventory¶
Filename Conventions¶
The filename prefix identifies the role of the image within the engine:
| Prefix | Role | Example |
|---|---|---|
$ |
2D weapon / ordnance cockpit icon | $AIM9M.PIC, $AGM65A.PIC |
_ |
Aircraft skin / texture (referenced by .SH TextureFile instruction) |
_A10.PIC, _KIN.PIC |
| (none) | All other images: UI, medals, backgrounds, terrain tiles | PALETTE.PIC, ATFSPLAS.PIC |
The $ and _ prefixes are engine conventions embedded in the filenames stored
in the .LIB archives. They have no effect on the file format itself.
FA_3.LIB Naming Convention (Encyclopedia Reference Images)¶
FA_3.LIB (Disc 2) contains 700+ JPEG-format PIC files used by the in-game
aircraft encyclopedia viewer. All are 512×384 pixels except the five bare-name
thumbnail files (640×480). They are never referenced by the 3D engine.
Numeric suffix <AC>_<N>.PIC (N = 0–9) — exterior photographs and action
shots of the aircraft, one image per slot. Most aircraft have 4–10 numeric
variants. The game cycles through them in the encyclopedia photo gallery.
Simple or uncommon aircraft may have only _0. Count: 678 files.
Letter suffixes:
| Suffix | Role | Count | Example |
|---|---|---|---|
_C |
Cockpit interior photograph | 48 | F14_C.PIC, F22_C.PIC |
_E |
Engine photograph or cutaway | 38 | F14_E.PIC, F22_E.PIC |
_P |
Profile diagram with callout labels | 37 | F14_P.PIC, F22_P.PIC |
_F |
Internal structure / systems cutaway (CAD/exploded view) | 16 | F22_F.PIC, F16C_F.PIC |
_F is present only on higher-profile or more technically complex aircraft:
AF1, ASTOVL, AV8, B747, CMCHE, E2000, E3, F117, F16C, F22, F260, F29, F31,
GRIPEN, RAFALE, V22.
Bare name <AC>.PIC (no suffix) — five files (A6, F15, F15J, F18C, TU160)
at 640×480 pixels. These are aircraft selection screen / hangar thumbnails. The
aircraft image is composited against a white background. All other aircraft use
the _0 exterior photo in contexts where a thumbnail is needed.
Round-Trip Notes¶
fx pic repackre-emits any PIC byte-identically from its parsed structure: the header fields are re-serialized (proving them), the 22-byte header tail at0x2A–0x3Fis carried verbatim, and every region is copied through at its recorded offset under full-coverage verification — an unaccounted or overlapping byte fails the repack rather than being silently copied. Validated against every PIC in the full install (census in tests/test_pic.cpp; 2,693 dense + 493 sparse, no JPEG PICs ship inside the LIBs).- Layout facts established by the whole-install census (inferred):
dense = header · pixels (
width×heightat 64) · row-heads (4×height) · optional 768-byte palette, withspans_offset = 0and a vestigialspans_sizethe engine never reads (commonly10×(height+1); the font PICs carry another image's stale value). Sparse = header · pixel runs · span table · optional palette, the span table 16-byte aligned over short all-zero padding. Font PICs end with a trailing 1,536-byte block (256 × 6-byte glyph records) named by header field0x2A(below), also 16-aligned. font_data_offset(0x2A) is not consumed by FA.EXE's PIC path. The.PICname-resolution and list paths (?FindPic@@YGXPAD0JJ@Z0x467C30,_MakePicList@160x4679C0) and every.PICstring consumer were traced; none dereference header offset0x2A. Consistent with runtime fonts being served by the FNT overlay DLLs, the field is offline glyph-metrics metadata (nonzero only in the 25*FNT/title PICs per the #175 census), carried verbatim through repack but never read by the base executable.fx pic pack(PNG import) always encodes as format=0 (dense) with a full inline palette. The game accepts format=0 in place of any sub-format, including JPEG originals; userepackwhen byte identity matters.- Keep image dimensions unchanged — the engine does not resize at load time.
- Pixels are quantized to the nearest palette color on re-encode; alpha < 128 maps to 0xFF.
Related¶
Formats: PAL — the system palette and the 6-bit color encoding;
LIB — container for every PIC; SH — 3D shapes reference _
textures via their TextureFile instruction.
PAL — VGA Palette (.PAL)¶
A .PAL file is exactly 768 bytes: 256 RGB triplets in VGA 6-bit format.
No header. PALETTE.PAL, the game's primary palette, is stored compressed
(flags=4) inside FA_2.LIB.
Tools¶
fx¶
fx pal info <file.PAL> # summary
fx pal dump <file.PAL> # entry-by-entry listing
fxs¶
Opening a .PAL record (in a LIB or standalone) shows a 16×16 swatch grid
with per-index RGB tooltips, and can set the palette applied to PIC/CB8
previews — see gui.md.
Other Tools¶
No standard image editor reads the raw 6-bit VGA palette format directly. Use a hex editor to view or patch individual entries (3 bytes per color: R, G, B at 6-bit scale).
- HxD — free, Windows; lightweight and fast for small binary files like
.PAL - VS Code + Hex Editor — free, cross-platform; convenient if already using VS Code for text editing
- 010 Editor
$— paid; binary templates allow a labelled struct view over the palette entries
File Layout¶
Single-byte values only; no multi-byte integers.
| Offset | Size | Type | Description |
|---|---|---|---|
0x00 |
768 | u8[256×3] | R, G, B for each palette index 0..255 |
Each channel is stored as a 6-bit value (range 0–63). Scale to 8-bit for display or PNG output:
uint8_t to_8bit(uint8_t v6) { return (v6 << 2) | (v6 >> 6); }
The System Palette¶
PALETTE.PALis the game's primary palette. It is stored compressed (flags=4) insideFA_2.LIBand must be extracted before decoding paletted PIC files.- Each game version (USNF, ATF, FA) ships its own palette — do not mix them.
- Palette index 0xFF (255) is reserved as transparent across all PIC sub-formats.
Round-Trip Notes¶
The format has no derived or redundant fields, so decode → encode reproduces
the input byte-for-byte; tests/test_pal.cpp asserts it, alongside the 6-bit
→ 8-bit scaling values.
Related¶
Formats: PIC — consumes this palette (inline palettes use the
same 6-bit encoding); LIB — carries PALETTE.PAL DCL-compressed.
RAW — Raw Screen Capture (.RAW)¶
.RAW files are proprietary binary screenshots written by the FA engine.
They are not compatible with standard image tools and must be converted before
viewing or editing. Triggered in-game with Ctrl-Alt-Shift-V; files are
written to the FA install directory as screen0.raw, screen1.raw, etc.
(incrementing counter) — they never appear inside the .LIB archives.
Tools¶
fx¶
fx raw info <file.RAW> # header summary
fx raw unpack <file.RAW> [-o out.png] # convert to PNG
fx raw pack <file.png> [-o out.RAW] # PNG -> RAW (max 256 distinct colours)
Other Tools¶
- GIMP — free, cross-platform
- Paint.NET — free, Windows
- Photoshop
$— industry standard - Affinity Photo
$— one-time purchase alternative to Photoshop
File Layout¶
Width and height are stored big-endian — unusual for this little-endian engine, and confirmed against captures at four resolutions (1024×768, 800×600, 640×480, 320×200; see below).
| Offset | Size | Type | Description |
|---|---|---|---|
0x00 |
6 | char[6] | Magic mhwanh |
0x06 |
2 | u8[2] | Constant 00 04 (identical at every resolution) |
0x08 |
2 | u16 BE | Width in pixels |
0x0A |
2 | u16 BE | Height in pixels |
0x0C |
2 | u8[2] | Constant 01 00 |
0x0E |
18 | u8[18] | Null padding |
0x20 |
768 | u8[256×3] | Embedded palette: 256 × RGB8 triplets (8-bit 0–255) |
0x320 |
w × h | u8[] | Pixel data: 8-bit palette indices, row-major, top-to-bottom |
Total size for a 1024×768 screenshot: 787,232 bytes (32 + 768 + 786,432).
Resolution evidence (confirmed): captures taken at the four supported
display modes decode as 04 00/03 00 (1024×768), 03 20/02 58
(800×600), 02 80/01 e0 (640×480), and 01 40/00 c8 (320×200) at
offsets 8/10 — big-endian width and height, with the surrounding fields
constant. An earlier revision read a little-endian width at 0x06 and
guessed w/256, h/256 at 8/10; the multi-resolution corpus falsifies
that.
Embedded palette: the .RAW palette is 8-bit RGB (0–255 per channel),
not the 6-bit VGA format used by .PAL and .PIC files. No scaling is
needed — values are already full 8-bit.
PALETTE.PAL: the PALETTE.PAL extracted from the LIBs is the in-game
cockpit/UI palette and does not match the palette embedded in .RAW files.
Use the embedded palette when converting screenshots.
Conversion¶
To convert a .RAW to a standard image:
1. Read the 32-byte header; width = u16 big-endian at offset 8, height =
u16 big-endian at offset 10.
2. Read 768 bytes of embedded palette (256 × R, G, B — already 8-bit).
3. Read width × height bytes of pixel indices.
4. Map each index through the palette to produce RGB output.
Round-Trip Notes¶
raw_repackre-emits any capture byte-identically from its parsed fields (header re-serialized, palette and pixels copied through; trailing bytes fail rather than being silently carried). All in-install captures verify (tests/test_raw.cpp).fx raw pack(PNG import) rebuilds the palette from the image's distinct colours in first-seen order — at most 256; the PNG→RAW→PNG loop is pixel-exact. A repacked file need not be byte-identical to an engine capture of the same scene (palette order is the encoder's choice).
Related¶
Formats: PAL — contrast: RAW's embedded palette is 8-bit, not VGA 6-bit; PIC — the engine's other paletted-image format.
Terrain & Maps
T2 — Terrain Map (.T2)¶
FA_2.LIB contains 16 .T2 files — one per theater. Each stores the terrain
grid data (height, color, surface type) for a campaign area. Referenced by
.MM theater files via the map keyword (e.g. map apa.T2).
Tools¶
fx¶
fx t2 info <file.T2> # grid, elevation range, surface classes
fx t2 dump <file.T2> [--leaves] # records as CSV (summaries / full leaf grid)
fx t2 heightmap <file.T2> <out.png> # leaf elevation bands as grayscale PNG
The fx_lib read API (t2_read, api.md § t2.h) decodes the
full map — header strings, the per-leaf records, and the per-tile summary
array — losslessly: header + records reassemble the file byte-identically
(proven over all 16 stock theaters).
fxs¶
The fxs asset viewer previews a .T2 as a textured 3D terrain — the leaf
grid as a heightfield (elevation band → height), each leaf textured with its
texture_variant tile through the shared fx_render module
(§ Terrain Texturing below). Open the theater's texture LIB alongside the map
(tiles ship in a sibling LIB) so the tiles resolve. See
gui.md § T2 terrain.
File Layout¶
All multi-byte integers are little-endian.
Header¶
| Offset | Size | Type | Description |
|---|---|---|---|
0x00 |
4 | char[4] | Magic BIT2 (ASCII, no null) |
0x04 |
60 | char[60] | Theater name, null-padded ASCII (e.g. "Panama") |
0x40 |
20 | u8[20] | Reserved — never read by T_Load/T_GetLeaf; all-zero in every theater |
0x54 |
12 | char[12] | PIC texture atlas reference, null-padded ASCII (e.g. "apa.PIC") |
0x60 |
4 | u32 | Reserved — unread by the loader/sampler; 0 in every theater |
0x64 |
4 | u32 | Map dimension (tile columns = tile rows; 25 or 32) — a descriptive copy; the sampler reads the authoritative tiles_w/tiles_h at 0x7D/0x81 |
0x68 |
4 | u32 | Reserved — unread by the loader/sampler; 0 in every theater |
0x6C |
4 | u32 | Map dimension (duplicate of 0x64) — also descriptive, not read by the sampler |
0x70 |
8 | u8[8] | Reserved — never read by T_Load/T_GetLeaf; all-zero in every theater |
0x79 |
4 | u32 | leaf_step — leaves per tile side (8 in all theaters). (The old "0x78 = always 2048" reading was this field misaligned by one byte: 8 << 8.) |
0x7D |
4 | u32 | tiles_w — tile grid width (the old "0x7C = cols × 256" reading was this field misaligned) |
0x81 |
4 | u32 | tiles_h — tile grid height |
0x85 |
4 | u32 | File offset of the tile-summary array (tiles_w × tiles_h × 3 bytes; the payload tail) |
0x89 |
4 | u32 | leaves_w = tiles_w × leaf_step |
0x8D |
4 | u32 | leaves_h = tiles_h × leaf_step |
0x91 |
4 | u32 | File offset of the leaf array (leaves_w × leaves_h × 3 bytes) — always 0x95 |
0x95 |
— | Terrain data payload: the two flat arrays (structure below) |
Map Sizes¶
| Grid (w×h) | Files | tiles_w ([0x7D]) |
tiles_h ([0x81]) |
tiles | File size |
|---|---|---|---|---|---|
| 32×32 | 11 | 32 | 32 | 1024 | 199,829 B |
| 26×25 | 4 (EGY/FRA/UKR/VLA) | 26 | 25 | 650 | 126,899 B |
| 25×25 | 1 (TVIET) | 25 | 25 | 625 | 122,024 B |
When addressing tile (col, row): tile_index = row × tiles_w + col
(T_GetLeaf indexes both arrays row-major).
Data Payload¶
The payload is two flat row-major arrays of 3-byte records (field map
above; the engine's T_Load @0x4C5D70 relocates the two offsets into
pointers, and T_GetLeaf @0x4C6040 indexes both arrays as
(row × width + col) × 3):
| Offset | Size | Contents |
|---|---|---|
0x95 ([0x91]) |
leaves_w × leaves_h × 3 |
Leaf array — the full-resolution terrain grid (8×8 leaves per tile) |
[0x85] |
tiles_w × tiles_h × 3 |
Tile-summary array — one coarse record per tile |
T_GetLeaf(x, y, step) reads the leaf array while step < leaf_step and
falls back to the tile-summary array (indexing by x / leaf_step,
y / leaf_step) for coarser requests — the summaries are the pre-baked
far-LOD representation.
Superseded readings. Earlier revisions described the payload as a "21-byte sub-header + N_tiles × 195-byte tiles". That cut was an artifact: the "sub-header" was the tail of the real header field map above (its mysterious "class constants — one fixed value per grid-size class" decode exactly as the array offsets and leaf-grid dimensions, which are pure functions of the grid size), and the "195-byte tile with an interleaved summary record" does not exist — leaves are stored globally row-major, and the per-tile summaries live in the separate tail array. The old "record 0 selection algorithm" mystery dissolves with it: the summaries never sat among the leaves in the first place.
3-Byte Record (leaf and tile-summary)¶
| Byte | Field | Values / Notes |
|---|---|---|
| 0 | Surface class | 0xFF = water/ocean (all theaters); 0xD2 = land (APA, IRA, GRE, NSK, LFA — single land type); 0xD0–0xDA = multiple land classes (EGY, UKR: sand, rocky, dunes, etc.); 0xC2, 0xC4–0xC7 = land classes (TVIET: jungle, paddy, savanna, etc.) |
| 1 | Elevation band | 0 = sea level / coastal; higher = altitude above sea. Max observed per theater: APA=12, GRE=4, IRA=6, EGY=6, NSK=12, LFA=4, UKR=6, TVIET=16. Water leaves (class 0xFF) carry bands too — almost all 0–1, with a handful of elevated water leaves in GRE/LFA/PGU/WTA (inland lakes, up to band 9 in WTA) |
| 2 | Texture variant | 0–31; selects a sub-texture within the PIC atlas for this surface class |
File Inventory¶
| File | Name | Grid |
|---|---|---|
| APA.T2 | Panama | 32×32 |
| BAL.T2 | The Baltics | 32×32 |
| CUB.T2 | Cuba | 32×32 |
| EGY.T2 | Egypt | 25×26 |
| FRA.T2 | France | 25×26 |
| GRE.T2 | Greece | 32×32 |
| IRA.T2 | Iraq | 32×32 |
| KURILE.T2 | Kuril Islands | 32×32 |
| LFA.T2 | Falkland Islands | 32×32 |
| NSK.T2 | North/South Korea | 32×32 |
| PGU.T2 | Persian Gulf | 32×32 |
| SPA.T2 | Pakistan | 32×32 |
| TVIET.T2 | North Vietnam | 25×25 |
| UKR.T2 | Ukraine | 25×26 |
| VLA.T2 | Vladivostok | 25×26 |
| WTA.T2 | Taiwan | 32×32 |
All 16 live in FA_2.LIB. The PIC reference names the texture atlas used for
terrain tile rendering. Each theater has a matching .PIC file with the same
base name (e.g. APA.T2 → apa.PIC).
Engine Notes¶
Loader and grid sampler (resolved 2026-07-05)¶
The two long-standing open questions were resolved statically (#262):
- Sub-header "class constants" — decoded as the header field map above
(
leaf_step, tile/leaf grid dimensions, and the two array offsets), read byT_Load(0x4C5D70) /T_GetLeaf(0x4C6040). The earlier literal scan (0x95,0x80,195,21) missed the loader because the engine's fields sit at0x79–0x94and no 195-byte stride exists. The values are pure functions of the grid size — why every theater of a size class shared them byte-for-byte. - Tile summary record selection — no selection happens: the summaries
are a separate authored far-LOD array at
[0x85], returned byT_GetLeafwhen the requested step reachesleaf_step. The "interleaved record 0" was a mis-cut of the flat leaf array.
Header field-access set (resolved #132).
T_Load reads exactly two header words — it relocates the tile-summary and leaf
offsets at +0x85/+0x91 into pointers (*(ptr) += _th) and derives the
<theater>land.PIC name; it takes the theater name from the mission, not the
0x04 field. T_GetLeaf then reads only leaf_step (+0x79), the two grid
dimensions (+0x7D/+0x81), the leaf extents (+0x89/+0x8D), and the two
relocated pointers. No engine path reads the interspersed fields at
0x40/0x60/0x68/0x70 — they are reserved padding, which is why every
shipped theater leaves them zero. This closes the old "world-space bounds"
hypothesis: the grid is addressed purely by the relocated array offsets and the
leaf/tile dimensions, with no world-space extent field consumed.
Terrain Texturing¶
Per-leaf tile → PIC atlas. The mechanism, from static RE + asset inspection; intent is not recovered.
Each leaf's texture variant (byte 2 of the 3-byte record) selects a numbered
theater texture tile <theater><N>.PIC — a 256×256 palette-less indexed
dense PIC (APA0.PIC … APA25.PIC for Panama; LAND.PIC is the shared
fallback). T_Load (0x4C5D70) resolves the theater's textures on map load.
Confirmed: every observed tile's pixels fall in the palette band 192–255
(measured across APA0/APA1/APA4/APA12). The .PIC named in the header
at 0x54 (e.g. apa.PIC) is the overview map — 800×800 with its own
128-colour embedded palette used only for indices 1–127, swapped in separately
by MAPSwapPalette (0x42532A) — not the terrain tile palette.
The colours for the 192–255 band are not shipped in a .PAL: the install's
only palette, PALETTE.PAL, leaves 192–254 as (63,0,63) magenta placeholders.
Inferred: the band is filled at scene init from the theater's atmosphere/sky
(LAY) state into the live _curPalette (0x583DC4), the same path the
Remap / _tmapRemapTable chain below runs through. The fxs viewer stands
in for that band with a default earthy ramp
(terrain_preview.cpp,
FillDefaultTerrainBand); the atmosphere-driven palette and the
T_CellTmapLookup (0x4A8840) named-tmap landmark overlay are not yet
reproduced.
Surface Class → Palette Mapping (Remap pipeline)¶
The surface class byte (byte 0 of each 3-byte sub-tile record) is never used as
a raw palette index. At scene init, @DoSetTmapRemaps@0 (0x004cc518) builds
a 256-byte precomputed cache:
// pseudocode — restored by fixing Remap()'s signature in Ghidra
for (uint i = 0; i < 256; i++)
_tmapRemapTable[i] = Remap((byte)i);
The terrain renderer indexes into _tmapRemapTable[surface_class] per-polygon
for speed rather than calling Remap() directly.
Remap() — 0x004cc44c¶
Remap(byte surface_class) maps a surface class to a final 8-bit palette index
under the current atmosphere state. Its pipeline (decompiled after signature
fix):
Stage 1 — Extended surface class redirect
If the incoming value is > 255 (i.e. an extended surface class passed via full
EAX), the function indexes into DAT_0055b9e0, a uint16_t[] redirect table:
palette_index = DAT_0055b9e0[surface_class] // ushort lookup
if palette_index > 255: return palette_index directly (bypass atmosphere)
Surface classes 0–255 pass through this stage unchanged.
Stage 2 — Atmosphere / lighting chain (gated by _effects bits)
Applied only when _effects & 0x8 is set:
| Step | Condition | Operation |
|---|---|---|
| Shade table | unaff_EBX != 0 (caller-set flag) |
idx = _currentShadeTable[idx] |
| Blend tables | _effects & 0x10 |
idx = DAT_005843c8[DAT_005843c4[idx]] |
| Tint table | always (within effects block) | idx = _currentTintTable[idx] |
| Brightness | idx != 0xFF |
idx += _globalColorAdd |
Stage 3 — return the final byte palette index.
Globals referenced¶
| Symbol | Role |
|---|---|
_tmapRemapTable |
256-byte precomputed cache; rebuilt at scene init by @DoSetTmapRemaps@0 |
DAT_0055b9e0 |
uint16_t[] extended surface class redirect table (surface class > 255 path) |
_effects |
Rendering effects bitfield; bit 3 gates atmosphere chain, bit 4 gates blend layer |
_currentShadeTable |
Secondary atmosphere layer pointer; applied when unaff_EBX != 0 |
_currentTintTable |
Primary atmosphere layer pointer; always applied within the effects block |
DAT_005843c4 / DAT_005843c8 |
Additional atmosphere blend layer pointers (double-pass blend) |
_globalColorAdd |
Global brightness/gamma shift; added to final index unless it is 0xFF |
unaff_EBX |
Implicit caller-set flag (EBX) gating the shade table step; semantics unknown |
The _currentShadeTable, _currentTintTable, DAT_005843c4, and
DAT_005843c8 are the same atmosphere layer globals populated by
ParseLayerFile / SetActiveLayerByAngle (see
architecture.md — Sky & Atmosphere subsystem).
Relation to .MM tmap coordinates¶
MISSIONTextProc is the MM file text parser (keyword dispatch loop). The
tmap keyword handler (confirmed from decompile) reads 4 × s16 values per
entry (format tmap %d %d %d %d), bounded to 3500 entries (counter
tlistSize), stored into arrays at tlist/DAT_005733e4. A second form
tmap_named reads a name string + 2 × s16, stored as a 53-byte struct at
tdic + i × 0x35. The tdic keyword reads 1 × u32 + 32 × u8 into the same
struct at offset +0x0D (max 300 entries, counter tdicSize).
Approximate tile-index formula (based on observed tmap values in MM files):
T2_tile_col = tmap_col / 8
T2_tile_row = tmap_row / 8
tile_index = T2_tile_row × dim_x + T2_tile_col
Round-Trip Notes¶
The write path (t2_write, t2_repack — api.md § t2.h) is
byte-identical: a t2_read → t2_write round-trip reproduces the file
exactly, verified over all 16 stock theaters (t2_repack census). This holds
because the format is fully accounted for — the entire pre-payload region is
carried verbatim in T2Map::header (including the reserved sub-header bytes at
0x40/0x60/0x68/0x70, now confirmed unread by the loader/sampler and
zero in every theater — see Engine Notes), and the payload is just the two flat
record arrays. No field is recomputed on write, so nothing can drift.
t2_write re-derives the grid extents from the header's own offset fields and
requires the (editable) leaves / summaries vectors to match them, so an
edited map that keeps the grid shape serializes safely; a map whose record
counts no longer agree with its header is rejected (empty result) rather than
emitting a file the loader would misread.
Related¶
Formats: MM — theater files that reference .T2 via
map <name>.T2; PIC — terrain texture atlas (same base name as
.T2).
Engine: architecture.md — Sky & Atmosphere subsystem
shares the atmosphere layer globals used by the Remap pipeline.
MM — Theater Map Layout (.MM)¶
FA_2.LIB contains 75 .MM files. Each defines a theater — the complete scene
description for a group of missions: terrain reference, weather, object
placements, waypoints, and terrain tile overrides. Format is plain ASCII
text with a keyword/argument syntax, CRLF line endings.
Tools¶
fx¶
fx mm info <file.MM> # scene summary (alias of fx mission)
fx mm unpack <file.MM> [-o out] # export to editable text
fx mm pack <file> [-o out.MM] # re-encode (byte-identical)
File Layout¶
Plain text; keyword/argument lines, blocks terminated by a lone . line.
Sections appear in this order:
- File header — global scene parameters
sidesblock — faction alignment tabletime/historicalera— time of day and eraobjblocks — static object placementsspecialblocks — named map labels (cities, bodies of water)tmapentries — sparse terrain tile overridestmap_namedentries — named tile positions (present in some files)waypoint2blocks — AI/scripted waypoint sequences (present in some files)tdicblocks — per-tile collision/passability bitmaps
Header Keywords¶
| Keyword | Arguments | Description |
|---|---|---|
textFormat |
(none) | File type marker — always the first line |
map |
<name>.T2 |
Terrain map file to load |
layer |
<name>.LAY <index> |
Cloud/atmosphere layer file and slot index |
clouds |
<int> |
Cloud density (0 = none) |
wind |
<speed> <direction> |
Wind speed and bearing |
view |
<x> <y> <z> |
Initial camera world-space position |
Sides Block¶
Defines faction alignment. Four variants across all 75 files:
| Keyword | Entry count | File count |
|---|---|---|
sides |
18 | 9 |
sides2 |
19 | 32 |
sides3 |
24 | 9 |
sides4 |
64 | 25 |
Each entry is a tab-indented hex byte: $00 = neutral/friendly, $80 =
hostile to the player.
The table is a flat array indexed by faction code: entry i = hostility of
side code i. The number suffix (2/3/4) is a format version indicating how
many faction codes are defined — each version is a strict superset of the
previous (sides2[0–18] == sides3[0–18] == sides4[0–18];
sides3[0–23] == sides4[0–23]). Theaters added in later FA versions use a
higher-numbered variant to cover newly introduced faction codes.
The nationality field in obj blocks is not an index into this table —
it is a cosmetic UI code (country flag/emblem in briefings) and can exceed 162,
far beyond the 64-entry maximum.
Object Block (obj)¶
Each obj block defines one static scene object. Terminated by a lone . line.
| Field | Arguments | Description |
|---|---|---|
type |
<name>.OT |
Object type reference |
pos |
<x> <y> <z> |
World-space position |
angle |
<yaw> <pitch> <roll> |
Orientation in degrees |
flags |
<hex> |
Object state/behaviour flags (see below) |
speed |
<int> |
Initial speed |
alias |
<int> |
Unique object ID (negative integers) |
nationality |
<int> |
Primary faction code |
nationality2 |
<int> |
Secondary faction code |
nationality3 |
<int> |
Tertiary faction code |
name |
<str> |
Display name |
color |
<int> |
Color index |
icon |
<int> |
Map icon ID (-1 = no icon) |
react |
<hex> <hex> <hex> |
Hostile faction bitmask: three 16-bit words covering faction codes 0–47 (bit n set = react to faction n as hostile) |
searchDist |
<int> |
AI detection radius |
skill |
<int> |
AI skill level |
tdic |
<int> |
Tile dictionary index |
special |
(none) | Marks object as a special/scripted entity |
w_for |
<int> |
Waypoint owner reference |
map_obj_success_flags |
<alias> <hex> |
Looks up entity by alias, then writes the hex value into entity ot_flags bits 5–7: entity+1 = (value) \| (entity+1 & 0xffffff1f). Used to set or restore per-object mission-objective state. The mission save handler (FUN_00495e80) serialises current bit 5–7 state back using this keyword. |
flags Bit Survey¶
8 distinct values observed across all 75 files (8,373 obj blocks total):
| Value | Bits | Object types | Count |
|---|---|---|---|
$0 |
— | Decorative buildings, flags, landmarks (no game logic) | 471 |
$1 |
0 | Fuel depots, bunkers, strips, flags (friendly?) | 968 |
$3 |
0, 1 | Mobile military units — all .NT (AA guns, SAMs, fighters) |
232 |
$401 |
0, 10 | Fuel depots, control towers, strips, flags | 5,524 |
$403 |
0, 1, 10 | Mobile military units — all .NT |
241 |
$601 |
0, 9, 10 | Named structures (carrier KING.OT, houses, factory) | 24 |
$4003 |
0, 1, 14 | Runways (STRIP1, DTSTRP, STRIP5, STRIP7) | 906 |
$4403 |
0, 1, 10, 14 | Runways (STRIP4, STRIP) | 2 |
Bit meanings:
- Bit 0 ($1) — inferred — present on all non-decorative objects; likely
"active / participates in game logic"
- Bit 1 ($2) — inferred — set on mobile units (.NT) and runways;
"mobile or functional structure"
- Bit 14 ($4000) — inferred — set only on runway strips; "landing surface"
- Bit 9 ($200) — confirmed — @Reaction@12 (0x464040): when set, entity
is immediately rejected as a valid AI target (returns '\x1f' rejection
code, same as non-targetable or dead). Only 24 objects carry this flag
($601): carrier KING.OT, houses, and factory types. Semantics: "protected
from AI targeting / not auto-targetable" — used for mission-critical scene
objects enemies should not autonomously engage.
- Bit 10 ($400) — confirmed — _Reaction_12 (0x464040) and
_MaskEvents_4 (0x463ea0): drives civilian/light-type event handling. Set on
fuel depots, control towers, runway strips, and flags ($401). Shared
semantic with NT.md bit 10. Semantics: "civilian or non-combat structure"
— participates in game logic (bit 0 set) but uses lighter event-system
dispatch paths.
Example¶
obj
type STRIP1.OT
pos 890323 0 462935
angle 0 0 0
nationality3 185
flags $4003
speed 0
alias -10100
.
Special Block (special)¶
Named geographic labels shown on the theater map (cities, seas, landmarks).
Same field set as obj but without type.
special
pos 1816892 0 1071944
name East Falkland
color 48
icon -1
flags $0
.
Terrain Tile Data¶
tmap — sparse tile overrides. Only non-default tiles appear; the grid is
otherwise implied from the .T2 terrain file.
tmap <col> <row> <tile_id> <variant>
colandroware multiples of 4 (grid coordinates in 4-unit steps)tile_idis an index into the terrain tile setvariantis a sub-tile selector (0–3 observed)
tmap_named — named tile positions. Like tmap but with a symbolic key
encoding the position:
tmap_named k<col3><row3> <col> <row>
The key k<col3><row3> zero-pads column and row to 3 digits each (e.g.
k000004 = col 0, row 4). The explicit <col> and <row> arguments are
always identical to the values encoded in the key — they are redundant.
Present in maps that reference these tiles from scripts or other systems.
Waypoint Block (waypoint2)¶
waypoint2 <count>
w_index <int>
w_flags <int>
w_goal <int>
w_next <int>
w_pos2 <unk> <unk> <x> <altitude> <z>
w_speed <int>
w_wng <int> <int> <int> <int>
w_react <int> <int> <int>
w_searchDist <int>
w_preferredTargetId <int>
w_name <str>
[next waypoint entry...]
count is the number of waypoint entries that follow. Each entry begins with
w_index. w_pos2 has 5 arguments; the first two are always 0 0 in observed
files.
Tile Dictionary (tdic)¶
Follows the tmap section. Each tdic block defines a 4×8 binary
passability/collision grid for a tile variant.
tdic <id>
<b0> <b1> <b2> <b3>
<b0> <b1> <b2> <b3>
... (8 rows)
Values are 0 (passable) or 1 (blocked). <id> observed as 256 in all cases.
Engine Notes¶
World-Space Coordinate System — confirmed¶
?MAPWorldToScreen (MAPWorldToScreen) maps 3D world-space positions to 2D map
screen coordinates:
screen_x = DAT_00536508 + (world_x - worldCenter) / mapScale;
screen_y = DAT_0053650a + (DAT_00536528 - world_z) / mapScale;
world_x/world_zare the first and third components of the int[3] world position vector (Y =world_zin the map's X/Z plane;world_y= altitude, not used for map display)worldCenter/DAT_00536528— world-space map center (origin) for X and Z axes (set at theater load time)DAT_00536508/DAT_0053650a— screen center pixel coordinatesmapScale— world-units-per-pixel scale factor (runtime zoom level); larger = more zoomed out
The Z-axis inversion (origin_z - world_z) means positive world-Z maps to
upward on screen, consistent with the engine's +Z = northward convention.
pos and view values in MM files are in these same world-space integer units.
World-space unit = 1 foot (confirmed). Calibrated via JT.md seeker-range
cross-check: AIM9X lobe 1 max ^50000 = 8.2 nm; 50,000 feet / 6,076 ft/nm
≈ 8.23 nm ✓. The FA engine uses feet throughout for all world-space coordinates.
Round-Trip Notes¶
fx mm pack (the mission codec) re-emits the keyword stream byte-identically,
including CRLF line endings and tab indentation; tests/test_mission.cpp
asserts byte preservation over both .M and .MM inputs.
Open Questions¶
1. w_goal semantics¶
Only 0 and 1 observed across all 75 files (192 waypoints total). w_goal 0
always appears with w_flags 1 and w_speed 0 (stationary anchor/spawn
point); w_goal 1 appears with w_flags 0, a non-zero speed, and a w_react
bitmask (active patrol waypoint). Exact goal-type semantics need a trace of the
waypoint consumer in the game executable.
Status: open — re-static (#54)
Related¶
Formats: T2 — terrain height/color/type maps referenced via
map; LAY — cloud layer files referenced via layer; M —
.M individual missions placed within a theater; CAM — campaign
definitions that group .MM theaters; BRF — .OT object type
definitions referenced by obj blocks.
3D & Scene
SH — Shape 3D Model (.SH)¶
.SH files hold the 3D geometry for every aircraft, vehicle, weapon, and scene
object — 1275 of them in FA_2.LIB. Each is a Phar Lap PE/LE executable
whose CODE section carries a shape bytecode stream: vertex buffers, faces,
texture references, and conditional jumps the engine interprets at render time
(some models embed raw x86 code as well).
Tools¶
fx¶
fx sh info <file.SH> # scale, bounding box, vertex/face count, textures
fx sh unpack <file.SH> [-o out.obj] # export Wavefront OBJ (with usemtl directives)
The exported OBJ uses mtllib shape.mtl and usemtl <texture_name> directives
when textures are present. A .mtl file is not written automatically — create
one manually if needed for rendering. The library API behind these commands
(sh_parse_info / sh_parse_mesh / sh_to_obj) is documented in
api.md.
Other Tools¶
- Blender — free, cross-platform; best option for inspecting and measuring exported OBJ geometry
- MeshLab — free, cross-platform; lightweight viewer with basic mesh statistics
- FASHion — free, FA-specific; vertex repositioning only (see workflow below)
- SketchUp 8 — free (legacy version required by FASHion plugin); use alongside FASHion
- 3ds Max
$— paid; full mesh editing if a pack command is added in future
Community editing workflow (FASHion + SketchUp 8):
- FASHion can only reposition individual vertices; it cannot add or remove vertices, change face topology, or alter the overall mesh structure. The rebuild operation overwrites the original file in place — always back up before editing.
-
SketchUp 8 is used as the 3D viewport. FASHion exports a vertex coordinate file that SketchUp loads via a plugin; after adjustments the modified coordinates are exported back and FASHion rebuilds the shape.
-
Unpack the target
.SHfrom its.LIBwithfx lib unpack. - Open the
.SHin FASHion; export the vertex file. - Load into SketchUp 8 (install outside
Program Filesto avoid permissions issues). - Reposition vertices as needed.
- Export from SketchUp; rebuild in FASHion → overwrites the
.SH. - Repack with
fx lib patch.
For bulk vertex edits (e.g. scaling an entire section), the community workflow converts the vertex file to a spreadsheet, applies transformations numerically, then reconverts before importing back into FASHion.
SH files with x86-only geometry (65/1275 in FA — see Round-Trip Notes) cannot be edited with FASHion and require direct x86 disassembly for modification.
File Layout¶
All multi-byte integers are little-endian.
Container: Phar Lap PE/LE Executable¶
SH files are Phar Lap PE/LE executables. The shape bytecode lives in the CODE section. Parse via the standard MZ/PE header chain:
data[0x00..0x02] MZ signature: 'M' 'Z'
data[0x3C..0x40] e_lfanew (u32 LE) -> offset of PE/LE header
pe[0..2] Phar Lap signature: 'P' 'L' (same layout as standard 'P' 'E')
pe[4..6] Machine (u16, ignored)
pe[6..8] NumberOfSections (u16)
pe[20..22] SizeOfOptionalHeader (u16)
pe[24 + SizeOfOptionalHeader ..] Section table (40 bytes per entry)
Section entry layout:
[0..8] Name (8 bytes, null-padded) -- always "CODE" for the code section
[8..12] VirtualSize (u32)
[12..16] VirtualAddress (u32)
[16..20] SizeOfRawData (u32) <-- use this
[20..24] PointerToRawData (u32) <-- use this as file offset
[24..40] (ignored)
Take the first section with PointerToRawData > 0 — that is the code
section. Always named CODE. PointerToRawData is the file offset;
SizeOfRawData is its length.
Code Section Structure¶
[FF FF] 2-byte signature
[radius_world i16] authored bounding radius, world units — engine-unused
[radius i16] approximate bounding-sphere radius, shape units
[scale i16] coordinate scale (bytes 6..8): see table below
[ext[0] i16] bounding extent X (half-width)
[ext[1] i16] bounding extent Y (half-depth)
[ext[2] i16] bounding extent Z (half-height)
[instruction stream] variable-length opcodes
radius (bytes 4..6) is an authored, approximate bounding-sphere radius in
shape units: across all 1275 FA_2.LIB shapes it is always positive (min 10) and
correlates 0.996 with ‖ext‖₂, sitting anywhere between the true max vertex
norm and the (loosely padded) header extents. The engine folds it into the
projection-shift computation, where only its magnitude (leading bit) matters —
see Engine Notes.
radius_world (bytes 2..4) is the same radius expressed in world units:
where nonzero it equals radius * 2^(scale-8) exactly in 93/134 files, with
authoring drift in the rest (ratios 1.4–4.8). It is zero in 1141/1275 shapes —
every aircraft and weapon — and nonzero only for ground/naval scenery (ships,
runways, cities, rocks, SAM sites). No the game executable code reads it: it is authoring
residue — see Engine Notes for the tracing evidence.
Scale table (world_coord_feet = raw_i16 * scale_factor):
| scale field | scale_factor | Notes |
|---|---|---|
| 7 | 0.5 | USNF97 and earlier only |
| 8 | 1.0 | Standard FA -- 1 unit = 1 foot |
| 9 | 2.0 | Large objects |
| 10 | 4.0 | Very large objects |
| 11 | 8.0 | Terrain features |
| 0 | 1.0 | Treated as 8 |
Instruction Dispatch¶
Instructions are either Byte-magic (1-byte opcode) or Word-magic
(2-byte opcode: [op_byte, 0x00]). Dispatch is on the first byte.
Key instructions:
| First byte | Name | Total size | Notes |
|---|---|---|---|
0xFF |
Header | 14 | Always first; scale at [6..8] |
0x00 |
EndObject | all remaining | Triggers X86Unknown skip if obj_end_off set |
0x01 |
EndShape | all remaining | Terminates the shape |
0x1E |
ShortEOF | 1+ | Return from the current call frame (do_short_eof = ret); trailing 0x1E run is alignment. See Engine Notes |
0x38 |
ShortJump | 3 | [38][rel16]; see Engine Notes |
0xBC |
UnkBC | 2 | |
0xF0 |
X86Code | variable | Bail out -- x86 machine code |
0xF6 |
VertexInfo | 7 | [F6][idx u16][color u8][normal i8[3]] |
0xFC |
Face | variable | See face format section |
Word-magic instructions (second byte = 0x00):
| First byte | Name | Total size |
|---|---|---|
0x06 |
OrderPlane | 16 + u16@[14] — plane-test draw-order selector; see Engine Notes |
0x08 |
Unk08 | 4 |
0x0C |
OrderPlaneYZ | 12 + u16@[10] — two-axis (y/z) OrderPlane variant |
0x0E |
OrderPlaneXZ | 12 + u16@[10] — two-axis (x/z) OrderPlane variant |
0x10 |
OrderPlaneXY | 12 + u16@[10] — two-axis (x/y) OrderPlane variant |
0x12 |
Unmask | 4 |
0x2E |
Unk2E | 4 |
0x3A |
Unk3A | 6 |
0x40 |
JumpToFrame | 4 + u16@[2]*2 |
0x42 |
SourceName | 2 + strlen + 1 |
0x44 |
Unk44 | 4 |
0x46 |
Unk46 | 2 |
0x48 |
Jump | 4 |
0x4E |
Unk4E | 2 |
0x50 |
LongJump | 6 |
0x66 |
Unk66 | 10 |
0x68 |
Unk68 | 8 |
0x6C |
OrderField | 13/14/16 (trailing embedded 38/48/50 jump) — object-field draw-order selector |
0x6E |
UnmaskLong | 6 |
0x72 |
Unk72 | 4 |
0x76 |
Unk76 | 10 |
0x78 |
BBoxCull | 12 |
0x7A |
Unk7A | 10 |
0x82 |
VertexBuffer | 6 + u16@[2]*6 |
0x96 |
Unk96 | 6 |
0xA6 |
JumpToDetail | 6 |
0xAC |
JumpToDamage | 4 |
0xB2 |
UnkB2 | 2 |
0xB8 |
UnkB8 | 4 |
0xC4 |
XformUnmask | 16 |
0xC6 |
XformUnmaskLong | 18 |
0xC8 |
JumpToLOD | 8 |
0xCA |
UnkCA | 4 |
0xCE |
UnkCE | 40 |
0xD0 |
UnkD0 | 4 |
0xD2 |
UnkD2 | 8 |
0xDA |
UnkDA | 4 |
0xDC |
UnkDC | 12 |
0xE0 |
TextureIndex | 4 |
0xE2 |
TextureFile | 16 |
0xE4 |
UnkE4 | 20 |
0xE6 |
UnkE6 | 10 |
0xE8 |
UnkE8 | 6 |
0xEA |
UnkEA | 8 |
0xEE |
UnkEE | 2 |
0xF2 |
PtrToObjEnd | 4 |
VertexBuffer (0x82 0x00)¶
Pushes a batch of vertices into the global vertex pool.
[82 00] opcode (2 bytes)
[nverts u16] number of vertices in this buffer
[push_at u16] byte offset into the global pool where this buffer starts
[x y z i16 ...] nverts * 3 signed 16-bit coordinates (LE)
Pool index = push_at / 8. Vertex slot size is 8 bytes in the engine's
pool (6 bytes of coords + 2 bytes alignment padding), so push_at is always a
multiple of 8. Face indices reference global pool indices.
TextureFile (0xE2 0x00)¶
Sets the current texture for subsequent faces.
[E2 00] opcode
[name 14 bytes] null-padded ASCII filename (e.g. "_A10.PIC")
PtrToObjEnd (0xF2 0x00)¶
Records the absolute code-section byte offset of the EndObject instruction.
[F2 00] opcode
[offset u16] absolute byte offset within the code section
Face (0xFC)¶
Variable-length polygon face instruction.
[FC]
[content_flags u8] see FaceContentFlags below
[layout_flags u8] see FaceLayoutFlags below
[color u8] palette color index for untextured rendering
[is_shadow u8] non-zero if this face is a shadow polygon
[if HAVE_FACE_NORMAL (content_flags & 0x40):]
[face_normal i16[3]] face normal vector, scale by 1/32765.0 to get float
[if USE_BYTE_FACE_CENTER (layout_flags & 0x02):]
[face_center i8[3]]
[else:]
[face_center i16[3]]
[nindices u8] number of vertex indices (= number of polygon corners)
[if USE_SHORT_INDICES (layout_flags & 0x04):]
[indices u16[nindices]] 2-byte pool indices
[else:]
[indices u8[nindices]] 1-byte pool indices
[if HAVE_TEXCOORDS (content_flags & 0x04):]
[if USE_BYTE_TEXCOORDS (layout_flags & 0x01):]
[(s u8, t u8) * nindices] 8-bit texcoords
[else:]
[(s u16, t u16) * nindices] 16-bit texcoords
FaceContentFlags:
| Bit | Mask | Meaning |
|---|---|---|
| 7 | 0x80 | Unknown (Unk1) |
| 6 | 0x40 | HAVE_FACE_NORMAL |
| 5 | 0x20 | Unknown (Unk2 — brighter shading) |
| 4 | 0x10 | Unknown (Unk3 — perspective-correct mapping) |
| 3 | 0x08 | Unknown (Unk4) |
| 2 | 0x04 | HAVE_TEXCOORDS |
| 1 | 0x02 | FILL_BACKGROUND |
| 0 | 0x01 | Unknown (Unk5) |
FaceLayoutFlags:
| Bit | Mask | Meaning |
|---|---|---|
| 3 | 0x08 | Unknown (Unk0) |
| 2 | 0x04 | USE_SHORT_INDICES (u16 instead of u8) |
| 1 | 0x02 | USE_BYTE_FACE_CENTER (i8[3] instead of i16[3]) |
| 0 | 0x01 | USE_BYTE_TEXCOORDS (u8[2] instead of u16[2]) |
X86Unknown Region¶
Some shapes embed native x86 machine code in the instruction stream, entered
by the 0xF0 X86Code opcode. These blocks are not procedural geometry
generators — they are conditional selectors: each reads a piece of live game
state and re-enters the bytecode interpreter on the sub-stream that matches. They
exist because the bytecode's own conditionals cannot read arbitrary engine
globals; the authoring tool emitted a small x86 switch instead. 208 of the
1275 FA_2.LIB shapes carry them.
The fx read codec bounds these regions (skip protocol below) and recovers
the guarded sub-stream geometry via the PE relocation table (sub-stream
harvest below) so articulated models render more completely; this section
specifies the runtime contract so the game-accurate, state-selected effect
can be reimplemented — see Round-Trip Notes and fa-bridge#21
for the interpreter that reads live state, and
#297 for the codec-side
work remaining.
Skip protocol (read codec)¶
The parser bounds each region:
- PtrToObjEnd (0xF2) seen: record
obj_end_off = offset_field. - EndObject (0x00) seen while
current_pos < obj_end_off: the range[current_pos .. obj_end_off)is the x86 region (native code plus the sub-stream geometry it guards). Skip toobj_end_offand continue. - EndObject (0x00) seen while
current_pos >= obj_end_off: real end of object; stop parsing.
Sub-stream harvest (read codec, reloc-based)¶
Rather than dropping the guarded geometry, the codec recovers it without
executing the x86. Each x86 selector sets esi to a sub-stream via an internal
pointer that the Phar Lap PE base-relocation table fixes up, so the reloc
entries whose target lands inside the code section are the exact sub-stream
entry offsets (a byte scan would false-positive on x86 bytes — the relocations do
not). The codec (lib/src/sh.cpp, collect_reloc_targets + harvest_target):
- Parses the
.reloctable; keeps targets that point into the code section and are notFF 25trampolines (those reach the game executable's exports). - From each target, walks the geometry (VertexBuffer / Face / TextureFile, plus
VertexInfo) structurally: it follows
Unmask/UnmaskLongand draw-order-selector calls into their fragments (which return at their ShortEOF), follows0x48/0x38jumps, and applies the same frame/damage/LOD/detail selection as the base walk (bounded by a visited-set + recursion depth). Because some fragments are only reached by calls the static walk cannot decode (x86, object-field conditions), a reloc-entered walk also continues through top-level ShortEOFs instead of stopping — the walk-through that recovers a complete airframe (the A-10's left wing, the F-16's full planform). - Writes vertices append-only (never below the base pool count) so state-variant sub-streams that reuse low pool slots cannot corrupt the base mesh. When a VertexBuffer is skipped by that guard, the faces that follow it in the same walk are dropped too — they index the skipped buffer, and collecting them drew coarse-LOD faces with finest-pool vertices (giant garbage polygons).
By default this yields the base mesh plus every reloc-reachable sub-stream —
all articulation states merged. The codec now also recovers, clean-room from
each shape's own bytes, which _PL* input every selector reads and which
sub-stream a given compare value selects, so a caller can render one discrete
state instead of the merge:
- Input identification — the
_PL*names are present in the shape's own.idataimport table; eachFF 25 <IAT>trampoline binds a code address to an import, and a base relocation pins thecmp word [trampoline], imm8operand, so the input the block compares is read directly (relocations anchor the parse — a raw byte scan would false-positive on x86, the fixups do not). - Sub-stream identification — the relocation-pinned code pointers in a block
that are neither trampolines nor the head of another block are its guarded
sub-stream entries; each is tagged with the governing
(input, compare value). - Contiguous-run attribution — a moving part is one run of
F0blocks that abut (each block's byte range ends where the next begins), because the engine's x86 falls straight through the chain. Thecmp [trampoline], immtherefore governs the sub-streams of the following blocks in the run too — including blocks that carry no compare of their own: a case's geometry commonly lands in a trailing no-cmpblock (e.g. the A10 gear guards spread across acmp-block →cmp-block → geometry-block run). The last-seen(input, value)is carried forward across the run and reset at a run break — a block whose head does not abut the previous block's end, i.e. real geometry sits between them. Attributing only thecmp-block's own pointers (a stub) left the geometry un-guarded, so a non-matching selection failed to hide it (#443). - Selection —
fx::ShState::articulation[input] = valueemits only the sub-streams whose case matches; an unset input keeps the merged default. Only the shape's own compare immediates are read; the per-shape case→variant meaning is documented by OpenFA and not transcribed.
This is #295 (expose the inputs — fx::sh_articulations, ShInfo::articulations)
and the discrete half of
#297. The parametric
patch some blocks also apply (e.g. _PLgearPos interpolated into the gear
geometry) is not reproduced — discrete states render at their authored position.
Entry contract (0xF0 → native)¶
do_start_asm (0x4D4254), the 0xF0 handler, is two instructions:
push esi ; esi = the bytecode pointer, now just past the F0 00 opcode —
ret ; i.e. the address of the embedded x86 itself. RET jumps to it.
So the interpreter transfers to the payload by push esi; ret, entering native
execution at the current stream position with esi pointing at the payload.
The x86 inherits the interpreter's register and memory state (the shared vertex
pool, view matrix, and viewer-relative position globals used elsewhere in
Engine Notes); it uses esi for position-independent access to
the sub-streams that follow it.
External references (trampolines)¶
The payload reaches the game executable globals and functions through trampolines —
6-byte indirect jumps [FF 25][target u32] whose target the shape's Phar Lap
PE relocations bind to a the game executable export by name at load time. Two kinds:
- Inputs — a global the block reads. Across the corpus these are dominated by
the
_PL*articulation-state block thatShapeSetupinitializes (independently confirmed: the same symbols appear as writers there and as trampoline reads here):
| Trampoline | Shapes | Selects |
|---|---|---|
_PLgearDown / _PLgearPos |
126 / 103 | landing-gear geometry |
_PLrightFlap / _PLleftFlap |
104 / 104 | flap geometry per side |
_PLafterBurner |
85 | exhaust/afterburner geometry |
_PLbrake |
75 | airbrake geometry |
_PLrudder |
73 | rudder deflection geometry |
_PLhook |
17 | arrestor-hook geometry |
_PLswingWing |
15 | variable-sweep wing geometry |
_PLcanardPos |
14 | canard geometry |
_PLbayOpen / _PLbayDoorPos |
10 / 6 | weapons-bay doors |
_PLvtOn / _PLvtAngle |
4 / 1 | thrust-vectoring nozzles |
_PLslats |
3 | leading-edge slats |
_effects / _effectsAllowed |
23 / 31 | render-effect gating |
brentObjId, _SAMcount, @HARDNumLoaded@8 |
12, — , 4 | effect shapes (e.g. FIRE.SH): draw driven by object id / live counts |
- Callback —
do_start_interp(0x4D4240), the bytecode entry. All 208 blocks reference it. The payload setsesito the selected sub-stream and jumps here to resume interpreting that geometry.
Runtime behavior (switch → re-enter)¶
Each block is therefore:
read <input global> ; via an FF25 trampoline
switch on its value:
case k0: esi = &substream_0
case k1: esi = &substream_1
...
goto do_start_interp ; interpret the selected sub-stream
The value→variant mapping is per-shape (e.g. _PLgearDown: 0 = retracted,
1 = extended, with 4 = strut on F117.SH). The exhaustive decode of those
case values is documented by the OpenFA
project's sh crate (GPLv3) as a symbol→state table
(_PLgearDown/_PLrightFlap/_PLslats/_PLbayOpen/_PLbrake/_PLhook/…);
the mechanism, trampoline inventory, and entry contract here are independently
derived (Ghidra on the game executable plus a structural parse of all 1275 shapes) and the
per-shape case values are attributed to OpenFA per the license boundary — never
transcribed.
Implementing this (fa-bridge#21, clean-room): for each block, read the named global, map its value to a sub-stream via the state tables, and interpret that sub-stream. The original x86 need never execute — the switch is what matters.
Inventory¶
| Metric | Count | Source |
|---|---|---|
Shapes embedding x86 blocks (reference do_start_interp) |
208 | structural parse |
…reading _PL* articulation state |
134 | structural parse |
…reading effect state (brentObjId/_SAMcount/@HARDNumLoaded@8) |
12 | structural parse |
| Shapes producing no static OBJ geometry (x86 gates everything) | 65 | fx codec (Round-Trip Notes) |
The 65 fully-gated shapes are procedural effects (FIRE.SH, FLARE.SH,
DEBRIS.SH, EXP.SH, CLOUD*.SH, …) and a few complex models (AC130.SH);
the rest are articulated aircraft whose base mesh extracts normally but whose
moving-part variants sit behind these switches. The evidence script is
AnalyzeSHX86.java.
.PTS distribution files¶
Community mod archives sometimes distribute aircraft shadow/crash shapes as
.PTS files (e.g. A10.PTS) rather than the in-LIB convention of A10_S.SH.
The binary format is identical — parse with the same SH parser. The
shadow_shape ptr in the corresponding .PT file points to the _S.SH
name; the .PTS rename is a distribution artifact only. (Unrelated to the
in-LIB PTS overlay DLL format documented in PTS.md.)
Cross-validation against OpenFA — audited¶
The instruction inventory above was audited against the
OpenFA sh crate (GPLv3; commit
7507fef5, 2024-10-29). Attribution: the mnemonic names used here
(Header, ShortEOF, Unmask, XformUnmask, JumpToFrame/Detail/Damage/LOD,
VertexBuffer, VertexInfo, SourceName, TextureFile/Index, PtrToObjEnd,
EndObject/EndShape, X86Code, and the Unk* scheme) originate from OpenFA's
reverse engineering. Facts are documented here with attribution; no code
crosses the license boundary — sizes and formulas were independently
re-validated by fx_lib's parser walking all 1275 FA_2.LIB shapes with zero
errors.
Agreement — the two inventories are identical on every checkable fact:
- Same 55 opcodes; neither project knows an opcode the other lacks.
- All fixed sizes match, including the full
Unk*set (0x08…0xEE). - All variable-size formulas match:
0x06 = 16 + u16@[14],0x0C/0x0E/0x10 = 12 + u16@[10],0x40 = 4 + u16@[2]*2,0x42 = 2 + strlen + 1,0x82 = 6 + u16@[2]*6, and the 0x6C flag arms (0x38→13, 0x48→14, 0x50→16). - Face content/layout flag bits, the header layout, the scale table
(including USNF97-only
7 → 0.5and0 → 1.0), and thepush_at % 8 == 0vertex-pool constraint all match.
Recorded differences (both inert for our export-only parser):
| Topic | This spec | OpenFA | Note |
|---|---|---|---|
0xF0 magic class |
byte-magic | word-magic (F0 00) |
both immediately delegate to x86 handling, so no parse divergence; adjudicated: the engine has no magic-class concept at all — dispatch is uniform vector_table[opcode*2] (see Engine Notes) |
EndObject extent |
consumes all remaining input, with the PtrToObjEnd/EndObject skip protocol for x86 regions | 18-byte errata heuristic, because OpenFA parses through x86 regions via trampolines instead of skipping them | different models of the same stream behavior; ours is validated by the 1275/1275 walk |
Where this spec now exceeds OpenFA: the two header fields OpenFA marks
unknown ("probably super important, but I don't know what they mean") are
resolved as radius / radius_world by engine tracing — see
Engine Notes — and OpenFA's in-source hypotheses for the
formerly open opcodes are now adjudicated by the handler decompiles: 0xC8
is the distance LOD branch, and the 0x6C/0x06-family ops OpenFA suspected
of "manual backface culling" are the draw-order selectors (both chains
always render; the plane/dot-product test only swaps the order — close to,
but not, a culling gate). 0x1E, which OpenFA treats as a NOP pad, is the
fragment return (do_short_eof = ret).
Engine Notes¶
Shape opcodes that branch on entity state are handled by the game executable functions. Confirmed from FA.SMS:
| VA | Symbol | Description |
|---|---|---|
0x4D22D4 |
do_ifdestroyed |
Tests whether the entity is in destroyed state; skips or follows a conditional branch in the shape bytecode stream |
0x4D057C |
GRAddBrentObj |
Registers a shape instance for rendering: consumes the header (radius, scale), culls, computes the projection shift, and queues a render-sort record whose stream pointer starts at header+0xe |
Header field consumption (traced)¶
GRAddBrentObj(shape, x, y, z) is the only the game executable code that touches the raw
14-byte header:
scale(+6) shifts the viewer-relative Δx/Δy/Δz into shape units.radius(+4) is OR-ed with those shifted magnitudes before the_shift_tableleading-bit lookup that selects the projection/precision shift — flooring the shift so the whole shape stays in 16-bit range — and is copied into the render-sort record (+0x10). Only the leading bit matters, which is why authored values need not be exact radii.radius_world(+2) is never read. Evidence: the interpreter receives the stream pointer already advanced to header+0xe; a scan of every decompiled the game executable function found no 16-bit or 32-bit read of header+2 co-occurring with other header-field access; and a raw-listing scan of the hand-written interpreter region (0x4CD000–0x4D7000, including 5342 instructions outside Ghidra functions) shows all 467[reg+0x2]accesses are word-magic operand fetches and zero negative-displacement reads that could reach back from the stream start (AnalyzeSHHeader.java).
Interpreter dispatch — vector_table (traced)¶
The shape interpreter is hand-written threaded code: there is no central decode loop. Every handler ends by fetching and dispatching the next instruction inline (150+ dispatch sites across 0x4C9581–0x4D6F42):
mov ax, [esi] ; AL = opcode, AH = first operand byte
lea esi, [esi+2] ; consume 2 bytes
movzx ebx, al
jmp [vector_table + ebx*2] ; 4-byte entries at opcode*2
vector_table (the FA.SMS name; .data, 0x5183A0) holds 128 dword handler
pointers addressed at opcode*2 — so only even opcodes dispatch cleanly,
and the parser-side byte-magic/word-magic distinction dissolves at engine
level: every fetch consumes [op][first-operand-byte], and handlers whose
operands start at byte 1 back up themselves (the 3-byte 0x38 handler begins
with DEC ESI). 0xFF/0x01 never reach the table in well-formed streams
(the header is consumed at queue time by GRAddBrentObj).
Handler symbols recovered from FA.SMS (the full 128-entry map is reproduced by
AnalyzeSHDispatch.java,
which also materializes the handlers as Ghidra functions):
| Op | Spec name | Handler | FA.SMS symbol |
|---|---|---|---|
0x1E |
ShortEOF | 0x4D17F4 |
do_short_eof — plain ret: returns from the current call frame (fragment end) |
0x38 |
ShortJump | 0x4D30E4 |
do_short_ijmp — DEC ESI, then falls into the 0x48 Jump handler |
0x40 |
JumpToFrame | 0x4D3134 |
do_anim_jmp |
0x42 |
SourceName | 0x4D17BC |
do_shape_name |
0x44 |
Unk44 | 0x4D42EC |
do_setcoarse |
0x46 |
Unk46 | 0x4D478C |
do_force_no_pmap |
0x50 |
LongJump | 0x4D3100 |
do_ijmp_long — [50 00][rel32], unconditional esi += rel32 |
0x6E |
UnmaskLong | 0x4D22A8 |
do_sfcal_long |
0xA6 |
JumpToDetail | 0x4D2318 |
(unnamed) — skips rel16 when detail global 0x515EEE ≥ operand threshold |
0xAC |
JumpToDamage | 0x4D22D4 |
do_ifdestroyed — esi += rel16 when _destroyed (0x50C39C, set per entity by ShapeSetup) is non-zero |
0xB2 |
UnkB2 | 0x4D2344 |
do_use_terrain_detail |
0xB8 |
UnkB8 | 0x4D22FC |
do_no_overlap |
0xC6 |
XformUnmaskLong | 0x4D33D8 |
do_icall_long |
0xC8 |
JumpToLOD | 0x4D416C |
do_jumpfar4 — skips its 6-byte operand when flag 0x515EF0 & 0x20000; otherwise computes a view-space depth dot product for the distance test |
0xCE |
UnkCE | 0x4D47A4 |
do_streamer_def |
0xD0 |
UnkD0 | 0x4D47B8 |
do_streamer_draw |
0xD2 |
UnkD2 | 0x4D4894 |
do_screen_coords |
0xDA |
UnkDA | 0x4D42C8 |
do_setlight |
0xE4 |
UnkE4 | 0x4D4A47 |
do_brush_area |
0xE6 |
UnkE6 | 0x4D4A6D |
do_brush_area_full |
0xE8 |
UnkE8 | 0x4D5475 |
do_new_smap |
0xEA |
UnkEA | 0x4D5644 |
do_new_rmap |
0xEE |
UnkEE | 0x4D4A30 |
do_brush_trans |
0xF0 |
X86Code | 0x4D4254 |
do_start_asm |
0xF2 |
PtrToObjEnd | 0x4D4258 |
do_collision_info |
0xF6 |
VertexInfo | 0x4D4308 |
do_set_point_color |
0xFC |
Face | 0x4D43DC |
do_new_poly |
Engine-only opcodes (handlers exist; absent from the 1275-file FA corpus):
0x34 → do_nop, 0x36/0x3E → do_new_pmap_or_tmap, 0x5C →
do_setcolor2, 0xEC → do_brush_solid, 0xF4 → do_set_gouraud,
0xF8 → do_drawobj000, 0xFA → do_fullpntg16, 0xFE → do_nt.
Six slots (0x14, 0x16, 0x3C, 0xA8, 0xAA, 0xC0) share
do_if_not_effect — a conditional skip keyed on the effects setting — and
ten unassigned slots point at a common stub (0x4D17E0).
These handler names are FA.SMS facts. The animation and LOD/damage families are specified below.
Animation opcodes (traced)¶
Shapes animate by free-running frame selection against a single global counter — there is no per-entity animation clock, start trigger, or authored playback rate.
0x40 JumpToFrame — do_anim_jmp (0x4D3134). Layout
[40 00][nframes u16][rel16 × nframes] (total 4 + nframes*2, matching the
skip-table formula). Runtime:
idx = _frameCounter mod nframes
target = &frame_table[idx] + (int16)frame_table[idx] ; rel16 is relative
; to its own slot
_frameCounter (0x4EB738) is a global incremented once per rendered frame in
the main flight loop (FlyingLoop, 0x404C70) as
_frameCounter = (_frameCounter + 1) & 0x7FFF, and reset to 0 on screen
transitions. Because selection is _frameCounter mod nframes, every animated
model is phase-locked to the same counter: a shape with nframes = 8
advances one frame per rendered frame and repeats every 8 frames, independent
of which entity draws it. Used by 510/1275 shapes (5610 opcodes).
Playback (for fa-bridge#19): keep a render-tick counter masked to 15 bits; at
each JumpToFrame compute counter % nframes, read that slot's signed rel16,
and branch to slot_address + rel16.
Control-flow primitives (shared by the animation and LOD/damage families —
each advances the instruction pointer esi; rel is signed and, except where
noted, relative to the end of its own operand):
| Op | Name | Layout | Behavior | Corpus |
|---|---|---|---|---|
0x48 |
Jump (0x4D30E5) |
[48 00][rel16] |
esi += rel16 |
533 files |
0x38 |
ShortJump — do_short_ijmp (0x4D30E4) |
[38][rel16] (3 bytes) |
identical to Jump; handler does DEC ESI then shares the 0x48 body |
724 files |
0x50 |
LongJump — do_ijmp_long (0x4D3100) |
[50 00][rel32] |
esi += rel32 |
0 (engine-only) |
Fragment calls and draw-order selectors (traced)¶
A shape's stream is not a linear list — it is a web of fragments linked by call-and-continue selector opcodes. Three pieces make the structure:
0x1E ShortEOF — do_short_eof (0x4D17F4). The handler is a plain
ret: it pops the current interpreter call frame. A fragment invoked by any of
the call-form opcodes below ends at its ShortEOF and control resumes in the
caller; at top level it ends the object. A run of 0x1E bytes is alignment
after the return — only the first ever executes. (Previously read as a NOP pad;
that misreading is what made static walks merge every alternative block.)
0x12 Unmask — do_unmask (0x4D2278) (728 files) and UnmaskLong
(0x6E, unused) call the referenced sub-stream through the dispatch
table's call form (FF 14, versus FF 24 for the normal tail jump): the
callee chain runs until its ShortEOF rets, then control resumes after the
opcode. The common idiom is [12 → body][38 → join][body…, 1E] — call the
inline body, then jump past it.
Draw-order selectors — the mid-stream conditionals. Each evaluates a condition, then renders both of its sub-chains: it calls one (which returns at its ShortEOF) and tail-continues at the other; the condition only swaps which is drawn first. They are painter's-algorithm ordering for overlapping parts, not visibility gates — a static reader must follow both links:
| Op | Handler | Condition | Call target | Continue |
|---|---|---|---|---|
0x6C OrderField |
sh_op_6C (0x4D23AC) |
object-record field [w0] vs w1 |
opd + w3 + 8 |
opd + 6 + w2 |
0x06 OrderPlane |
sh_op_06 (0x4D2450) |
sign of nx·(x+xv) + ny·(y+yv) + nz·(z+zv) |
opd + 16 + rel |
next instruction |
0x0C/0x0E/0x10 |
sh_op_0C/0E/10 |
two-axis plane sign (y/z, x/z, x/y) | opd + 12 + rel |
next instruction |
(opd = operand start, two bytes past the opcode. For 0x6C the 13/14/16-byte
sizes are a trailing embedded 38/48/50 jump; for the plane forms the
size-field u16 doubles as the continue link, which is why their total size is
base + u16.) A fine-detail airframe is typically one long chain of these
nodes — the A-10's fuselage, nose, and tail fragments are all reached through
0x06/0x6C links, not linear fall-through.
Sub-model draw (articulated parts). XformUnmask (0xC4, 2 files) /
XformUnmaskLong (0xC6, unused) work like Unmask but first save the view
matrix and object-position globals and apply the operand offsets to the object
position, so the sub-stream renders at a relative transform — the
attached-part mechanism. Their per-operand layout is only partially traced and
stays in Open Questions; they are not part
of frame playback.
LOD and damage-state opcodes (traced)¶
Three conditional branches choose which geometry block renders. Each jumps
forward to an alternative block when its condition selects it; the
fall-through (default) block is the primary geometry. All rel16 are signed and
relative to the end of the operand.
0xAC JumpToDamage — do_ifdestroyed (0x4D22D4). Layout [AC 00][rel16].
if (_destroyed != 0) esi += rel16 ; branch to the damaged sub-model
; else fall through to intact geometry
_destroyed (0x50C39C) is set per entity by ShapeSetup (0x4AB450) as the
shape is queued: it is (health_word == 0) read from the object record (+0xE),
with a _forceDestroyed override forcing 1. So the damaged geometry is authored
inline after the opcode and reached only for destroyed entities. Rare — 62/1275
shapes (65 opcodes), almost all ground/naval targets (e.g. BARKSA.SH).
0xA6 JumpToDetail (0x4D2318). Layout [A6 00][rel16][threshold u16].
if (_detail < threshold) esi += rel16 ; branch to the lower-detail block
; else fall through to full detail
A static quality switch on the user's detail preference _detail
(0x515EEE), independent of distance. Near-universal: 1094/1275 shapes.
0xC8 JumpToLOD — do_jumpfar4 (0x4D416C). Layout
[C8 00][size u16][pixel_threshold u16][rel16] (6-byte operand → total 8).
Distance-based. When the force-max-detail flag _effects & 0x20000 is set,
the operand is skipped and the block always renders; otherwise the handler
compares the object's projected on-screen size against the threshold:
depth = -(xv·m3 + yv·m6 + zv·m9) ; view-space Z, clamped >= 20
projected = (size * aspecty * scrh) / depth / 2 ; ~ vertical pixels on screen
threshold = (pixel_threshold * sizeAdjust) >> 8
if (projected < threshold) esi += rel16 ; too small -> next (coarser) LOD
; else fall through to this LOD
with xv/yv/zv (0x51CDAA/AC/AE) the object's viewer-relative position,
m3/m6/m9 (0x515F48/4E/54) the view-matrix depth row, aspecty
(0x51837C), scrh (0x5182F4) the screen height, and sizeAdjust
(0x51D125) a global tuning factor. LOD blocks chain: each JumpToLOD renders
its block when the object is large enough on screen, else jumps past it to the
next, coarser block. Near-universal: 1098/1275 shapes (2064 opcodes).
State-selected rendering (read codec)¶
sh_parse_mesh(data, size, ShState{}) executes the stream structurally —
following unconditional jumps (0x48/0x38), calling Unmask and draw-order
selector fragments (which return at their ShortEOF), and branching each
conditional the way the engine would for one selected state — so it emits
one state's geometry rather than every block merged. ShState selects:
destroyed—0xACJumpToDamage:false(default) falls through to the intact geometry;truetakes the branch into the damaged sub-model.ShMesh::has_damagereports whether the shape carries an inline branch at all — aircraft usually don't (their destruction is the whole-model_A…_Dswap, shape-selection.md);sh_variant_namederives those sibling names for callers that resolve the swap themselves.frame— the0x40JumpToFrame index. The codec computesidx = frame mod nframesper opcode and branches to that slot (the sameslot_address + rel16the interpreter uses, withframestanding in for_frameCounter), and reports the model's animation length asShMesh::frame_count(the maxnframesseen;0= static).lod— the0xC8JumpToLOD level:0(default) = finest …ShMesh::lod_count - 1= coarsest. The engine compares the object's projected on-screen size to each site's pixel threshold; the codec stands in a synthetic size — level 0 exceeds every threshold (all sites fall through to their finest block), level k sits just below the k-th largest distinct threshold in the shape, so exactly the engine's blocks for that apparent size render.lod_count= distinct thresholds + 1.detail— the0xA6JumpToDetail preference word (the engine's_detail): a site branches to its lower-detail block whendetail < threshold. Defaults to maximum (every full-detail block);ShMesh::has_detailreports whether any site exists.
Frame selection is applied in the base stream, inside called fragments, and
inside the x86-gated sub-streams recovered by the reloc harvest (the F-16
class keeps its 0x40 table behind control flow the harvest doesn't follow and
stays frame_count = 0). The reloc harvest itself runs only for the finest
intact path (lod == 0, no detail/LOD branch taken): its sub-streams are
authored against the finest vertex pool, and a harvest walk that skips a
VertexBuffer to protect the pool also drops the faces that follow it — they
index the skipped buffer (this is what previously drew coarse-LOD faces with
finest-pool vertices: the giant garbage polygons). The fxs preview exposes a
Destroyed toggle, a Frame slider (frame_count > 1), a LOD slider
(lod_count > 1), and a Low detail checkbox (has_detail) — docs/gui.md.
Round-Trip Notes¶
The codec is deliberately export-only: OBJ→SH is out of scope because a shape is a bytecode program (animation frames, LOD variants, damage states, embedded x86), not a plain mesh — regenerating one from a static OBJ would discard the behavioral stream.
Extraction coverage, tested against all 1275 .SH files from FA_2.LIB:
| Result | Count | % |
|---|---|---|
| Vertices + faces extracted | 1257 | 98.6% |
| No geometry (no OBJ output) | 18 | 1.4% |
| Parser crash / error | 0 | 0% |
(The counts are for the default state — finest LOD, full detail, intact, frame 0. Since the structural walk landed, per-shape face counts are one coherent state, no longer the union of every LOD/frame/damage block.)
The remaining no-geometry files are pure procedural effects that emit their
geometry entirely from x86 (no static VertexBuffer/Face at all): FIRE.SH,
FLARE.SH, BULLET.SH, CHAFF.SH, CLOUD*.SH, CRATER.SH, DEBRIS.SH,
EXP.SH, EJECT.SH, etc. (Complex airframes that were previously x86-only, such
as AC130.SH, now recover their facets via the walk-through harvest above.)
Shadow models (*_S.SH): flat ground silhouettes, Z=0, typically 6-20 faces.
Sample results:
| File | Scale | Verts | Faces (state 0) | LODs | Textures |
|---|---|---|---|---|---|
| A10.SH | 8 (1x) | 425 | 377 | 3 (377/63/10 faces) | _a10.PIC |
| A10_S.SH | 8 (1x) | 21 | 6 | 1 | (none) |
| F22.SH | 8 (1x) | 452 | 407 | 3 | |
| F15E.SH | 8 (1x) | 483 | 556 | 3 | |
| AC130.SH | 9 (2x) | 283 | 335 | 3 | |
| HANGAR.SH | 8 (1x) | 10 | 6 | 2 | |
| TREEA.SH | 8 (1x) | 19 | 11 | 3 | _treea.PIC |
Texture coordinates. Faces with HAVE_TEXCOORDS carry one (s, t) per
corner (ShFace::texcoords, parallel to indices); the codec extracts them in
texel space (origin top-left, pixels of the referenced PIC) since the shape
does not record its texture's dimensions. sh_to_obj emits them as vt lines
with f v/vt faces; a consumer normalizes by the PIC's width/height (and flips
V) for a 0..1 sampler. Example: A10.SH — 202/377 faces textured against _a10.PIC.
Further limitations: sh_to_obj exports whichever single state was parsed
(the default ShState — finest LOD, full detail, intact, frame 0 — unless the
caller selected otherwise via the in-memory parse; see
State-selected rendering). The
articulation variants merged by the reloc harvest (gear up and down, …)
remain undistinguished within that state, and there is no CLI flag to pick a
state for export yet.
Open Questions¶
1. Remaining Unk* opcode semantics¶
All opcodes have confirmed sizes (the parser walks every FA shape without
error). The control-flow families are now traced — animation (0x40, 0x48,
0x38, 0x50; see Animation opcodes), LOD/damage
(0xA6, 0xAC, 0xC8; see
LOD and damage-state opcodes), and the
fragment-call structure (0x1E ShortEOF, 0x12/0x6E Unmask calls, the
0x06/0x0C/0x0E/0x10/0x6C draw-order selectors; see
Fragment calls and draw-order selectors)
— as is the dispatch mechanism
that named a dozen render-state handlers. What remains untraced is the
render-state / attribute set (do_setcoarse, do_setlight, the
do_brush_* and do_new_smap/do_new_rmap family, the streamer pair, and the
remaining Unk* word-magic entries), the exact per-operand layout of the
Xform sub-model calls (0xC4/0xC6), and the unknown Face flag bits. These
affect surface appearance, not geometry or the animation/LOD/damage playback
contract.
Status: open — re-static (#52)
2. x86-embedded geometry regions¶
The runtime contract of these regions is specified in
X86Unknown Region: the 0xF0 → push esi; ret entry, the
FF25 trampoline reads of _PL*/effect globals, and the do_start_interp
re-entry that selects a geometry sub-stream. What remains is not a spec gap but
a codec limitation: the static fx read path skips these regions, so the
65 fully-gated shapes still export no OBJ (see Round-Trip Notes),
and the exhaustive per-shape case-value tables are carried with attribution to
OpenFA rather than re-derived here.
Status: specified — implementable by fa-bridge#21 (closed #125)
Related¶
Formats: PIC — _-prefixed skin textures referenced by
TextureFile; LIB — container (FA_2.LIB ×1275); PT —
flight-model records whose shadow_shape field names the _S.SH shadow
shapes.
Engine: shape-selection.md — how the engine picks
which .SH to draw (the whole-model damage swap and per-class _A…_D
variant set), the inter-shape counterpart to this file's intra-shape LOD/damage
opcodes; renderer.md — the shape interpreter and
rasterizer pipeline; architecture.md — Phar Lap overlay
loading.
INF — Aircraft Tech Sheet (.INF)¶
.INF files contain the technical information sheet for an aircraft, displayed
in-game on the aircraft selection screen and in the mission planner. One .INF
per aircraft; not all aircraft have one. FA_3.LIB (Disc 2) carries 269 of them,
DCL-compressed (flags=4).
Tools¶
fx¶
fx inf dump <file.INF> # parsed directive/body listing
Extract via fx lib unpack first. Edit in any plain text editor, re-pack into
a custom LIB, and configure FA to load it.
fxs¶
Opening an .INF record shows a Styled tab — each directive section
rendered with its in-game alignment and title/body weight, editable per
section (text, alignment, title/body style, insert/delete) — and a Source
tab with the raw dot-command text. Edited sections are recomposed through
fx::inf_rebuild_section; untouched sections keep their exact source bytes.
See gui.md.
File Layout¶
Plain text; no binary fields.
Custom dot-command markup — plain text, not RTF. Lines beginning with .
are formatting directives; all other lines are body text.
.body .right
Jane's All The World's Aircraft
.title .center
A-10 THUNDERBOLT II
.body .left
TITLE:
FAIRCHILD REPUBLIC THUNDERBOLT II
...
Known Directives¶
| Directive | Effect |
|---|---|
.body .right |
Body text, right-aligned (used for the "Jane's" header) |
.body .left |
Body text, left-aligned (main content) |
.title .center |
Title text, centred (aircraft name) |
The renderer is the in-game text display, not a standard RTF or HTML control. Only the directives listed above have been observed.
Content Structure¶
All observed .INF files follow the same layout (sourced from Jane's All The
World's Aircraft):
- Header: "Jane's All The World's Aircraft" right-aligned
- Title: Aircraft designation, centred
- Body sections: TITLE, TYPE, PROGRAMME, DESIGN FEATURES, FLYING CONTROLS, STRUCTURE, LANDING GEAR, POWER PLANT, ACCOMMODATION, ARMAMENT, DIMENSIONS: EXTERNAL, WEIGHTS AND LOADINGS, PERFORMANCE
- Structured footer (machine-readable performance data):
LENGTH (m): 16.26 HEIGHT (m): 4.47 WINGSPAN (m): 17.53 MAX T.O. WEIGHT (kg): 21,500 MAX WING LOAD (kg/m/2): 449.88 T.O. RUN (m): 1372 LANDING RUN (m): 762 MAX RATE CLIMB (m/min): 1828These key-value pairs are likely parsed by the engine to populate the stat display.
Round-Trip Notes¶
inf_parse keeps each section's exact source bytes (InfSection::raw);
inf_serialize concatenates them, so parse → serialize is byte-identical by
construction — line endings, blank-line runs, stat-footer separators (both
: and space formats), and unterminated or DOS-EOF tails all survive.
tests/test_inf.cpp asserts it on synthetic samples; verified across all
269/269 .INF entries in FA_3.LIB (Disc 2). The stats map is a derived
view — extraction no longer removes footer lines from section text.
Open Questions¶
1. Directive set and footer consumption¶
Only three directives (.body .right, .body .left, .title .center) have
been observed across the corpus, and the structured footer is inferred to be
engine-parsed for the stat display; the renderer and its directive dispatch in
The game executable have not been traced.
Status: open — re-static (#54)
Related¶
Formats: BRF — .PT aircraft flight model records paired with
each .INF; PIC — aircraft skin textures also in FA_3.LIB;
LIB — the container (FA_3.LIB, DCL-compressed entries).
HGR — Hangar Screen (.HGR)¶
FA_2.LIB contains 2 .HGR files. Each defines a hangar screen — the aircraft
selection and loadout interface shown at an airbase. Each is a Win32 PE DLL
loaded at runtime; H_AIRB.HGR decompresses to 4608 bytes.
Tools¶
fx¶
fx hgr info <file.HGR> # container check + referenced PIC assets
fx hgr strings <file.HGR> [-n MIN] # embedded strings
Same MZ + Phar Lap PL container family as CAM. Both shipped
files surface their documented references (h_airb.PIC twice +
SELICONS.PIC; the carrier variant h_airb2.PIC twice + SELICON3.PIC).
File Layout¶
All multi-byte integers are little-endian.
Win32 PE DLL with an embedded sub-resource. String analysis of H_AIRB.HGR
reveals asset references:
h_airb.PIC— hangar background image (appears twice, likely for foreground and background layers)SELICONS.PIC— aircraft selection icons displayed in the hangar UI
Slot Entry Layout — confirmed¶
Aircraft slot entries start at offset +0x27 from the sub-resource base:
30 × 8-byte entries, read by FUN_004558f0 (VA 0x4558f0):
| Offset | Type | Field | Notes |
|---|---|---|---|
+0x00 |
s16 | x_offset |
Aircraft hangar X position; -1 = slot unused (empty sentinel) |
+0x02 |
s16 | y_offset |
Aircraft hangar Y position |
+0x04 |
s16 | angle_index |
Index into angle-correction table asStack_b (0-based) |
+0x06 |
s16 | occupied |
Slot occupancy flag: 0 = free, 1 = assigned |
Screen position is computed as:
screen_x = asStack_b[angle_index * 2] + x_offset;
screen_y = asStack_b[angle_index * 2 + 1] + y_offset;
where asStack_b[5] is a 5-short correction table loaded from the sub-resource.
A separate slot-index table for the carrier path sits at +0x117 (4 ints per
entry, up to 4 entries).
File Inventory¶
| File | Purpose |
|---|---|
| H_AIRB.HGR | Air base hangar screen |
| H_AIRB2.HGR | Carrier hangar screen (confirmed via FA_2.LIB string scan) |
Engine Notes¶
_SelectPlane (HGR load trigger, called from _MISSIONInit2@0):
1. Calls FUN_004809d0 — initialises ?hangarName@@3PADA (0x004fb1e8) from
DAT_004fbbf0, and copies "ord_air3.PIC" to armPicName
2. pilotName selects carrier (non-zero) vs. land-base hangar
3. Calls SelectRepairPlane(0, &hangarName, '\0', '\x01') — actual HGR file loader
SelectRepairPlane (HGR file loader) — confirmed structure:
- Loads HGR DLL via RMAccess(name, 0x8000)
- When param_4 == '\0' (standard load): skips first 13 bytes (pcVar5 = pcVar6 + 0xd)
- Loads embedded sub-resource via RMAccessHandle(pcVar5, 0x8104)
- Iterates up to numItems aircraft types (capped at 100)
- Builds X coordinate array (local_640: 400 shorts) and Y coordinate array
(local_320: 400 shorts) for aircraft screen positions
Slot search (param_1 == '\0', standard assign): finds first slot where
x_offset != -1 and occupied == 0, copies its 8 bytes to output buffer, sets
occupied = 1.
Carrier assign path (param_1 != '\0'): walks the slot-index table at
param_5 + 0x117; finds first entry pointing to a free slot; marks all
referenced slots as occupied.
Open Questions¶
1. Sub-resource layout beyond the slot tables¶
The 13-byte skip, the 30 × 8-byte slot table at +0x27, the 5-short correction table, and the carrier slot-index table at +0x117 are confirmed; the remainder of the ~4.5 KB sub-resource (and any other embedded resources in the DLL) is unmapped.
Status: open — re-static (#54)
Related¶
Formats: PIC — h_airb.PIC and SELICONS.PIC are PIC atlas
files; MNU — menus that transition to the hangar screen.
LAY — Sky and Atmosphere Layers (.LAY)¶
FA_2.LIB contains 24 .LAY files (e.g. CLOUD1.LAY, DAY1.LAY, DAY2.LAY).
Each defines a complete atmospheric rendering configuration — sky gradient,
cloud layers, horizon, and ocean surface — used during flight. Referenced by
name from .MM theater files (layer day2.LAY 0). Each is a Win32 PE DLL
loaded at runtime.
Tools¶
fx¶
fx lay dump <file.LAY> # atmosphere parameters as JSON
fx lay gradient <file.LAY> -o gradient.png # sky colour ramp as a PNG strip
fx lay set <file.LAY> <key=value ...> [-o out] # edit header scalars / layerN.<field>
File Layout¶
All multi-byte integers are little-endian.
Win32 PE DLL (Phar Lap format, signature PL\0\0). CLOUD1.LAY decompresses
to 16896 bytes — significantly larger than other overlays (4608 bytes),
because .LAY files embed the full sky/atmosphere rendering lookup tables as
data. DAY1.LAY decompresses to 20992 bytes, confirming that file size is
not uniform across LAY variants.
String analysis, CODE section analysis, and the game executable decompilation reveal the embedded content:
wave1.SH— animated ocean wave mesh, referenced by name in both cloud and day variants (string at CODE VA ~0x11C2)_T_HorizonProc— imported frommain.dll(= the game executable; one of the 2 functions in the game executable's PE export table — see architecture.md). LAY files call the engine's horizon rendering function rather than implementing it. (An earlier claim that_T_HorizonProcis an export was incorrect — it is an import in all observed LAY files.)- DLL data header — first 0x78 bytes (0x1e dwords) of the CODE section are copied verbatim to the game executable globals at load time (full map below).
- LAYER array —
Nentries × 0x160 bytes each, at a CODE VA stored in the header. Each entry defines one sky condition (altitude range, gradient table, cloud textures, visibility). - Sky gradient tables — byte arrays in CODE, values 0x09–0x3F (CLOUD1) or 0x01–0x3C (DAY1). These are copied directly into LAYER entries at load time.
- Wave parameter block — 16 bytes at CODE VA ~0x11B0: fixed-point ocean wave amplitude/frequency parameters (identical between CLOUD1 and DAY1 — not weather-specific)
- Colour palette — 0xc0 (192) dwords at a VA in the header; copied to a working buffer used by the gradient blending pipeline.
- Colour entry table — 48-byte entries (stride 0x30); each entry
[terminator_byte][R][G][B][count:u32][colour_array:u32[]].FindNearestColorEntrywalks entries (stops whenterminator_byte != 0) computing Manhattan distance|R_entry−R| + |G_entry−G| + |B_entry−B|to find the nearest match for each LAYER'sbase_rgb. The match pointer is then stored in the LAYER'scolour_entry_ptr(+0x3A).colour_array(starting at entry+8) containscount4-byte palette indices used byFUN_004b3410to look up a rendering colour given a brightness factor.
CODE Section Layout — confirmed¶
Unlike other small overlays (4608 bytes, CODE at file offset 0x200), LAY
files have a larger PE header structure:
| Section | VA | VSize | File offset | File size |
|---|---|---|---|---|
CODE |
0x1000 | 0x34D6 | 0x400 | 0x3600 |
.idata |
0x5000 | 0x5C | 0x3A00 | 0x200 |
.reloc |
0x6000 | 0x400 | 0x3C00 | 0x400 |
$$DOSX |
0x7000 | 0x200 | 0x4000 | 0x200 |
(CLOUD1.LAY dimensions shown. DAY1.LAY CODE vsize = 0x40C6, file offset still 0x400.)
The CODE section contains all rendering data. The engine interprets this data;
_T_HorizonProc (0x4AACF0 in the game executable, confirmed from FA.SMS) is the horizon
renderer called by the LAY DLL.
CODE section structure (VA offsets from 0x1000)¶
| VA range | Content |
|---|---|
| 0x1000–0x1077 | DLL data header — 0x78 bytes copied verbatim to the game executable globals at load time |
| 0x1078–0x10AF | Layer parameter sub-block — VA back-pointer (0x1078), u32 count=38, INT_MAX sentinel (0x7FFFFFFF), u32 5000, u32 165, and additional u32 fields |
| 0x10B0–0x1175 | Sky gradient / colour sub-block — 8-byte header + 190 bytes of palette index data |
| 0x1176–0x11A5 | Zero-fill padding |
| 0x11A6–0x11FF | Wave / scene parameter block — u32 counts, VA pointer, wave amplitude/frequency bytes, "wave1.SH" string, INT_MAX sentinel, more VAs |
| 0x1200+ | Second gradient block + additional sub-tables; LAYER array at a VA specified in the header |
DLL data header (VA 0x1000, first 0x78 bytes)¶
These 30 dwords are copied verbatim into the game executable's BSS segment at hdr when the
LAY file is loaded. After relocation all pointer fields hold absolute VAs
within the loaded DLL image.
| Header offset | the game executable global | Role |
|---|---|---|
+0x00 |
hdr | tint-table pointer [0] — _hdr (0x580DB0) is the base of the per-tint tint-table pointer array that @WRSetTint@4 (0x4B3170) indexes by tint level (_currentTintTable = (&_hdr)[tint]); this entry is tint level 0's table. +0x04/+0x08/+0x0C are levels 1–3 (copied to _DAT_0055be28/2c/30 at init) |
+0x04 |
DAT_00580db4 | → _DAT_0055be28 |
+0x08 |
DAT_00580db8 | → _DAT_0055be2c |
+0x0C |
DAT_00580dbc | → _DAT_0055be30 |
+0x10 |
DAT_00580dc0 | VA of default LAYER entry → copied to active-layer ptrs (currentTintTable, currentShadeTable, DAT_005843c4, DAT_005843c8) at load |
+0x14 |
DAT_00580dc4 | sky_angle_scale — multiplied by above-horizon elevation angle, shifted >>8 to get sky_layer_array index; used by SetActiveLayerByAngle |
+0x18 |
DAT_00580dc8 | sky_layer_array[0] — first element of 10-entry LAYER-ptr array indexed by above-horizon angle ((&DAT_00580dc8)[idx]); indices 1–6 also aliased to _DAT_0055be34.._DAT_0055be48 by ParseLayerFile; indices 7–9 at +0x34–+0x3C (no named alias — accessed only via pointer arithmetic) |
+0x1C |
DAT_00580dcc | sky_layer_array[1] → _DAT_0055be34 |
+0x20 |
DAT_00580dd0 | sky_layer_array[2] → _DAT_0055be38 |
+0x24 |
DAT_00580dd4 | sky_layer_array[3] → _DAT_0055be3c |
+0x28 |
DAT_00580dd8 | sky_layer_array[4] → _DAT_0055be40 |
+0x2C |
DAT_00580ddc | sky_layer_array[5] → _DAT_0055be44 |
+0x30 |
DAT_00580de0 | sky_layer_array[6] → _DAT_0055be48 |
+0x34 |
DAT_00580de4 | sky_layer_array[7] — indices 7–9 are the array's high-angle tail; reachable when (angle × sky_angle_scale) >> 8 ≥ 7; no _DAT_0055be* alias assigned |
+0x38 |
DAT_00580de8 | sky_layer_array[8] |
+0x3C |
DAT_00580dec | sky_layer_array[9] |
+0x40 |
DAT_00580df0 | below_angle_scale — multiplied by (−0xC0 − angle) and shifted >>6 for below-horizon layer index; used by SetActiveLayerByAngle when angle < −0xC0 |
+0x44 |
DAT_00580df4 | below_layer_array[0] — first element of 10-entry LAYER-ptr array for below-horizon/underwater angles; indices 1–6 aliased to _DAT_0055be4c.._DAT_0055be60; indices 7–9 at +0x60–+0x68 |
+0x48 |
DAT_00580df8 | below_layer_array[1] → _DAT_0055be4c |
+0x4C |
DAT_00580dfc | below_layer_array[2] → _DAT_0055be50 |
+0x50 |
DAT_00580e00 | below_layer_array[3] → _DAT_0055be54 |
+0x54 |
DAT_00580e04 | below_layer_array[4] → _DAT_0055be58 |
+0x58 |
DAT_00580e08 | below_layer_array[5] → _DAT_0055be5c |
+0x5C |
DAT_00580e0c | below_layer_array[6] → _DAT_0055be60 |
+0x60 |
DAT_00580e10 | below_layer_array[7] — high-angle tail; no _DAT_0055be* alias |
+0x64 |
DAT_00580e14 | below_layer_array[8] |
+0x68 |
DAT_00580e18 | below_layer_array[9] |
+0x6C |
DAT_00580e1c | VA of colour entry table (stride-0x30 entries: [term][R][G][B][count u32][colour_array…]) |
+0x70 |
DAT_00580e20 | VA of palette buffer (0xc0 × 4 bytes) — copied to working gradient buffer |
+0x74 |
DAT_00580e24 | VA of LAYER array (0x160-byte entries, terminated by entry[0] bit 0) |
LAYER Struct Layout (0x160 bytes)¶
Confirmed from the game executable decompilation of WRInit, FUN_004b3be0,
FUN_004b3cb0, and WRWeatherEffects.
| Offset | Type | Description |
|---|---|---|
+0x00 |
u8 | Flags — bit 0: end-of-array sentinel; bit 1: brightness-gradient enabled |
+0x02 |
s32 | sel_alt_min — raw altitude lower bound; CopyLayersToRuntime selects this layer when sel_alt_min ≤ currentTimeOfDay ≤ sel_alt_max |
+0x06 |
s32 | sel_alt_max — raw altitude upper bound (same scale as currentTimeOfDay) |
+0x0A |
s32 | alt_min — rendering lower bound in altitude >> 8 units; GetLayerBoundary compares param_1 >> 8 against this field |
+0x0E |
s32 | alt_max — rendering upper bound in altitude >> 8 units |
+0x12 |
s32 | fog_alt_low — lower altitude bound of fog visibility ramp; below this GetFogColour returns vis_lo index |
+0x16 |
s32 | vis_lo — fog visibility at fog_alt_low (0x00..0xFF; scaled to colour_array index as vis_lo × count >> 8) |
+0x1A |
s32 | fog_alt_high — upper altitude bound of fog visibility ramp; above this, vis_hi is used |
+0x1E |
s32 | vis_hi — fog visibility at fog_alt_high; between bounds the engine linearly interpolates |
+0x22 |
s32 | extinction_param — used by GetLayerVisibility to compute layer-range extinction value |
+0x26 |
s32 | gradient_alt_start — altitude at which brightness gradient begins |
+0x2A |
s32 | gradient_val_start — blend intensity at gradient_alt_start (0..0x100) |
+0x2E |
s32 | gradient_alt_end — altitude at which gradient reaches full intensity |
+0x32 |
s32 | gradient_val_end — blend intensity at gradient_alt_end (0..0x100) |
+0x36 |
u8[3] | base_rgb — 3-byte RGB colour used as blend target for brightness gradient and for nearest-match colour table lookup |
+0x3A |
ptr | colour_entry_ptr — pointer to matching entry in colour-matching table; set at load time by FindNearestColorEntry |
+0x3E |
u8[93] | zenith_grad — 31 × 3-byte RGB entries from zenith toward horizon (indices 0–30) |
+0x9B |
u8[96] | horizon_grad — 32 × 3-byte RGB entries from horizon downward (indices 0–31) |
+0xFB |
u8[3] | horizon_base_rgb — 3-byte RGB interpolated by InterpolateLayers (via LerpRGB) separately from horizon_grad. T_DefaultHorizon reads +0xFC (the Green byte) as the overcast/rain horizon base colour. |
+0xFE |
u32 | fog_density — per-frame fog opacity; updated each frame by WRFogLayerUpdate (random jitter ±25, clamped to [0xD9, 0xEB] = [217, 235]) |
+0x102 |
char[] | cloud_pic — ASCIIZ wildcard pattern for cloud PIC texture (e.g. SKY*4.PIC); loaded by LoadPICByWildcard |
+0x118 |
char[] | sky_pic — ASCIIZ wildcard pattern for secondary sky PIC texture |
+0x136 |
ptr | activate_fn — function pointer called by CopyLayersToRuntime when this layer is selected; NULL for most layers |
+0x14E |
u8 | visibility — minimum visibility/opacity byte; consulted by layer-range queries |
The LAYER array is terminated when an entry's flag byte has bit 0 set. The
array pointer is stored at offset 0x74 in the DLL data header (global
DAT_00580e24 in the game executable).
Identified sub-block types (from CODE inspection)¶
| Sub-block type | Example VA | Description |
|---|---|---|
| Identity table | 0x3CF8 | 256-byte passthrough: 00 01 02 ... FF. Used when no colour remapping is needed. |
| Colour remap table | 0x34F8 | 256-byte table mapping code indices to upper-palette entries (0xB4–0xBF = sky blue palette range in EGA/VGA extended palette). |
| RGB triplet array | 0x15F8 | 3-byte tuples in (R, G, B) form. Observed values: 00 3F 3F = VGA full-brightness teal/cyan. |
Sky gradient sub-block (0x10B0)¶
This sub-block sits between the DLL data header and the wave parameter block. It is not the LAYER array; it is one of the colour sub-tables referenced from within LAYER entries via the colour entry table.
31 00 00 00 00 00 10 10 ← 8-byte header
0E 3F 3F 3F 3B 3B 3C 38 38 39 35 35 36 31 31 32 ← gradient data begins
31 31 32 32 32 33 32 32 33 33 33 33 33 33 34 34
34 34 34 34 35 34 34 35 35 35 36 36 36 37 35 35
36 ...
Header fields:
| Offset | Type | Value | Meaning |
|---|---|---|---|
+0 |
u32 | 0x31 = 49 | Builder metadata — not read by the game executable at runtime |
+4 |
u16 | 0 | Builder metadata |
+6 |
u8 | 0x10 = 16 | Builder metadata |
+7 |
u8 | 0x10 = 16 | Builder metadata |
No the game executable function reads these four bytes. The engine accesses gradient and
colour data exclusively via pre-initialised pointers stored in LAYER struct
entries (e.g. colour_entry_ptr at +0x3A) and in the DLL data header; it never
decodes this 8-byte preamble. The header is a LAY file builder artefact —
present in all observed files but opaque to the runtime.
Gradient data (190 bytes, VA 0x10B8–0x1175) encodes sky colour as VGA 6-bit palette indices (range 0–63). Values are non-monotonic — not a simple linear ramp. The curve represents sky brightness/colour as a function of altitude band, e.g. bright at horizon (0x3F=63) transitioning through mid-sky blues (0x31–0x37) and darker values toward zenith. Multiple sequential sub-tables follow within the 190-byte range.
Wave / scene parameter block (~0x11A6)¶
66 00 00 00 ← u32 0x66=102
DD 02 00 00 ← u32 0x2DD=733
00 00 00 00
D0 44 00 00 ← VA pointer (0x44D0, within CODE)
FE 1F 38 0E 70 62 00 00 30 0B 01 00 18 47 E8 B8 ← wave amplitude/freq params
4B 64 64 64 64 ← 5 bytes
77 61 76 65 31 2E 53 48 00 ← "wave1.SH\0" — ocean mesh
FF FF FF 7F ← INT_MAX sentinel
94 11 00 00 ← VA 0x1194
1C 25 00 00 ← VA 0x251C
...
"wave1.SH" is the ocean wave mesh loaded by the LAY DLL. The wave parameters
(16 bytes preceding the string) are identical across all CLOUD1 and DAY1
variants — weather state does not affect wave motion physics.
File Inventory¶
All 24 files live in FA_2.LIB.
Suffix naming convention — theater variants¶
Each base LAY name has up to five theater-specific variants, identified by a
single-letter suffix. The suffix assignment is confirmed by reading the layer
directive in every .MM theater file:
| Suffix | Theater | Example MM | LAY referenced |
|---|---|---|---|
| (none) | Ukraine, Kurile, Vietnam (Tonkin) | UKR.MM, KURILE.MM, TVIET.MM |
day2.LAY |
B |
Baltic | BAL.MM |
day2b.LAY |
E |
Egypt | EGY.MM |
day2e.LAY |
F |
France | FRA.MM |
day2f.LAY |
V |
Vladivostok | VLA.MM |
day2v.LAY |
T |
(unused — no MM file references *t.LAY) |
— | — |
The T-suffix files (cloud1t.LAY, day2t.LAY) are shipped in FA_2.LIB but
never loaded at runtime — no .MM file contains a layer *t.LAY directive.
They may be a residual from a cut theater or a tool-generated placeholder.
The ~*F.MM suffix pattern (e.g. ~BALF.MM, ~EGYF.MM) is unrelated to the
LAY suffix — in .MM filenames F denotes the campaign finale mission
map, not France.
CLOUD1 vs DAY1 comparison¶
| Property | CLOUD1 | DAY1 |
|---|---|---|
| Total file | 16896 bytes | 20992 bytes (+4096) |
| CODE VSize | 0x34D6 (13526) | 0x40C6 (16582) |
| .reloc size | 0x400 (1024) | 0x800 (2048) |
| Differing bytes | — | 15202/16896 |
DAY1 has 3056 more bytes of CODE and 1024 more bytes of relocation data. The extra CODE contains additional sky gradient sub-tables and colour remap tables for clear-day conditions (no cloud cover). Nearly all bytes differ because inserting new sub-blocks shifts the VA space and invalidates most pointer values throughout the file.
CLOUD1B vs CLOUD1 — functionally identical, not byte-identical¶
Binary diff of CLOUD1B.LAY vs CLOUD1.LAY shows 3 bytes different, all
at PE header offsets 0x0088–0x008A (inside the Phar Lap PE header, before
the CODE section at file offset 0x400). The gradient tables, LAYER array,
wave params, and all rendering data are identical. The 3-byte difference is a
PE-level field (likely checksum or build timestamp) updated by the LAY builder
tool when it generated the Baltic overcast variant. Visually, Baltic overcast
and Ukraine overcast are the same sky configuration — the difference is
metadata only.
Engine Notes¶
Brightness gradient mechanics (FUN_004b3cb0)¶
When bit 1 of the flags byte is set, the engine dynamically tints the LAYER's gradient tables based on current altitude:
- Factor =
(altitude − gradient_alt_start) × 0x100 / (gradient_alt_end − gradient_alt_start), clamped to[0, 0x100]. - Horizon band (
+0x9B..+0xFA, 32 entries): each RGB triple is linearly blended towardbase_rgbbyfactor. Full-intensity factor = fully tinted to base colour. - Lower zenith band (
+0x98..+0x6Edescending, 14 entries): same blend but factor decreases byfactor/15per step — creates a smooth zenith-to-horizon brightness gradient.
Linear blend formula (used throughout):
dst = dst + (src − dst) × factor >> 8 (per-channel).
Fog colour mechanics (FUN_004b3410 — GetFogColour)¶
Each LAYER defines a piecewise-linear altitude→visibility ramp:
- If altitude ≤
fog_alt_low(+0x12): usevis_lo(+0x16) as the colour-array fraction. - If altitude ≥
fog_alt_high(+0x1A): usevis_hi(+0x1E). - Between: linearly interpolate
(vis_hi − vis_lo) × (alt − fog_alt_low) / (fog_alt_high − fog_alt_low) + vis_lo. - Multiply result by
colour_entry.count, shift >>8 to get the final colour-array index. - Return
colour_array[index]from the entry pointed to bycolour_entry_ptr(+0x3A). - Return value is a bool:
altitude ≤ extinction_param(i.e., within the extinction/fog zone).
WRSetRemaps (GetFogColourAtBoundary) blends the colour from two adjacent
layers when near an altitude transition boundary, clamping the ramp fields
against per-frame weather globals before calling GetFogColour.
Angle-based layer selection (SetShadingTable — SetActiveLayerByAngle)¶
The atmosphere changes depending on the view elevation angle (passed in AX register as a signed 16-bit value):
- Above horizon (angle ≥ 1): index =
(angle × sky_angle_scale) >> 8; selectsky_layer_array[index](header +0x18 base, up to 7 entries at +0x18..+0x30). - At or near horizon (−0xC0 ≤ angle ≤ 0): use
sky_layer_array[0](header +0x18). - Below horizon (angle < −0xC0, and
lighteningAllowedflag set): index =((−0xC0 − angle) × below_angle_scale) >> 6; selectbelow_layer_array[index](header +0x44 base, entries at +0x44..+0x5C).
The result is written to currentShadeTable (secondary active-layer pointer
used by the rendering pipeline).
Confirmed Functions¶
| Address | Name | Role |
|---|---|---|
0x004b4370 |
ParseLayerFile |
Load LAY DLL, copy header to globals, init LAYER array |
0x004b46d0 |
FreeLayerFile |
Close/free the loaded LAY DLL handle |
0x004b3170 |
GetLayerByIndex |
currentTintTable = (&hdr)[param_1] — select active layer |
0x004b3750 |
CopyLayersToRuntime |
Copy LAYER entries from DLL data into curLayers array |
0x004b3820 |
InterpolateLayers |
Blend two LAYER structs based on altitude fraction |
0x004b3be0 |
GetLayerAtAltitude |
Search curLayers for entry spanning the given altitude; interpolate |
0x004b3ad0 |
FindNearestColorEntry |
Manhattan-distance nearest-colour match in the colour entry table |
0x004b3b60 |
LerpInt |
*dst += (src − *dst) × factor >> 8 |
0x004b3b80 |
LerpRGB |
Per-channel lerp on 3-byte RGB entry |
0x004b3cb0 |
ApplyBrightnessGradient |
Altitude-driven tint of zenith/horizon RGB bands |
0x004b3d90 |
UpdateSkyState |
Per-frame: smooth-transition all atmosphere parameters, apply to working palette |
0x004b4170 |
UpdateAuroraClouds |
Aurora/cloud density update based on aircraft altitude and weather flags |
0x004b4680 |
LoadPICByWildcard |
Parse * wildcard range from LAYER string field, call Sprintf to load PIC |
0x004b46f0 |
SetSkyActive |
DAT_0050c8b8 = param_1 |
0x004b4700 |
ClearFrameColorTable |
Zero the 0xc0-dword frame colour buffer |
0x004b4720 |
GetLayerVisibility |
Walk LAYER entries in altitude range, return minimum visibility byte |
0x004b3190 |
GetLayerBoundary |
Search curLayers (base curLayers) at stride 0x160 for entry spanning alt >> 8; sets *param_2 = 1 if at a layer transition boundary |
0x004b3410 |
GetFogColour |
(layer, altitude, *out_colour) → bool; linearly interpolates vis_lo..vis_hi over fog_alt_low..fog_alt_high, looks up colour_array[idx] via colour_entry_ptr; returns altitude ≤ extinction_param |
0x004b31f0 |
GetFogColourAtBoundary |
Wrapper: calls GetFogColour for both the primary layer (DAT_00580d90) and the boundary layer found by GetLayerBoundary; blends output by altitude position within the transition zone |
0x004b4320 |
WRFogLayerUpdate |
Per-frame fog update: add random jitter in [−25, +26] to LAYER+0xfe, clamp to [0xD9, 0xEB] |
0x004b4790 |
ClearAtmosphereBuffer |
Clears 0x843 dwords at DAT_00581140; called at end of ParseLayerFile |
0x004cc4b4 |
SetActiveLayerByAngle |
Sets currentShadeTable (secondary active-layer ptr) from a signed 16-bit elevation angle: above horizon → sky_layer_array[angle × sky_angle_scale >> 8]; near/at horizon → sky_layer_array[0]; below −0xC0 → below_layer_array[(−0xC0 − angle) × below_angle_scale >> 6] |
0x004aacf0 |
T_DefaultHorizon |
Default horizon renderer (the game executable); reads colour bytes from currentTintTable+0xD4..+0xFC (active LAYER colour table); calls SolidHorizon / GouraudHorizon for gradient rendering |
Round-Trip Notes¶
lay_repack (#99) rebuilds a LAY DLL around edited header fields and
layers. The write path re-emits only what the parser models:
- Header — the angle scales and the sky/below band tables (which
select the LAYER each altitude band uses) are editable. The structural
VAs (
layer_array_va,colour_entry_table_va,palette_buffer_va) must match the original — their tables cannot be relocated by a field rewrite, so edits reject. - Layers — every parsed field, including both gradient ramps, is
written back at its slot offset. The layer count and each layer's
end-sentinel bit (flags bit 0) must match the original: moving the
sentinel would change the array length the engine walks.
cloud_pic/sky_picfit their fixed 22-byte slots (NUL kept when shorter). - Everything else — PE headers, the colour/palette lookup tables, and
unmodelled layer bytes (
+0x01,+0x39–0x3D,+0x12E–0x14D,+0x14F–0x15F) carry over verbatim.
An unedited parse→repack is therefore byte-identical; proven per-overlay
over all 24 install LAYs (tests/test_lay.cpp, FX_FA_ROOT census).
Related¶
Formats: MM — theater files that reference .LAY files via the
layer keyword; SH — wave1.SH is the ocean wave mesh loaded by
.LAY; PIC — ocean*06.PIC ocean texture atlas and the
cloud_pic/sky_pic wildcard textures.
Engine: architecture.md — the overlay DLL loading architecture and the game executable's export table.
Audio
11K — Raw PCM Audio (.11K / .5K / .8K)¶
FA audio files are raw, headerless, signed 8-bit mono PCM. The sample rate is encoded in the file extension:
| Extension | Sample rate | Notes |
|---|---|---|
.5K |
5512 Hz | |
.8K |
8000 Hz | Confirmed via TOOLKIT LIBPTR cache |
.11K |
11025 Hz | Most common |
.22K |
22050 Hz | Supported by TOOLKIT; not observed in FA LIBs (may be ATF/USNF only) |
Tools¶
fx¶
fx audio info <file.11K|.5K|.8K> # sample rate, sample count, duration
fx audio unpack <file.11K|.5K|.8K> [-o out.wav] # raw PCM → WAV
fx audio pack <in.wav> -o <out.11K|.5K|.8K> # WAV → raw PCM
[-r 11025] # override sample rate (default from ext)
The output extension determines the stored sample rate when packing.
Input WAV must be mono, 8-bit; fx rejects stereo or 16-bit input.
Other Tools¶
Audacity can also import the raw PCM file directly without the fx step
(File → Import → Raw Data: signed 8-bit, mono, sample rate from extension).
- Audacity — free, cross-platform; raw import, noise reduction, pitch/tempo tools
- Adobe Audition
$— paid; professional mastering and spectral repair
File Layout¶
Single-byte samples; no multi-byte integers.
- Signed 8-bit (
int8_t), range −128..127 - Mono (1 channel)
- No header, no footer — the file is raw samples from byte 0
Filename Prefix Conventions¶
The filename prefix (before any letters) is an engine convention, not a format difference:
| Prefix | Meaning | Example |
|---|---|---|
& |
Looping ambient / cockpit sound | &AFTB2.11K |
^ |
Voice / radio callout (one-shot) | ^ENGAGE.11K, ^MLTRY-A.11K |
fx lib unpack maps & and ^ to _ on extraction because Windows rejects
those characters in filenames. The original names are preserved in memory for
patching.
WAV Conversion¶
WAV stores 8-bit audio as unsigned (0..255), not signed. Apply ^ 0x80 on
both directions:
// FA raw → WAV sample
wav_byte = (uint8_t)((int8_t)fa_byte + 128); // equivalently: fa_byte ^ 0x80
// WAV sample → FA raw
fa_byte = (int8_t)(wav_byte - 128);
WAV header (mono, 8-bit, sample rate R):
RIFF chunk: "RIFF" + (file_size - 8) u32LE + "WAVE"
fmt chunk: "fmt " + 16 u32LE + 1 u16LE (PCM) + 1 u16LE (channels)
+ R u32LE (sample rate) + R u32LE (byte rate)
+ 1 u16LE (block align) + 8 u16LE (bits per sample)
data chunk: "data" + sample_count u32LE + [samples]
To create a .5K variant of a sound: pack at 11025 Hz after doubling playback
speed 2×, then rename the extension to .5K. The engine plays it at half rate,
yielding the correct pitch with reduced quality.
Engine Notes¶
AIL Runtime Integration (the game executable)¶
The game executable calls the Miles Audio Interface Library (AIL) API directly. The following API calls and globals were confirmed via Ghidra analysis of the The game executable main binary:
Initialization sequence:
_AIL_startup_0();
_AIL_set_preference_8(4, 0x78); // MIDI preference
_AIL_midiOutOpen_12(&_musicDriverHandle, 0, 0xFFFFFFFF); // open MIDI driver
_AIL_lock_0();
_AIL_set_XMIDI_master_volume_8(...);
_AIL_unlock_0();
_AIL_register_timer_4(&_PollMod__YGXK_Z) → _timerHandle; // register poll callback
_AIL_set_timer_frequency_8(_timerHandle, 0x1e); // 30 Hz
_AIL_start_timer_4(_timerHandle);
_AIL_set_preference_8(0xf, 0);
_AIL_set_preference_8(0xe, 0x4000);
_AIL_set_preference_8(0, 0x10);
_AIL_set_preference_8(2, 0x28f);
_AIL_waveOutOpen_16(&_soundDriverHandle, 0, 0, &fmt); // 22050 Hz, stereo, 16-bit
// Allocate mix channels (loop):
_AIL_allocate_sample_handle_4(_soundDriverHandle) → _mixChanHandle[i]
Runtime globals:
| Global | Type | Role |
|---|---|---|
_musicDriverHandle__3PAU_MDI_DRIVER__A |
MDI_DRIVER* | MIDI driver handle |
_soundDriverHandle__3PAU_DIG_DRIVER__A |
DIG_DRIVER* | Digital audio driver handle (22050 Hz stereo 16-bit) |
_timerHandle__3JA |
HTIMER | 30 Hz poll timer for _PollMod |
_mixChanHandle__3PAPAU_SAMPLE__A |
HSAMPLE[] | Per-channel digital audio handles |
_ailActive__3DA |
bool | AIL system active flag |
Channel teardown: _AIL_end_sample_4(channel) — called per channel on
shutdown.
The WAIL32.DLL and msapi.dll are the AIL wrapper DLLs loaded at runtime. Their import surface has not been traced (no Ghidra output available for the secondary project). IP.EXE audio path is also uncharted.
Round-Trip Notes¶
The format is raw samples with no derived fields, so WAV → PCM → WAV round
trips are byte-identical; tests/test_audio.cpp asserts the PCM round-trip.
The ^ 0x80 signedness flip is exactly self-inverse.
An earlier draft of this page described .MUS files as named pointers to
.11K audio; the confirmed decode shows .MUS playlists drive .XMI
MIDI tracks instead — see MUS.md for the corrected music-slot table
and modding workflow.
Related¶
Formats: LIB — container archives (and the &/^ extraction
mapping); MUS — the music playlist DLLs (MIDI, not PCM);
CB8 — FMV clips pair with a .11K of the same stem;
VDO — briefing videos share one .11K narration per 3-character
group prefix (AAC.11K for AACA…AACE), not per full stem.
XMI — Extended MIDI Audio (.XMI)¶
FA_2.LIB contains 78 .XMI files. XMI is Miles Sound System's Extended MIDI
format — a compact IFF-based variant of Standard MIDI that supports multiple
independent sequences in a single file. Confirmed by hex analysis: magic
FORM...XDIR. All 78 are in FA_2.LIB only (FA_1.LIB contains none; FA_3.LIB
is disc-2 content).
Tools¶
fx¶
fx xmi info <file.XMI> # sequences, timbres, chunk inventory
fx xmi export <file.XMI> [-s N] -o out.mid # sequence N (default 0) -> Standard MIDI
fx xmi export writes a Standard MIDI File (format 0) from the selected
sequence: the AIL delay encoding is rewritten to SMF variable-length deltas
and each note-on's XMI duration becomes a scheduled note-off. XMI→MID is a
one-way translation, not a byte-identity round-trip. All 78 stock .XMI
files export to structurally valid SMF (balanced note-on/note-off pairs,
monotonic deltas, proper end-of-track); the output parses in independent
MIDI libraries.
File Layout¶
IFF-style chunk structure — chunk sizes are big-endian, unlike every other FA format. Well-documented external format.
| Offset | Chunk | Description |
|---|---|---|
0x00 |
FORM | IFF outer envelope |
0x04 |
(size) | u32 BE: total content size |
0x08 |
XDIR | Extended MIDI directory marker |
0x0C |
INFO | Sequence count block |
| — | CAT | Sequence catalog |
| — | FORM XMID | One entry per MIDI sequence |
| — | TIMB | Instrument/timbre table |
| — | EVNT | MIDI event stream (AIL delta encoding) |
Key differences from Standard MIDI:
- Fixed 120 BPM base; tempo encoded as AIL-specific multipliers
- Delta times use AIL's variable-length encoding (not SMF)
- Multiple sequences in one file via the CAT/XMID structure
TIMB chunk (instrument table)¶
u16 entry count (little-endian), then count × 2 bytes — one (patch,
bank) pair per timbre the sequence uses. AIR003.XMI carries 18 entries in a
38-byte chunk (2 + 18×2). The per-entry field roles beyond patch/bank are
not independently verified here (see Open Questions).
EVNT chunk (event stream) — decoded and validated¶
The event stream is Standard-MIDI status bytes with two AIL differences,
recovered from the 78-file corpus and validated by the fx xmi export
round-trip to SMF:
- Delay (interval) encoding. Between events, every byte
< 0x80accumulates into the delay for the next event (a sum-of-bytes VLQ, not the SMF 7-bit VLQ). A delay of 256 ticks encodes as7F 7F 02. The first byte≥ 0x80ends the delay and is the event's status byte. - Note-on carries an explicit duration. A
0x9nnote-on is followed by note, velocity, and a duration as a standard 7-bit VLQ; there is no matching note-off in the stream. The exporter emits the note-on at the current tick and schedules a note-offdurationticks later. - All other events are Standard MIDI, with the usual data-byte counts:
0x8n/0xAn/0xBn/0xEn(two data bytes),0xCn/0xDn(one),0xF0/0xF7sysex (SMF-VLQ length + data), and0xFFmeta (type + SMF-VLQ length + data).FF 2Fends the sequence. Status bytes are always explicit — running status cannot occur because data bytes (< 0x80) would be consumed as delay.
Open Questions¶
1. AIL tempo scaling and TIMB entry semantics¶
Two residual details are externally documented (Miles Sound System) but not
independently verified against FA's files here: the exact mapping from AIL's
tempo multipliers to microseconds-per-quarter-note (the exporter emits a
default 120 BPM and passes any in-stream FF 51 tempo meta through
unchanged), and the meaning of the TIMB entry fields beyond the (patch,
bank) pair. Neither affects the note content of the exported MIDI.
Status: open — re-static (#54)
Related¶
Formats: MUS — playlist DLLs that select XMI tracks by index;
11K — PCM audio formats (.5K, .11K) used for sound effects and
voice.
MUS — Music Playlist Sequencer (.MUS)¶
FA_2.LIB contains 9 .MUS files (e.g. M_AIR.MUS). These control background
music playback. Each is a Win32 PE DLL loaded at runtime whose CODE section
holds a bytecode script (not compiled x86) that sequences .XMI track
playback; the .XMI files in FA_2.LIB are the actual audio.
Tools¶
fx¶
fx mus dump <file.MUS> # raw opcode stream; FB <idx> resolved to XMI filenames
The disassembler is fx::mus_disassemble in lib/src/mus.cpp
(api.md § mus.h); the fx mus dump CLI and the fxs music
editor are thin consumers of it. Direction is read only: the MUS CODE
section is a compiled DLL consumed by Miles as-is, with no authoring format
to write back — music is modded by swapping the referenced XMI tracks
(§ Replacing in-game music), not by re-emitting the DLL (round-trip
decision resolved in #101; see the front-matter rationale).
File Layout¶
All multi-byte integers are little-endian.
Each file contains a standard DOS MZ stub (128 bytes) followed by a
Phar Lap PE header (PL\0\0) at offset 0x80 (pointed to by the u32 at
0x3C). The bytecode CODE section begins at offset 0x200. All observed
.MUS files decompress to 4608 bytes. String analysis of M_AIR.MUS
yields only the standard PE header strings — no embedded .XMI track names
are visible as plain text, confirming XMI references are encoded as integer
indices resolved at runtime.
Bytecode Script Format — confirmed¶
| Opcode | Length | Meaning |
|---|---|---|
FF <name\0> |
variable | Playlist identifier string (e.g. "air") |
FA <sub> <u32> |
6 bytes | Setup/config; confirmed sub values: 0x21, 0x32, 0x50, 0x19 |
FB <mode> <idx> F9 |
4 bytes | Play XMI track <idx>; confirmed mode values: 0x50, 0x5A, 0x32, 0x19 |
FB <mode> <idx> |
3 bytes | Play XMI track — short form (no F9 terminator); appears in M_LAUNCH context |
FC |
1 byte | Shuffle/loop marker; followed by state dispatch block 01 02 03 02 01 02 03 02 01 |
FE <u32> |
5 bytes | Conditional branch (game-state test) |
FD <u24> |
4 bytes | Loop / jump |
The 01 02 03 02 01 02 03 02 01 byte pattern immediately following FC is a
state machine dispatch table — the same pattern appears in DLG CODE
sections just before JMP thunks, identifying it as a shared engine construct.
XMI Track Index Mapping¶
XMI track index N maps to file AIRnnn.XMI, where nnn is the zero-padded
decimal value of N:
index 1 → VALK01.XMI
index 3 → AIR003.XMI
index 4 → AIR004.XMI
…
index 127 → AIR127.XMI
The index space is sparse — many slots have no corresponding file in
FA_2.LIB (e.g. indices 2, 8, 10–12, 15, 17, 20, 27, 29, 30, 32–37, etc.). The
numeric suffix in the filename IS the track index; VALK01.XMI is the sole
exception to the AIRnnn naming pattern and occupies index 1.
File Inventory¶
The game has nine fixed music slots. Playlist decodes below are from
fx mus dump over all 9 files:
| File | Trigger | Playlist ID | Track indices | Notes |
|---|---|---|---|---|
M_AIR.MUS |
Dogfight | "air" |
4 6 107 108 109 18 110 116 117 118 119 24 29 21 121 122 123 125 126 127 | 9 38 62 65 67 19 | 26 tracks in two groups split by FE conditional; index 29 has no file in FA_2.LIB |
M_NORMAL.MUS |
Normal flight | "air" |
14 70 71 72 73 74 47 61 40 75 76 77 78 44 4 39 22 28 48 40 80 81 82 83 84 26 19 43 85 86 87 88 89 46 13 23 90 91 92 94 7 9 31 38 44 | 45 tracks; longest playlist |
M_DANGER.MUS |
Enemy detected | "air" |
100 101 13 48 47 61 28 39 46 43 41 26 102 5 18 4 45 104 19 23 21 6 | 22 tracks; index 41 has no file in FA_2.LIB |
M_VALK.MUS |
Ctrl+V hidden track | "valk" |
1 | 1 track → VALK01.XMI; valkyrie/dogfight state |
M_DECK.MUS |
On the deck | "air" |
14 13 43 | 3 tracks; carrier deck state |
M_HOME.MUS |
Almost home | "air" |
25 26 40 | 3 tracks; return-to-base |
M_LAUNCH.MUS |
Takeoff | "air" |
7 9 44 31 38 44 | 6 tracks (44 repeated); uses 3-byte FB form |
M_EJECT.MUS |
Ejected | "air" |
(none) | No FB opcodes; contains only FD/FE control flow — eject event redirects state rather than starting a new track |
M_SUCC.MUS |
Success | "air" |
(none) | No FB opcodes; mission-success event is state control only |
(The trigger column comes from the community TOOLKIT documentation of the nine
slots; an earlier account that these slots point at .11K files was wrong —
the confirmed decode below shows XMI playback via Miles.)
M_AIR.MUS decoded (detailed)¶
Playlist ID: "air" (in-flight music); CODE section at file offset 0x200.
Setup opcodes:
- FA 21 0x48 — likely fade-in time
- FA 21 0x7E — likely fade-out time
- FA 32 0x30 — likely tempo/volume
Group 1 (low-intensity, 20 tracks):
4 6 107 108 109 18 110 116 117 118 119 24 29 21 121 122 123 125 126 127
FE 0x48 conditional (game-state branch)
Group 2 (high-intensity, 6 tracks): 9 38 62 65 67 19
Replacing in-game music (community workflow)¶
- Author or convert a replacement
.XMI(see XMI.md) and name it for the target track index (AIRnnn.XMI). - Patch it into FA_2.LIB with
fx lib patch. - To change which tracks a slot plays, edit the slot's
FBopcodes and patch the.MUSback in;fx mus dumpverifies the result.
Engine Notes¶
Playback Architecture — confirmed via Ghidra¶
Traced from _SEQmusic (0x00446B70), ?MusicOn (0x004329E0),
?MusicVolume (0x00432B40) in the game executable.
_SEQmusic(name, seq_idx)
→ appends name to base path (DAT_004f4f6c) to form "M_AIR.MUS" etc.
→ calls MusicOn(filename, seq_idx)
→ RMAccess(filename, 0x10c) — loads MUS DLL from LIB archive
→ _AIL_allocate_sequence_handle — allocate Miles Sound System handle
→ _AIL_init_sequence(handle, mus_data, seq_idx) — pass MUS CODE section to AIL
→ _AIL_start_sequence(handle) — begin playback
The MUS CODE section is passed directly to the Miles Sound System (AIL).
FA does not interpret the FA/FB/FC/FD/FE bytes itself — Miles processes them
natively as XMIDI or MSS sequence data. The sub-opcode semantics (FA 0x19,
FA 0x21, etc.) are Miles-internal and cannot be decoded from the game executable alone.
Volume: ?MusicVolume(vol) maps the 0–100 game volume scale to AIL's
0–127 range: AIL_set_XMIDI_master_volume(handle, vol * 127 / 100).
_SEQfadein / _SEQfadeout (0x00446890 / 0x00446910) are palette
(screen) fades, not music fades. They operate on a 768-byte RGB palette
table (256 × 3 bytes at curPalette). They are unrelated to MUS audio.
seq_idx parameter: the short param_2 passed through _SEQmusic →
MusicOn → _AIL_init_sequence is the AIL sequence index — which section
of the XMIDI data to start playback from. Normally 0 (first sequence).
Open Questions¶
1. FA/FB sub-opcode semantics¶
The sub-opcode values (FA 0x19, FA 0x21, FB mode bytes, the FC state
dispatch) are consumed by Miles, not the game executable — decoding them requires tracing
the AIL wrapper (WAIL32.DLL), whose import surface is untraced, or consulting
Miles Sound System XMIDI documentation.
Status: open — re-static (#54)
Related¶
Formats: XMI — the Extended MIDI tracks these playlists select;
SEQ — the cutscene sequencer whose _SEQmusic path triggers these
slots; 11K — PCM sound effects (a music slot does not reference
these — see the correction note there).
Video & Cutscenes
CB8 — FMV Container (.CB8)¶
Multiplexed audio/video container for full-motion video. Used for intros,
cutscenes, and per-aircraft presentation clips. Each .CB8 is paired with a
.11K audio file of the same stem (for playback outside the container).
Found in FA_4C.LIB, FA_4D.LIB, FA_10.LIB, FA_10B.LIB, FA_11.LIB,
and FA_11B.LIB.
Tools¶
fx¶
fx cb8 info <file.CB8> # header and chunk summary
fx cb8 frames <file.CB8> [-o output_dir] # decode every frame to PGM (indices)
fx cb8 unpack <file.CB8> [-o output_dir] # decode every frame to colour PNG
fx cb8 repack <orig.CB8> <png_dir> [-o out] # rebuild the movie around edited frames
unpack renders through each frame's embedded palette. repack re-encodes
the video (each PNG must use ≤ 256 distinct colours; the per-frame palette is
rebuilt) while the DRBC header, every audio chunk, the stream order, and the
VooM timing carry over from the original verbatim — the decode→edit→repack
loop is pixel-exact (#95).
Other Tools¶
- GIMP — free, cross-platform; batch script (
File → Script-Fu) useful for processing many frames - Paint.NET — free, Windows
- Photoshop
$— industry standard; Image Processor script for batch frame edits - Affinity Photo
$— one-time purchase alternative to Photoshop
File Layout¶
All multi-byte integers are little-endian.
The file begins with a 64-byte DRBC header, followed by a sequence of variable-length typed chunks packed back-to-back. Each chunk starts with a 4-byte ASCII type tag and a 4-byte total size (including the tag and size field).
| Offset | Size | Description |
|---|---|---|
0x00 |
64 | DRBC file header |
0x40 |
var | Typed chunks: MRFA, MRFI, VooM (see below) |
Chunk structure:
| Offset | Size | Type | Description |
|---|---|---|---|
+0x00 |
4 | char[4] | ASCII type tag (MRFA, MRFI, or VooM) |
+0x04 |
4 | u32 | Total size of this chunk in bytes (including these 8 bytes) |
+0x08 |
var | Chunk payload |
DRBC File Header (64 bytes)¶
| Offset | Size | Type | Description |
|---|---|---|---|
0x00 |
4 | char[4] | Magic DRBC — the fourth generation of the format: InitCobra explicitly rejects ARBC, BRBC, and CRBC as too old (confirmed) |
0x04 |
4 | u32 | Flags: bit 0 = audio interleaved (MRFA chunks present), bit 1 = pixel-doubled playback (confirmed from InitCobra) |
0x08 |
2 | u16 | Audio timing divisor P (observed: 150) |
0x0A |
2 | u16 | Audio rate term Q (observed: 22050); samples per MRFA chunk = Q × 50 / P = 7350 (confirmed: InitCobra primes two chunks of exactly this size) |
0x0C |
4 | u32 | Format version — the engine requires < 0x67 (observed: 0x65) |
0x10 |
2 | u16 | Reserved — read into the 64-byte header buffer but never referenced by InitCobra (observed: 0x0000 or 0x0080) |
0x12 |
46 | u8[46] | 0xFF padding |
The engine reads the file sequentially — header, two priming MRFA chunks (when flag bit 0 is set; the first sample of each is forced to 0x80 silence), the VooM index, then frames — and never checks the MRFI/MRFA/VooM tags: the index is trusted for every seek (confirmed; the tags exist for the file format, not the player).
Chunk Type: MRFA — Audio Block¶
Raw PCM audio data. The chunk payload contains uncompressed 8-bit unsigned PCM
samples at 11025 Hz (matching the .11K convention). Silence is 0x80.
| Offset | Size | Type | Description |
|---|---|---|---|
+0x00 |
4 | char[4] | Magic MRFA |
+0x04 |
4 | u32 | Chunk size (observed: 7374) |
+0x08 |
4 | u32 | Audio-format descriptor — not consumed by the player (observed: 128) |
+0x0C |
4 | u32 | Audio-format descriptor — not consumed (observed: 0) |
+0x10 |
4 | u32 | Audio-format descriptor, consistent with bits-per-sample (observed: 8) — not consumed |
+0x14 |
4 | u32 | Audio-format descriptor, consistent with channel count (observed: 1 = mono) — not consumed |
+0x18 |
7350 | u8[] | Raw 8-bit unsigned PCM samples at 11025 Hz |
The four dwords at +0x08–+0x17 are read past by InitCobra but never
consumed: the playback audio format is fixed (8-bit / 11025 Hz / mono) and the
per-chunk sample count comes from the DRBC P/Q timing pair, not this header —
consistent with the doc's note that the chunk metadata "exist[s] for the file
format, not the player."
7350 samples ÷ 11025 Hz = 666.7 ms = exactly 10 video frames at 15 fps.
Chunk Type: MRFI — Video Key Frame¶
One self-contained, vector-quantized key frame (confirmed — engine trace
95, DecodeSVGA8Frame at 0x456EC0; the DecodeFrame dispatcher at¶
0x442370 has no inter decoder for the 8-bit submode, so nothing carries
over between frames). Every frame brings its own palette and codebooks.
An earlier revision of this section documented a delta/skip-map model with block data at
+0x18; that model was wrong — the region at+0x18is the palette — and it never decoded real frames. The layout below is read from the engine and validated by a pixel-exact decoder.
| Offset | Size | Type | Description |
|---|---|---|---|
+0x00 |
4 | char[4] | Magic MRFI (unchecked by the player) |
+0x04 |
4 | u32 | Chunk size, padded to a 4-byte multiple |
+0x08 |
1 | u8 | Frame kind: 0 = key (the only kind used by CB8) |
+0x09 |
1 | u8 | Submode: 5 = 8-bit paletted (6 = the 15/16/24-bit VDO path) |
+0x0A |
2 | u16 | A — detail-book entries for pixel rows < X |
+0x0C |
2 | u16 | B — detail-book entries for pixel rows >= X |
+0x0E |
2 | u16 | C — single-book entries |
+0x10 |
2 | u16 | S — mode-bitmap bytes (((cells + 31) / 32) × 4; 600 for 320×240) |
+0x12 |
2 | u16 | D — palette bytes (768) |
+0x14 |
2 | u16 | X — detail-book switch row in pixels (0xFFFF = never) |
+0x16 |
2 | u16 | Padding (0) |
+0x18 |
D |
u8[] | Palette: 256 × 3 bytes of 6-bit VGA RGB — every frame carries its own |
| — | (A+B)×4 |
u8[] | Detail book: 2×2-pixel entries (4 palette indices, row-major); entries 0..A-1 serve rows < X, A..A+B-1 rows >= X |
| — | C×4 |
u8[] | Single book: 2×2-pixel entries expanded to 4×4 at decode |
| — | S |
u8[] | Mode bitmap: one bit per 4×4 cell, row-major, continuous across rows; consumed as u32-LE words MSB-first |
| — | var | u8[] | Index stream (to end of chunk, zero-padded to the 4-byte boundary) |
Cell grid. The frame divides into 4×4-pixel cells, row-major (80 × 60 = 4,800 for 320×240). For each cell, its mode bit selects the coding:
- Bit 0 — single (
CopySB8): one index byte into the single book. The 4-byte entry(a, b, c, d)is a 2×2 block expanded to 4×4 — plainly, each value pixel-doubled into its quadrant (ExpandDB), or with a dither when the display path enables it (EDB, below). - Bit 1 — detail (
CopyDB8): four index bytes selecting detail-book entries placed as the TL, TR, BL, BR 2×2 quadrants; each 4-byte entry is one 2×2 block (row 0: bytes 0–1, row 1: bytes 2–3). Rows>= Xindex the second book half (theBentries).
Decode algorithm:
parse A,B,C,S,D,X; locate palette/detail/single/bitmap/index regions
cell = 0
for cy in 0..cell_rows-1:
book = detail[0..A) if cy*4 < X or X == 0xFFFF
= detail[A..A+B) otherwise
for cx in 0..cell_cols-1:
word = u32le(bitmap[(cell/32)*4 ..]) // MSB-first bit order
bit = (word >> (31 - cell%32)) & 1
if bit == 0: expand single[next_index()] into the 4x4 cell
else: place book[next_index()] at TL, TR, BL, BR (4 indices)
cell += 1
EDB dither (display-time option). When the SVGA path enables dithering,
each single-book value v expands not by plain doubling but as a 2×2
checkerboard of v and a partner p: the neighbour index (v−1 or v+1,
within the same 128-entry palette half, guarded at 0/0x7F/0x80/0xFF) whose
RGB — looked up in this frame's palette — is nearest to v's with
squared distance ≤ 8; p = v when neither qualifies. The expanded rows are
(a, pa, b, pb) / (pa, a, pb, b) and likewise for (c, d). fx decodes
with the plain expansion (deterministic and exactly invertible); the dither
is a player-side rendering variant, not stream data.
Chunk Type: VooM — Video Index / Key Frame¶
Serves as both a video key frame marker and an A/V index table. The payload begins with a 12-byte header describing the video stream, followed by a flat array of 16-byte index entries (one per MRFI frame).
| Offset | Size | Type | Description |
|---|---|---|---|
+0x00 |
4 | char[4] | Magic VooM |
+0x04 |
4 | u32 | Chunk size |
+0x08 |
4 | u32 | Video width in pixels (observed: 320) |
+0x0C |
4 | u32 | Video height in pixels (observed: 240) |
+0x10 |
4 | u32 | Audio sync rate = samples_per_frame × fps (observed: 6000 = 400 × 15) |
+0x14 |
16×N | Index entries (N = (chunk_size − 20) / 16) |
Index entry (16 bytes each):
| Offset | Size | Type | Description |
|---|---|---|---|
+0x00 |
4 | u32 | Absolute file offset of this MRFI chunk |
+0x04 |
4 | u32 | Byte size of this MRFI chunk |
+0x08 |
4 | u32 | Cumulative audio sample count at this frame (frame_index × samples_per_frame) |
+0x0C |
4 | u32 | Audio samples per frame (constant: 400) |
Typical Chunk Sequence¶
DRBC header
MRFA — priming audio block (first sample forced to 0x80 silence)
MRFA — second priming block
VooM — A/V index (N entries)
MRFI — key frame 0
MRFI — key frame 1
... — MRFA blocks interleaved roughly every 10 frames
MRFI — key frame N-1
Palette and Colour¶
Every MRFI frame embeds its own 768-byte palette (256 × 3 bytes of 6-bit
VGA RGB at payload offset +0x18) — resolved by the #95 engine trace. No
external palette exists or is needed: decoding is fully self-contained, and
palettes can change per frame (the movies fade by animating them).
PALETTE.PAL never applies to CB8 (its low indices are magenta placeholders); the earlier greyscale-fallback advice is obsolete now that the in-band palette decodes true colour.
File Inventory¶
| File | Video | Audio | Source LIB |
|---|---|---|---|
| ATF.CB8 | VooM 320×240 | .11K (external) | FA_4C.LIB |
| C_INTRO.CB8 | VooM 320×240 | .11K (external) | FA_4C.LIB |
| JANELOGO.CB8 | 466 MRFI frames | MRFA blocks (11025 Hz) | FA_4C.LIB |
| B2_D.CB8 | MRFI delta frames | MRFA blocks (11025 Hz) | FA_10.LIB |
JANELOGO.CB8 (6,496,064 bytes): VooM at offset 14812 with 466 index entries (chunk_size 7476 = 20 + 466×16). Frame 0 offset: 22288, duration: ~31.1 s @ 15 fps.
Engine Notes¶
Provenance: the Cobra framework¶
CB8's player is "Cobra", EA's in-house movie framework — not licensed
middleware (confirmed, #95 engine trace). The evidence: the decoder is
first-party C++ compiled into the game executable (InitCobra, SetupCobra,
PlayCobra, DecodeFrame — MSVC-mangled names from EA's own FA.SMS
symbols), it shares engine-internal structs (GlobalData, MovieContext,
FrameHeader), reads movies through the engine's own LIB resource
layer rather than a middleware I/O callback, and renders through the engine's
VGA banking (SetVESABank, DrawAcrossBank). The magic's generation lineage
— InitCobra rejects ARBC, BRBC, and CRBC as too old before accepting
DRBC, plus an internal version gate (< 0x67) — is a format evolving
privately across the USNF-line titles this anthology collects; licensed FMV
of the era (RAD's Smacker, and Bink from 1999) shipped as self-contained
libraries with their own containers. "CB8" reads as CodeBook, 8-bit; the
same Cobra dispatcher also serves the 15/16/24-bit submode used by the
VDO hi-color movies, so Cobra is the umbrella for both. The name's
origin is unrecorded in the binary — an internal codename (inferred).
Open Questions¶
1. Playback palette recovery — resolved¶
Resolved by the #95 engine trace: the palette is embedded per frame
(768 bytes at MRFI +0x18); nothing engine-internal or external exists.
DecodeSVGA8Frame reads it from the frame, and EDB's dither computes
neighbour distances against it.
Status: resolved — re-static (#95)
2. Unknown header fields — resolved (#54)¶
The static header unknowns are closed against the InitCobra (0x46ae10)
decompile: the loader reads the 64-byte DRBC header but references only the
magic, flags, audio-timing pair (P/Q), and version gate — the +0x10 field
is read into the buffer and never used (reserved). The four MRFA payload dwords
at +0x08..+0x17 are likewise read past but not consumed; the audio format is
fixed and per-chunk size is DRBC-driven, so they are file-format descriptors,
not decode inputs (see the DRBC / MRFA header tables above).
The one remaining item is not a file field: the palette-half selector EDB
consults (GlobalData+0xc1b0 bit 0) is a runtime playback / double-buffer
detail, best confirmed on the bench.
Status: resolved static; palette-half re-tagged re-gameplay (#56)
Related¶
Formats: 11K — the paired external audio (and the MRFA sample format); PAL — why PALETTE.PAL does not apply here; LIB — the six carrier archives.
VDO — RATVID Streaming Video (.VDO)¶
Video frames for mission briefing sequences. Found in FA_7.LIB (Disc 1) —
355 files, each a 4-character stem (e.g. AACA). Every .VDO has exactly
one same-stem .FBC frame-size index. Audio is shared per 3-character
briefing-group prefix: AAC.11K narrates the whole AACA…AACE variant
group (the 4th character A–J is the angle/variant). 104 of the 105 groups
carry a .11K; one group (IQC) is silent — so a .VDO is not guaranteed a
same-stem audio track. All are 320×200, magic RATV. Pairing verified across
the full corpus (#137).
Tools¶
fx¶
fx vdo info F16C.VDO [F16C.FBC] # header (+ frame count with the FBC)
fx vdo export AACA.VDO AACA.FBC -o out/ # decode every frame to PNG
fx_lib decodes read-only (the engine has no encoder). vdo_open needs the
paired .FBC — it supplies the frame boundaries the .VDO itself omits.
Decoding is sequential (frames are inter-coded); the decoder replays from frame
0 when asked for an earlier frame.
File Layout¶
All multi-byte integers are little-endian.
| Offset | Size | Description |
|---|---|---|
0x00 |
816 | Header (see below) |
0x330 |
var | Frame data blocks, one per frame (sizes given by paired .FBC) |
Header (816 bytes)¶
| Offset | Size | Type | Description |
|---|---|---|---|
0x00 |
6 | char[6] | Magic RATVID (ASCII, no null terminator) |
0x06 |
1 | u8 | Major version = 1 |
0x07 |
1 | u8 | Minor version = 2 |
0x08 |
4 | u32 | Frame rate (observed: 15 fps) |
0x0C |
4 | u32 | Unknown (observed: 0) |
0x10 |
2 | u16 | Frame count (N) |
0x12 |
2 | u16 | Width in pixels (observed: 320) |
0x14 |
2 | u16 | Height in pixels (observed: 200) |
0x16 |
2 | u16 | Palette entry count (always 256) |
0x18 |
2 | u16 | Audio channel count (always 1 = mono) |
0x1A |
2 | u16 | Audio sample rate in Hz (observed: 8000) |
0x1C |
4 | u32 | Unknown |
0x20 |
16 | u8[16] | Zeroed |
0x30 |
768 | u8[256×3] | VGA palette: 256 × 3 bytes (R, G, B each 6-bit, range 0–63) |
The 768-byte palette at offset 48 is a standard VGA DAC palette — 256 entries
of 3 bytes each, all values in 0–63 (6-bit per channel, matching VGA register
format). Entry 0 is always black (00 00 00). The palette is per-video; each
.VDO file carries its own.
Field at offset 22 is confirmed to be the palette entry count (always 256, matching the 768-byte palette block at +48). Field at offset 24 is confirmed to be the audio channel count (always 1 = mono).
Frame Data¶
Frame data begins at offset 816. Frames are packed back-to-back with no
delimiters. Frame N starts at offset 816 + sum(FBC[0..N-1]).
Frame data is palettized (8-bit palette indices into the header palette at
+48). Each frame is decoded by GetVDOFrame (VA 0x4AF510) into the VDO
struct's decode buffers.
The
.VDOcodec is not the CobraDecodeFramecluster. The shipped 320×200 8bpp movies decode through a small, self-contained path —GetVDOFrame→UnRLE→DecompressVideo(three functions at0x4C8AA4–0x4C8DBB, ~460 bytes) — and never touch theDecodeFramedispatcher (0x442370) or the ~45 Cobra/CB8 leaves at0x456300–0x45D090. Those are the CB8 player. "Cobra" is the shared FMV umbrella; the two codecs are distinct. (Reconciles the earlier note in video-decode.md that submode-6 served.VDO.)
Per-frame stream structure¶
GetVDOFrame reads the whole frame (FBC[n] bytes) and parses it as a leading
u16 tag followed by one or two RLE-or-raw sub-streams:
Tag (u16) |
Meaning |
|---|---|
0 |
Reuse the current codebook; decode the index stream only (delta frame) |
1 |
Color-table refresh (RLE) — no pixel blit this frame |
2 |
Image keyframe → DecompressVideoImage; a second u16 gives the sub-stream length |
| other | Codebook sub-stream: bit 0x8000 set ⇒ RLE-compressed, low 15 bits = its byte length (UnRLE expands it); raw otherwise |
After the codebook sub-stream a u16 marker separates the index sub-stream:
0 = end, 0xFFFF = an RLE-compressed index stream follows (UnRLE). The
frame is then rendered by DecompressVideo(colortable, codebook, index, width,
height). Verified against the corpus: frame 0 of AACA.VDO begins BE 8B
(tag = 0x8BBE, bit 0x8000 set ⇒ a 0x0BBE-byte RLE codebook block).
UnRLE (0x4C8AFC)¶
Byte-oriented RLE, prefixed by a u16 output-pixel count. Each control byte:
bit 0x80 set ⇒ run — length (b & 0x7f) + 1 (or, if 0x7f, a following
u16 + 1) of the next byte repeated; clear ⇒ literal — copy b bytes
verbatim. Decoding stops when the output count is exhausted.
DecompressVideo (0x4C8AA4) — the blit dispatch¶
Walks the index stream in 8-pixel groups (width × height / 8): a 0 byte
skips the group (leaves the prior frame's pixels — the inter-frame delta), and a
nonzero byte indexes a span-emitter jump table at 0x50E5CE whose handler
writes that group's 8 pixels. DecompressVideoImage (0x4C8CD8) is the keyframe
variant, adding a row-replication tail (each output row copied to the next — the
320×200 → 640×480 vertical doubling).
The index byte is an 8-bit copy mask (#139)¶
The 0x50E5CE handlers are generated x86, built once at startup by
BuildSelfModifyCode (0x4C8BEC, via VDOInit 0x405490) into a code buffer
at 0x50E9CE; the table statically holds only zeros. Each handler decodes one
index byte, and that byte is a per-pixel copy mask over the group's 8
pixels: a set bit copies the next source pixel; a clear bit keeps the previous
frame's pixel (advances the output pointer without reading source). So the
source stream carries only the pixels that changed, and the mask says where.
BuildSelfModifyCode emits, per index value:
0x00→add edi, 8; ret— skip the whole group (the0fast-path inDecompressVideo).0xFF→movsd; movsd; ret— copy all 8 pixels (A5 A5).- everything else → two
DoNibble(0x4C8C60) calls (high nibble → pixels 0–3, low nibble → pixels 4–7) +ret.
DoNibble emits the x86 for one 4-bit nibble = 4 pixels, MSB first:
| Nibble / 2-bit pair | Emitted | Effect |
|---|---|---|
nibble 0xF |
A5 (movsd) |
copy 4 |
pair 11 |
66 A5 (movsw) |
copy 2 |
pair 10 |
A4 47 (movsb; inc edi) |
copy, keep |
pair 01 |
47 A4 (inc edi; movsb) |
keep, copy |
pair 00 |
47 47 (inc edi; inc edi) |
keep 2 |
So the codec is a per-pixel delta: mask bit set ⇒ take a new pixel from the source stream; clear ⇒ retain the prior frame's pixel — plus the RLE that compresses the mask and source streams.
Worked example — AACA.VDO frame 0 (FBC[0] = 35,546 B, tag = 0x8BBE): the
mask sub-stream is a 0x0BBE-byte RLE block that expands to exactly 8,000
mask bytes (= 320×200/8, one per group); the 0xFFFF marker then introduces
an RLE source stream declaring 32,534 pixels. Reconciling the mask's set-bit
total against that source count byte-for-byte is the one detail the fx_lib
reference decoder pins down (#140).
Audio¶
Audio is stored separately in the paired .11K file (raw PCM, 8000 Hz mono
8-bit). It is not embedded in the .VDO.
File Inventory¶
| File | Frames | Width | Height | FPS | Audio Hz |
|---|---|---|---|---|---|
| AACA.VDO | 123 | 320 | 200 | 15 | 8000 |
| AACB.VDO | 260 | 320 | 200 | 15 | 8000 |
| IPCA.VDO | 1685 | 320 | 200 | 15 | 8000 |
Engine Notes¶
Confirmed functions (FA.SMS names), in load → decode order:
| VA | Symbol | Role |
|---|---|---|
0x4AF1E0 |
OpenVDOFile |
Open the .VDO file, begin streaming |
0x4AF200 |
ReadVDOHeader |
Parse the 816-byte file header into a VDOHEADER |
0x4AF230 |
ReadFrameSizesFile |
Read the paired .FBC frame-size index |
0x4AF2D0 |
ReadVDOPalette |
Load the 768-byte VGA palette into T_RGB[] |
0x4AF320 |
VDOfromVDOHEADER |
Build the runtime VDO struct from the header |
0x4AF3A0 |
AllocVDO |
Allocate the frame decode buffers (codebook / index / color) |
0x4AF070 |
StartVDOAudio |
Start the paired .11K narration track |
0x4AF510 |
GetVDOFrame |
Read + parse one frame, dispatch to the decoder |
0x4C8AFC |
UnRLE |
RLE decompress a sub-stream (see § UnRLE) |
0x4C8AA4 |
DecompressVideo |
Blit the index stream via the 0x50E5CE table |
0x4C8CD8 |
DecompressVideoImage |
Keyframe/image blit + row replication |
0x405490 |
VDOInit |
One-time init; calls BuildSelfModifyCode |
0x4C8BEC |
BuildSelfModifyCode |
Generate the 256 copy-mask handlers into 0x50E9CE |
0x4C8C60 |
DoNibble |
Emit the x86 for one nibble (4 pixels) of a handler |
0x4AF6B0 |
VDO_320x200_to_640x480 |
2× upscale for the SVGA present path |
All operate on the VDO struct, whose fields correspond to the header layout
above. These live in the 0x4AExxx/0x4C8xxx range — distinct from the
0x456300 Cobra/CB8 cluster.
Open Questions¶
1. tag-2 image-keyframe path¶
The fx_lib decoder (#140) decodes every frame of all 355 stock .VDO files —
the mask/RLE/copy-mask path is validated end to end (frame 0 of AACA.VDO
renders the briefing image pixel-for-pixel; the mask set-bit count matches the
source-pixel count exactly). The one unexercised branch is tag 2
(DecompressVideoImage, the row-replicated image keyframe): no shipped file
uses it, so the decoder treats it as a prior-frame passthrough. Confirming that
path needs a .VDO that carries it (none in the corpus) or the running engine.
Status: open — re-static (#55; only the corpus-absent tag-2 path remains)
Related¶
Formats: FBC — the paired frame-size index; 11K — the paired audio track; CB8 — the other (fully decoded) FMV format.
FBC — Video Frame Index (.FBC)¶
Companion index file for a .VDO video. Provides the byte size of each frame
so that a decoder can seek directly to any frame without scanning the video
data. Found in FA_7.LIB: exactly one .FBC per .VDO, sharing the same
4-character stem (355 pairs). Audio pairs differently — by 3-character prefix,
not by full stem (see § Relationship to .VDO).
Tools¶
fx¶
fx fbc info <file.FBC> # frame count + expected paired-VDO size
fx fbc ls <file.FBC> # per-frame size and VDO offset table
fx_lib round-trips the format byte-identically (fbc_read/fbc_write);
the fxs VDO editor renders the same index through the library.
File Layout¶
All multi-byte integers are little-endian.
No magic or header. The file is a flat array of N u32 values, where N equals
the frame count stored in the paired .VDO header (offset 16). File size is
always 4 × N bytes.
| Offset | Size | Type | Description |
|---|---|---|---|
0x00 |
4 × N | u32[N] | Byte size of each frame in the paired .VDO |
Relationship to .VDO¶
frame_data_offset(n) = 816 + sum(FBC[0 .. n-1])
invariant: sum(FBC[0 .. N-1]) == VDO_file_size - 816
N also equals the frame-count u16 at VDO header offset 0x10 (confirmed).
Verified against the full corpus (#137). All 355 .FBC/.VDO pairs in
FA_7.LIB satisfy the invariant with zero mismatches; frame counts range 12 →
1685 (41,163 frames total). Every .VDO has exactly one same-stem .FBC.
Audio pairing is by 3-character prefix, not full stem. The .11K narration
files use a 3-character stem (e.g. AAC.11K) shared across all 4-character
.VDO variants of that briefing group (AACA, AACB, … — the 4th character
A–J is the variant/angle). Of the 105 briefing-group prefixes, 104 carry
audio; one group (IQC) is silent. So a .VDO is not guaranteed a
same-stem .11K — it shares its group's track (if any).
Observed Values¶
Representative pairs (the invariant is verified across all 355 — see above):
| File | Entries (N) | FBC size | VDO size | VDO size − 816 |
|---|---|---|---|---|
| ABCA.FBC | 68 | 272 | 113,353 | 112,537 |
| AACA.FBC | 123 | 492 | 156,301 | 155,485 |
| AACB.FBC | 260 | 1040 | 362,853 | 362,045 |
| IPCA.FBC | 1685 | 6740 | 13,141,769 | 13,140,953 |
Related¶
Formats: VDO — the video stream this file indexes; 11K — the audio track, shared per 3-character briefing-group prefix.
Engine: the fxs VDO editor renders this index (frame table); the fx_lib codec is tracked in #107.
SEQ — Cutscene Timeline (.SEQ)¶
SEQ files drive in-game cutscenes (mission briefings, death screens, campaign intros). Each file is a plain ASCII text timeline: a sequence of timestamped commands that trigger bitmaps, sounds, palette changes, and fades. FA_2.LIB carries 126 of them.
Tools¶
fx¶
fx seq dump <file.SEQ> # pretty-print events to stdout
fx seq unpack <file.SEQ> [-o out.txt] # write editable text
fx seq pack <in.txt> -o <out.SEQ> # write binary SEQ
Other Tools¶
SEQ files are plain ASCII — open and edit directly, no conversion step needed.
- VS Code — free; multi-file find/replace useful for batch renaming bitmap or sound references
- Notepad++ — free, Windows; column editing helps with tab-aligned time fields
- Notepad / TextEdit — free, built-in; sufficient for small edits
File Layout¶
Plain text; no binary fields.
; optional comment lines
[blank lines]
<TAB><time><TAB><command>[<TAB><arg1> <arg2> ...]<CR><LF>
- Lines beginning with
;are comments and are preserved verbatim on round-trip. - Event lines begin with a tab, then a time field, then another tab, then a command.
- Lines may use
\r\nor\n— write\r\non output.
Time field¶
| Form | Meaning |
|---|---|
0 |
Absolute tick 0 |
5 |
Absolute tick 5 |
+23 |
Relative: 23 ticks after the previous event |
Trailing spaces after the time value (before the second tab) are legal and common.
Command and arguments¶
Arguments are space-separated on the same line, after the command:
- Quoted strings: "NAME" (filename references, no extension)
- Bare numbers / floats: 256, .5, 0
Known commands¶
| Command | Typical args | Notes |
|---|---|---|
bitmap |
"NAME" x y flags width |
Display image at (x,y) |
palette |
"NAME" |
Load a named palette |
font |
"NAME" |
Set current font |
video |
"NAME" |
Play video clip |
sound |
"NAME" |
Play sound (quoted, no extension; ^ prefix = looping) |
fadein |
seconds |
Fade to full brightness |
fadeout |
seconds |
Fade to black |
wait |
(none) | Pause until sound/video completes |
sync |
sub-command... | Execute sub-command synchronously |
Example¶
KDEAD.SEQ (92 bytes):
0 bitmap "KDEAD" 0 0 0 256
0 fadein .5
0 sound "^KDEAD.11K"
+23 sync fadeout .5
Round-Trip Notes¶
fx seq pack emits files byte-identical to the originals (tabs, trailing
spaces, CRLF); tests/test_seq.cpp asserts it, including comment and
blank-line preservation. Parsed event count for KDEAD.SEQ: 4 events.
Related¶
Formats: 11K — the sound command plays PCM clips (^ prefix
= looping); MUS — the engine's _SEQmusic path triggers music
slots from sequence state.
Mission & Campaign
M — Mission File (.M)¶
.M files define individual missions as plain-text textFormat data — one
keyword per line, with obj and waypoint2 blocks closed by a lone . (517
files in FA_2.LIB). The related .MM theater map format is distinct — see
MM.md.
Tools¶
fx¶
fx mission info <file.M> # map name, time, object count
fx mission unpack <file.M> [-o out.txt] # editable text
fx mission pack <in.txt> -o out.M # write back (byte-identical)
Other Tools¶
.M files require fx mission unpack → edit → fx mission pack. The
companion .MT briefing files are plain ASCII and can be opened directly.
- VS Code — free, cross-platform; multi-file search useful for tracking object names and map references across missions
- Notepad++ — free, Windows; lightweight for quick briefing text edits
File Layout¶
Plain ASCII text, CRLF line endings, one keyword per line — there is no
bracket syntax. The first line is textFormat. Top-level keywords stand alone;
obj and waypoint2 open blocks that a lone . line closes. Indentation
(tabs) is cosmetic — the structure is defined by the keywords, not the layout.
textFormat
brief
map BALTIC.T2
layer BALTIC.LAY 0
time 6 0
wind 270 15
clouds 20
obj
type F16.PT
pos 12345 0 67890
angle 0 0 0
nationality2 130
flags $411
alias -1
.
… more obj blocks …
waypoint2 4
w_index 0
w_goal 1
w_pos2 0 0 743800 16000 929588
w_speed 920
w_index 1
…
.
Token format¶
key value…— one keyword per line, space-separated values (string, decimal integer, or$-prefixed hex).obj….— an object block; the lone.closes it.waypoint2 N….— a waypoint block of N waypoints; the lone.closes it.sides4opens the nationality table (indented$XXvalues follow).
Top-level keys¶
| Key | Value | Description |
|---|---|---|
textFormat |
(none) | magic first line |
brief / briefmap / armplane / selectplane |
(none) | screen flags |
map |
file | terrain .T2 reference |
layer |
file index | .LAY reference |
clouds |
percent | cloud cover 0–100 |
wind |
dir speed | wind conditions |
time |
hour min | start time of day |
sides4 |
(block) | nationality table |
obj |
(block) | a placed object |
waypoint2 |
count (block) | a waypoint list |
Object block (obj … .)¶
| Key | Value |
|---|---|
type |
object type name (references .OT/.NT/.PT files) |
pos |
x y z (world units) |
angle |
pitch bank roll (degrees) |
nationality2 |
integer country code |
flags |
$hex behaviour flags |
speed |
initial speed |
alias |
signed id; targeted by a waypoint's w_preferredTargetId2 |
skill |
AI skill level |
react |
reaction triple ($hex $hex $hex) |
searchDist |
detection range |
name |
optional label |
The field list varies by object type; unknown fields are preserved verbatim.
mission_parse_objects (api.md § mission.h) promotes
type/pos/angle to typed members and keeps every other field in
MissionObj::fields.
Waypoint block (waypoint2 N … .)¶
N waypoints, each opened by w_index and carrying w_pos2 (position),
w_goal, w_next, w_flags, w_speed, w_react, w_searchDist,
w_preferredTargetId2 (target link), and w_name. These blocks are emitted
after all obj blocks and there are always fewer of them than objects, so
which object a block belongs to is not encoded adjacently — the engine's
assignment rule is not yet recovered (re-gameplay, #29). mission_parse_objects
therefore returns the blocks as a parallel waypoint_blocks list rather than
nesting them in objects.
Preferred target system¶
Waypoints can designate a specific object as their attack target using
w_preferredTargetId2. The value is the object's alias field encoded as a
negated unsigned 16-bit hex value:
hex_value = uint16_t(alias) (equivalently: 0x10000 + alias, since alias is negative)
| Alias | w_preferredTargetId2 |
|---|---|
| −1 | $ffff |
| −2 | $fffe |
| −255 | $ff01 |
| −256 | $ff00 |
| −257 | $feff |
| −288 | $fee0 |
The pattern continues linearly; alias −N = $(10000 − N) in hex. Up to 288
preferred target slots are supported ($ffff through $fee0). Does not apply
to map objects or the player object.
Example:
; Waypoint
w_preferredTargetId2 $ff01
; Target object with matching alias
obj
type TRUCK.NT
alias -255
Round-Trip Notes¶
- Parse → serialize produces byte-identical files for all 517
.Mfiles in FA_2.LIB;tests/test_mission.cppasserts byte preservation. mission_roundtripis a verbatim copy (CRLF-normalized), so it preserves the files' tab indentation regardless of whether the parser needs it.mission_parse_objectsextracts the full object + waypoint-block lists; the object count matchesmission_parse_infofor all 592 stock missions.
Related¶
Formats: MM — the theater map format sharing the mission codec;
MT — plain ASCII briefing/debrief companion to each .M file;
BRF — the .OT/.PT type definitions referenced by type fields.
MT — Mission Briefing Text (.MT)¶
FA_2.LIB contains 363 .MT files — roughly one per mission. Each stores the
full text content for the pre-mission briefing and post-mission debrief
screens. Format is plain ASCII text using a shared directive/markup engine
with .TXT files.
Tools¶
fx¶
fx mt info <file.MT> # mission id/title/type + section count + round-trip
Parsing rides the shared directive engine in lib/src/txt.cpp (the same
line-preserving parser as TXT); lib/src/mt.cpp adds the
section-1 header semantics. All 363 .MT files in FA_2.LIB round-trip
byte-identically.
File Layout¶
Plain ASCII text, CRLF line endings. Directives begin with . and may be
chained on one line, separated by spaces. Plain text between directives is
rendered in the current active style. Directives may also appear inline within
a content line (e.g. .header TITLE .body renders "TITLE" in header style
then switches to body).
Directives¶
| Directive | Description |
|---|---|
.section <N> |
Begin numbered section (see Section Semantics below) |
.header |
Switch to header render style |
.body |
Switch to body render style |
.center |
Center-align subsequent text |
.left |
Left-align subsequent text |
.underline |
Enable underline |
..underline |
Disable underline (.. prefix deactivates the named directive) |
.page |
Page break within a section — inserts a new screen without starting a new section |
Directives apply until overridden. Alignment and style are independent —
.center .underline .header sets all three simultaneously.
Section Semantics¶
Sections 1–5 are observed across all 363 files:
| Section | Purpose |
|---|---|
| 1 | Mission identifier — plain text line (format: --<ID> (<filename>)) followed by title and mission type |
| 2 | Pre-mission briefing — location, date/time, objectives, threat data |
| 3 | Debrief (primary outcome — typically success for single-player, Blue success for multiplayer) |
| 4 | Debrief (secondary outcome — failure, or Red success for multiplayer) |
| 5 | Debrief (draw / objectives incomplete) |
Most single-player missions use sections 1–4. Multiplayer and some campaign missions use all 5.
Section 1 ID Line¶
The first content line of section 1 follows the format:
--<MISSIONID> (<filename>)
e.g. --AB01 (bextra01). The -- prefix is the engine's cue (inferred) to
parse this line as the mission ID rather than display text.
Example — BEXTRA01.MT (complete section 2)¶
.section 2
.center .underline .header
WEAPONS FREE
..underline .left .body
TARTU AIRBASE
DATE : February 22
LOCAL TIME : 1200
WEATHER : Cloudy
.header .underline
MISSION OBJECTIVE
..underline .body
Destroy all guerrilla structures and vehicles in the area.
.header .underline
THREAT SUPPRESSION DATA
..underline .body
GROUND OPPOSITION : Shoulder-launched SAMs
AIR OPPOSITION : Possible U.N Rafales, Mirage 2000s
Related¶
Formats: M — .M mission files that reference these briefing
texts by filename; TXT — uses the same directive engine; adds
.button and .picture for UI screens.
CAM — Campaign Definition (.CAM)¶
FA_2.LIB contains 6 .CAM files — one per built-in campaign. Pilot save files
(.P) store the active campaign by this filename. Each is a Win32 PE DLL
loaded by the FA engine at runtime.
Tools¶
fx¶
fx cam info <file.CAM> # container check + CODE section geometry
fx cam strings <file.CAM> [-n MIN] # embedded campaign string tables
Read-only by design: the campaign configuration is what the tooling surfaces; the surrounding DLL machine code is engine territory (fighters-legacy), not asset tooling.
File Layout¶
All multi-byte integers are little-endian.
Win32-style DLL: MZ stub + a Phar Lap PL\0\0 image (PE32 section
layout with a COFF section table — pe_code_section in lib/src/pe.cpp
reads it; verified against BALTIC.CAM: sections CODE/.idata/.reloc/
$$DOSX, CODE at raw 0x400, VMA 0x1000). The standard DOS stub message
"!This program cannot be run in DOS mode." is present, followed by standard
PE sections .idata and .reloc. BALTIC.CAM decompresses to 8704
bytes; UKRAINE.CAM is larger to accommodate its 50-mission list.
All CAM files import from main.dll (= the game executable — see
architecture.md) and
export a campaign-specific set of functions that the engine calls to drive
campaign state.
CODE Section Binary Layout — confirmed¶
AnalyzeCAMDLL.java scanned the executable blocks (CODE section). Full string
dump of UKRAINE.CAM CODE section (0x1000–0x19ff, 2560 bytes):
| CODE offset | Content | Role |
|---|---|---|
0x1075 |
"Fighters Anthology (CD 1)" |
Required disc label |
0x108f |
"FA_4C.LIB" |
LIB archive for CD assets |
0x112e |
"SU33" |
Aircraft type available in campaign |
0x1166–0x11c9 |
F150.GAS–F500.GAS (4 entries) |
Fuel tank BRF files |
0x11ea–0x141d |
AA11.JT–AAS38.SEE (18 weapon entries) |
Weapon/sensor store pool |
0x1477 |
"F22N" |
Second aircraft type (F-22 Night) |
0x149a–0x158f |
U01I–U50I (50 × 5 bytes) |
Mission slot IDs — initial/available state keys |
0x1594–0x16eb |
~U01.M–~U50.M (50 × 7 bytes) |
Mission filename list (7 bytes each, null-terminated) |
0x16eb–0x17bb |
(0xD1 bytes — all 0x00) |
Null padding — confirmed by direct hex dump; no decoded content |
0x17bc–0x17d2 |
UMEDAL, UDEAD, UWON, ULOST |
Campaign outcome state IDs |
0x17e3–0x1873 |
U01O, U03O, U05O, … U49O (25 × odd missions) |
Secondary mission outcome IDs (odd-numbered missions only) |
KURILE.CAM uses the same layout with prefix K and 35 missions; its CODE
section is 0x1000–0x17ff (2048 bytes). Mission list starts at 0x14e1
(shorter weapon table). VIETNAM.CAM (T prefix, 25 missions), EGYPT.CAM
(E), BALTIC.CAM (B), VLAD.CAM (V) follow the same schema.
Embedded Data¶
Each .CAM DLL embeds its campaign configuration directly as flat arrays and
null-terminated strings:
CD and LIB reference:
Fighters Anthology (CD 1)
FA_4C.LIB
The campaign specifies which installation disc and LIB archive it requires for its assets.
Aircraft availability — named aircraft type identifiers listed before the
weapon table; e.g. UKRAINE.CAM includes SU33, F22N; BALTIC.CAM includes
E2000, GRIPEN, RAFALE, ASTOVLF. These are the aircraft the campaign
makes available to the player.
Weapon / stores tables — a sequence of BRF asset filenames (JT, GAS, SEE, ECM extensions) defines the weapon and sensor loadout pool for the campaign:
GSH301.JT M61.JT GAU12.JT GSH30.JT ADEN.JT ...
F150.GAS F250.GAS F350.GAS F500.GAS
AAS38.SEE ALQ167.ECM
AA11.JT AA12.JT AIM7.JT AIM9M.JT AIM120.JT ...
Mission list — null-terminated strings of the form ~<prefix><NN>.M, one
per mission in sequence order. The ~ prefix on mission filenames is the
game's notation for LIB-resident mission files.
Campaign state identifiers — short string keys track per-mission and campaign-wide state:
| Pattern | Example | Meaning |
|---|---|---|
<pre>NNI |
U01I, U02I |
Mission initial (available/locked) state |
<pre>NNO |
U01O, U03O |
Mission objective complete flag (odd-indexed in UKRAINE) |
<pre>MEDAL |
UMEDAL |
Campaign medal awarded |
<pre>DEAD |
UDEAD |
Player death recorded |
<pre>WON |
UWON |
Campaign won flag |
<pre>LOST |
ULOST |
Campaign lost flag |
Reference Chain: .CAM → .M → .MM / .LAY / .MC¶
The .CAM DLL holds only its own mission list (~<prefix>NN.M strings). It
does not reference .MM theater files, .LAY sky files, or .MC
condition files directly. Those references are carried by each .M mission
file via three keywords:
| Keyword | Target | Example |
|---|---|---|
map |
.MM theater file |
map ukr.T2 → loads UKR.MM |
layer |
.LAY sky file |
layer day2.LAY 0 |
code |
.MC condition DLL |
code u01 → loads U01.MC; code extra01 → loads EXTRA01.MC |
The .MC file to load is determined by the code directive in the .M file,
not by the .CAM DLL. Most missions use a unique per-mission .MC (U01.MC,
K16.MC, etc.); bonus missions share the generic EXTRA01.MC gate via
code extra01.
File Inventory¶
| File | Missions | Theater prefix |
|---|---|---|
| BALTIC.CAM | 40 (~B01.M–~B40.M) |
B |
| EGYPT.CAM | 40 (~E01.M–~E40.M) |
E |
| KURILE.CAM | 35 (~K01.M–~K35.M) |
K |
| UKRAINE.CAM | 50 (~U01.M–~U50.M) |
U |
| VIETNAM.CAM | 25 (~T01.M–~T25.M) |
T |
| VLAD.CAM | 40 (~V01.M–~V40.M) |
V |
All six live in FA_2.LIB.
Engine Notes¶
Loading Mechanism¶
FUN_00428412 (0x428412) is the canonical the game executable campaign/mission loader.
Called from the mission-map screen handler FUN_00422a71 (when
_curScreen == 3) and from FUN_0042a71a.
Execution sequence:
1. _MISSIONShutdown_0() — teardown prior mission
2. _MISSIONInit1_0() — engine pre-init
3. Select .mc_M file: s__mc_nato_M_004f0ca8 or s__mc_M_004f0ca0 based on
_natoFighters flag
4. _CallMissionProc_8(pcVar2, 0) — load the campaign DLL
5. Copy mission name string; call _CallMissionProc_8(&_missionName, 0) for
named missions
6. _MISSIONInit2_0() — post-DLL init; zeros six globals; calls
FUN_00422828, FUN_004242a0(0), FUN_00428340
7. _T_NamedTmaps_0() / _T_InitDictionary_0() — terrain dictionary
initialization
_CallMissionProc_8 (0x481940) is the central mission-DLL dispatcher. Its
callers: FUN_00428412, _ChooseScoreInit (0x441c60), _MISSIONTextProc@16
(0x481c10), _MISSIONCheckSuccess@0 (0x486860), and ?usnfmain@@YAXXZ
(0x403700 — main loop).
DLL Entry Point and Command Protocol¶
AnalyzeCAMDLL.java confirmed the dispatch function for KURILE.CAM
(representative of all CAM DLLs). The single exported entry point is at PE
code offset 0x1000 (FUN_00001000). The game executable calls it via
_CallMissionProc_8, passing a command byte as the first stack argument
(in_stack_00000004).
Command byte protocol:
| Cmd | Action |
|---|---|
0x00 |
No-op / init query — returns immediately |
0x01 |
String match + copy: scan DAT_000014e1 (mission name string) against current _missionName, then copy result to DAT_000017b6 |
0x02–0x03 |
No-op |
0x04 |
Flag check: if DAT_000017bc != 0, set DAT_000017a4 = 1 and invoke the campaign callback subfunction |
0x05–0x08 |
No-op |
Data globals are stored in the PE .data section at offsets around
0x1700–0x17C0. The mission list string table starts at DAT_000014e1.
Import Table (Functions CAM DLL Calls from the game executable)¶
The .idata section of each CAM DLL lists the the game executable functions it calls back
into. Note: these appear in the CAM DLL's import table (calls out to
The game executable) — they are not functions exported by the DLL.
Common imports (all campaigns):
| Function | Role |
|---|---|
_AddCampaignPlane |
Add aircraft to campaign fleet |
_AddCampaignStore |
Add weapon/store to campaign pool |
_CampaignPlanesLeft@0 |
Return remaining aircraft count |
_CheckCD |
Verify correct CD inserted |
_DoFadeout@0 |
Trigger screen fade transition |
_GetKeySlow |
Wait for key input |
_InitCampaignPilot |
Initialize pilot state for campaign start |
_SeqContinue |
Resume a cutscene sequence |
_SeqStart |
Start a cutscene sequence |
_campaignFailed |
Handle campaign failure outcome |
_campaignFailures |
Access failure count |
_campaignSucceeded |
Handle campaign success outcome |
_missionName |
Global: current mission name string pointer |
_playerDead |
Handle player aircraft loss |
@G_Flush@4 |
Flush graphics to screen |
Campaign-specific imports (KURILE.CAM example):
| Function | Role |
|---|---|
_KurileMedals |
Kurile-specific medal award logic |
_KurilePromotions |
Rank promotion handling |
_KurileQuit |
Campaign exit handler |
_KurileRescued |
Rescued pilot tracking |
UKRAINE.CAM, VIETNAM.CAM, etc. import analogous _Ukraine* / _Vietnam*
functions from the game executable.
Related¶
Formats: P — pilot save files store the active campaign .CAM
filename; M — .M mission files referenced by ~<prefix>NN.M
strings; MC — per-mission condition files; BRF — .JT,
.GAS, .SEE, .ECM weapon type files listed in the weapon table.
Engine: architecture.md — the overlay DLL loading architecture.
MC — Mission Condition Script (.MC)¶
FA_2.LIB contains 21 .MC files. Each implements the runtime condition checks
for a specific mission event — trigger conditions, completion logic, and
failure detection. Each is a Win32 PE DLL loaded at runtime; all observed
files decompress to 4608 bytes.
Tools¶
fx¶
fx mc info <file.MC> # container check + CODE section geometry
fx mc strings <file.MC> [-n MIN] # embedded strings (incl. API imports)
Same MZ + Phar Lap PL container family as CAM; all 21 shipped
files validate. (The plain-text .mc_M campaign scripts described below are
a separate mechanism, not covered by this codec.)
File Layout¶
All multi-byte integers are little-endian.
Win32 PE DLL. String analysis of all .MC files reveals the mission condition
API imported from the game executable:
| Import | Description |
|---|---|
@OBJAlias@8 |
Look up a game object by its alias ID |
@OBJGet@4 |
Get a game object by index (EXTRA01.MC) |
_Dist@8 |
Compute distance between two objects |
_MISSIONSuccess@0 |
Trigger mission success outcome |
_MSGSendChatter@24 |
Send a radio chatter message to the player |
_OnTheGround@0 |
Test whether an object is on the ground |
_PopCurObj@0 |
Pop the current object from the evaluation stack |
_PushCurObj@4 |
Push an object onto the evaluation stack |
_currentTime |
Global: current game time in ticks |
_playerId |
Global: the player's object ID |
These are physics/world-state query functions — the .MC DLL polls game state
each tick to detect mission trigger conditions (e.g. player landed, target
destroyed, distance threshold crossed).
Condition Function Protocol — confirmed¶
AnalyzeMCDLL.java confirmed the condition function for U34.MC
(representative of all single-condition MC DLLs). The exported entry is at PE
code offset 0x1000.
Function signature:
short FUN_00001000(short param_1, undefined4 param_2, undefined4 param_3, short *param_4);
Command encoding via *param_4:
*param_4 |
Behavior |
|---|---|
0x20 (32) |
Pass-through: return param_1 unchanged (query current state) |
0x00 |
Evaluate condition: if DAT_00001212 != 0 AND DAT_00001211 == 0, return DAT_00001212; else return 0 |
Data section layout (PE offsets):
- DAT_00001211 (offset 0x1211): alive/active inhibit flag — condition
fires only when this is zero
- DAT_00001212 (offset 0x1212): target status byte — the success code
returned when the condition triggers
The game executable writes these bytes to track the mission state for the DLL. When
*param_4 = 0x00, if the target is destroyed (DAT_00001212 != 0) and not
still alive (DAT_00001211 == 0), the MC DLL returns the success code and
The game executable calls _MISSIONSuccess@0 to end the mission.
The .idata string scan confirmed _MISSIONSuccess at offset 0x207F and
_OBJAlias at offset 0x2065.
Campaign Condition Text Files (.mc_M, .mc_nato_M)¶
These are distinct from the .MC PE DLL files above. The campaign engine
(FUN_00428412) loads campaign-wide condition scripts as plain-text files
with suffix .mc_M (standard campaign) or .mc_nato_M (NATO campaign
variant). Loaded via CallMissionProc → MISSIONTextProc (text parser using
FUN_00483c90 as tokenizer + __strlwr).
Confirmed keywords parsed by MISSIONTextProc:
| Keyword | Effect |
|---|---|
textformat |
Sets file format version/type flag |
briefmap |
Sets doBriefMap = 1 (briefing map active) |
selectplane |
Sets doSelectPlane = 1 (plane selection screen) |
armplane |
Sets doArmPlane = 1 (arming screen) |
layer |
Reads layer name and integer parameter |
| (startup coords) | Reads 3 fixed-point world-space coordinates (× 256) |
These files are stored in the LIB archive with .mc_M / .mc_nato_M
suffixes, not .MC. They are text keyword files, not Win32 PE DLLs.
File Inventory¶
All 21 filenames:
CATFAIL.MC, EXTRA01.MC, FOO.MC, K16.MC, K17.MC, TRAIN01.MC,
U01.MC, U07.MC, U08.MC, U11.MC, U12.MC, U15.MC, U22.MC,
U23.MC, U24.MC, U25.MC, U29.MC, U34.MC, UKR01.MC, UKR02.MC,
VIET03.MC
U*.MC— Ukraine campaign mission conditions (not all 50 missions have a dedicated.MC; only those with non-trivial trigger logic)K*.MC— Kurile campaign missionsVIET03.MC— Vietnam campaign mission 3CATFAIL.MC— carrier takeoff failure conditionTRAIN01.MC— training mission conditionUKR01.MC,UKR02.MC— Ukraine campaign-level eventsEXTRA01.MC— generic bonus-mission condition gate (uses@OBJAlias@8,@OBJGet@4,_MISSIONSuccess@0; no debug strings). Used by allEXTRA01.M–EXTRA20.Mstandalone bonus missions andBEXTRA01.M–BEXTRA13.MBaltic bonus missions via thecode extra01directive in each.Mfile.FOO.MC— developer timing test: embeds debug string"The time is now >= 10 seconds!", uses_currentTimeand_MSGSendChatter@24withRADIOBP.5Kaudio; name is a classic programmer placeholder
Engine Notes¶
Dispatch Chain — confirmed¶
The full MC dispatch chain from game entry:
?usnfmain@@YAXXZ (0x403700) -- main loop
FUN_00428412 (0x428412) -- campaign/mission loader
└─ _CallMissionProc_8 (0x481940) -- central DLL dispatcher
├─ _MISSIONInit2_0 (0x480b50) -- post-load init; assigns _eventFilterProc
├─ _MISSIONTextProc@16 (0x481c10) -- re-entry: text condition parse
└─ _MISSIONCheckSuccess@0 (0x486860) -- re-entry: success test
_MISSIONInit2_0 (0x480b50) iterates all _objPtrs, calls _MAPSetSide_4,
_OBJFindHumans_0, _OBJAliasAll_12, _OBJAliasForMulti_0, _TIMEInit_12,
_G_SetScaleMax_8, then loads the mission DLL via
_RMAccess_8(&_missionDLLName__3PADA, 0x8000), storing the result in
_eventFilterProc.
Confirmed condition keyword consumers:
| Keyword | Consumer functions |
|---|---|
DESTROY |
FUN_0043a5c0, FUN_00431ab0, FUN_0044fe10 |
FAIL |
FUN_004a2a41 |
tmap |
_MISSIONTextProc@16 (string at 0x4fc228); FUN_00495e80 (.MC handler at 0x495e80, string at 0x5010f4) |
The cond keyword does not appear as a handled keyword in
_MISSIONTextProc@16 — exhaustive read of the ~1,500-line decompile
(DumpAllFunctions.txt lines 101716–103259) found no "cond" string
comparison or dispatch branch. cond is absent from FA's condition parser; it
may be a keyword from a different Jane's title or an unreachable dead branch
stripped from this build.
Related¶
Formats: CAM — campaign engine that loads .MC files to
evaluate mission state; M — .M mission files whose code directive
selects the .MC to load.
AI — Object AI Script (.AI)¶
Each .AI file defines the AI behaviour for one object class using a custom
goto-based scripting language — plain ASCII text, CRLF line endings, 9
files in FA_2.LIB. A companion compiled .BI file exists for each script of
the same base name — see BI.md.
Tools¶
fx¶
fx ai compile <file.AI> -o <file.BI> # full parse + compile to BI bytecode DLL
fx bi decompile <file.BI> # recover AI source from fx-compiled bytecode
fx ai compile is a complete parser/validator for the language documented
below. fx bi decompile (BI.md) is its inverse: it reconstructs AI
source whose recompilation is byte-identical to the input, closing the
compile→decompile→recompile loop for every BI fx produces. Synthesized labels
(L####) replace the original label names, and comments are not recovered.
File Layout¶
Plain text; no binary fields.
Execution Model¶
The engine calls each object's AI script once per tick. Execution starts at
the top of the file and falls through the opening if dispatch chain. The
first condition that matches jumps to the corresponding handler label. exit
returns control to the engine; restart re-enters the script from the top on
the next tick.
; top-level dispatch (evaluated every tick)
if do_nothing goto nothing
if do_ir_launch goto ir_launch
...
exit
nothing:
exit
ir_launch:
<instructions>
restart
Syntax¶
Comments:
; anything after semicolon is a comment
Labels: any identifier followed by :. Labels are local to the file.
Variables: four general-purpose integer registers: %a, %b, %c, %d.
%a = <expr> ; assign
%a = alt ; read numeric attribute
%a = random 100 ; random 0–99
%a = h + 45 ; arithmetic on engine values
%a = turnRadius * 3
%a = 50 - random 20
Conditional branches — single-line: if <condition> goto <label>;
block form:
.if <condition>
<instructions>
.else ; optional
<instructions>
.endif
Conditions may be combined with &&, ||, and not. Trailing , is
equivalent to && (observed in F.AI).
Control flow:
| Statement | Description |
|---|---|
exit |
Return to engine; script resumes at top next tick |
restart |
Re-enter script from top immediately |
goto <label> |
Unconditional jump |
Engine State Flags (do_*)¶
Set by the engine before each tick. All 9 files dispatch on at least
do_nothing through do_attack.
| Flag | Meaning |
|---|---|
do_nothing |
No action required |
do_ir_launch |
Fire IR (heat-seeking) weapon |
do_radar_launch |
Fire radar-guided weapon |
do_hit |
Object has been hit |
do_evade |
Evade incoming threat |
do_attack |
Engage target |
Conditions¶
Boolean attributes:
| Attribute | Description |
|---|---|
tgt |
Has an active target |
tgtAhead |
Target is within forward arc |
tgtFacing |
Target is facing this object |
tgtIsPlane |
Target is an aircraft (not ground unit) |
tgtIsFighter |
Target is fighter class |
tgtHumanControl |
Target is human-controlled |
canClimb / canclimb |
Aircraft has energy to climb |
betterSpeed |
Speed advantage over target |
betterTwr |
Thrust-to-weight advantage over target |
wingCombat |
Wingman is currently engaged |
wingApproach |
Wingman is on approach |
Numeric attributes (used in comparisons: <, >, <=, >=, ==):
| Attribute | Description |
|---|---|
alt |
Current altitude (feet) |
speed |
Current airspeed |
distToTgt |
3D distance to target |
hrzDistToTgt |
Horizontal distance to target |
altDiff / altdiff |
Altitude difference vs target |
hdiff |
Heading difference vs target |
pdiff |
Pitch difference vs target |
tgtOffBeam |
Target off-beam angle (degrees) |
speedDiff |
Speed difference vs target |
turnRadius |
Current turn radius |
minSpeed |
Minimum flyable speed |
skill |
AI skill level (integer, 0 = lowest) |
b |
Internal counter (used in loop constructs) |
p |
Internal parameter |
Arithmetic is valid in comparisons: alt < turnRadius * 3,
distToTgt < minSpeed + 500.
Probability conditions:
| Condition | Description |
|---|---|
chance <int> |
True if a global counter equals the value — used as a sparse timer |
percent <int> |
True with probability N% (0–100) |
random <int> |
Evaluates to a random value 0–(N−1); used in expressions like random 3 > 1 |
Instructions¶
Movement:
| Instruction | Signature | Description |
|---|---|---|
move |
move <hdg> <angle> <alt> <speed_mode> <duration> |
Fly to heading/altitude. <angle> = bank angle (0 = wings level, 180 = inverted); <alt> scaled ×0xb6 internally (0x7fffffff = altitude-unlimited); <speed_mode> = one of corner/max/etc.; <duration> = integer 0–15. Confirmed from _CTDo_move (0x465cc0) bytecode pop order: heading, angle, altitude, speed, duration. |
moveToAlt |
moveToAlt <hdg> <alt> maxSpeed <value> |
Climb or descend to altitude |
homePos |
homePos <hdg> <alt> <alt2> corner <value> |
Return to home position |
homeAngle |
homeAngle <hdg> <alt> <speed_mode> <roll> <value> |
Fly to home angle |
jink |
jink <hdg> <defl_angle> <defl1> <defl2> … <count> <speed_mode> <duration> |
Jinking evasive maneuver. <defl_angle> = base deflection; <defl1>/<defl2> = alternating left/right deflection magnitudes; <count> = jink repetitions 0–4; <speed_mode> = speed control (same domain as move speed — clamped to COMinSpeed..COMaxSpeed by FUN_00465e00); <duration> = integer 0–15. Confirmed from _CTDo_jink (0x4663f0) → _MVRJink@40 (0x4ac9e0): param_8=count, param_9=speed, param_10=duration, param_3/param_4=deflection angles. |
circle |
circle <cx> <cy> <cz> <radius> <alt> <speed> |
Orbit a point (AC130 only) |
wm_break |
wm_break <angle> engageP |
Break away from wingman |
wm_approach |
wm_approach <offset> <engageP\|int> corner |
Wingman approach |
move roll argument: any = no bank constraint (engine picks optimal);
0 = wings level (upright); 180 = inverted. Comments in F.AI confirm:
move %a 0 180 corner 1 = "roll over on my back, staying horizontal";
move %a + 180 0 0 corner 0 = "roll out to level". The engageP keyword is a
valid value for the <alt> argument (altitude of the engage/attack waypoint),
not the roll argument.
Maneuvers:
| Instruction | Description |
|---|---|
maneuver "<name>" |
Execute a named preset maneuver (displayed to player) |
immelman corner |
Immelmann turn |
invert |
Push-over / invert |
yoyo <alt> corner <value> |
Yo-yo maneuver |
btoh |
Barrel turn onto heading |
Control:
| Instruction | Signature | Description |
|---|---|---|
switch |
switch random <N> <label1> … <labelN> |
Jump to one of N labels chosen uniformly at random |
Named Maneuvers¶
Maneuver names are trilingual strings: "<English>;<German>;<French>". The UI
displays the locale-appropriate segment.
| Name |
|---|
"BREAK LEFT;LINKS ABDREHEN;APPROCHE GAUCHE" |
"BREAK RIGHT;RECHTS ABDREHEN;APPROCHE DROITE" |
"CLIMB;STEIGEN;MONTEE" |
"DIVE BOMB;STURZANGRIFF;BOMBARDER" |
"DIVE;STURZFLUG;PIQUE" |
"FAST-HIGH;SCHNELL-HOCH;APPROCHE DU HAUT" |
"GND ATTACK;BODENANGRIFF;ATTAQUE AU SOL" |
"LOOP;LOOPING;BOUCLE" |
"OFFSET PASS;VORBEIFLUG SEITE;PASSE LATERALE" |
"OVERHEAD PASS;VORBEIFLUG OBEN;PASSE HAUTE" |
"OVERSHOOT;ÜBERSCHUSS;OVERSHOOT" |
"POP-UP;POP-UP;ATTAQUE SURPRISE" |
"PURSUIT;VERFOLGUNG;POURSUITE" |
"REVERSE;ABSCHWUNG;180" |
"SCISSORS;SCISSORS;CISEAUX" |
"SEPARATE;LÖSEN;SEPARATION" |
"SPLIT-S;SPLIT-S;IMMELMANN" |
"STRAIGHT;GERADEAUS;TOUT DROIT" |
"UNDERNEATH PASS;VORBEIFLUG UNTEN;AU-DESSOUS" |
"VERT SCISSORS;VERTIKALSCHERE;CISEAUX VERTIC." |
Engine-defined switch target labels (no maneuver string needed):
fastHigh, popup, offsetPass, overheadPass, homeOnTgtRear,
homeAboveBelow, straightClimb, homeOnTarget, straightDive,
vertScissors, split_s, immelman, breakLeft, breakRight, h_jink,
v_jink, turnAround.
File Inventory¶
| File | Size | Object class |
|---|---|---|
| AC130.AI | 3,728 B | AC-130 Spectre gunship |
| B.AI | 3,970 B | Bomber |
| F.AI | 20,616 B | Fighter (primary; shared by most aircraft) |
| F117.AI | 18,823 B | F-117 stealth (based on F.AI) |
| H.AI | 12,412 B | Helicopter |
| HYDRO.AI | 1,816 B | Hydrofoil / fast patrol boat |
| LARGE.AI | 960 B | Large ship |
| LINER.AI | 917 B | Ocean liner |
| MOTH.AI | 18,422 B | Moth (variant of F.AI) |
All 9 live in FA_2.LIB.
Engine Notes¶
The _CTDo_* and _CTEval_* condition/action dispatcher functions exist in
the game executable itself at VA range 0x464C80–0x467110 — not only in the
companion .BI DLL files. This means the interpreter core is compiled into
the main executable; the .BI DLLs supply per-object script data but delegate
dispatch back to the game executable's built-in handlers.
Related¶
Formats: BI — compiled binary companion, one per .AI file;
BRF — object type records (.OT, .NT, .PT) that reference AI
files by name.
BI — Compiled AI Runtime (.BI)¶
FA_2.LIB contains 9 .BI files — exactly one per .AI script file (e.g.
AC130.BI paired with AC130.AI). Each is a Win32 PE DLL whose CODE
section contains only compiled AI bytecode; the runtime operations it
references live in the game executable.
Tools¶
fx¶
fx bi dump <file.BI> # disassemble bytecode to mnemonics
fx bi decompile <file.BI> # recover recompilable AI source
fx ai compile <file.AI> -o <file.BI> # produce a BI from AI source
fx bi decompile is the inverse of fx ai compile: it reconstructs AI source
whose recompilation is byte-identical to the input, so
ai_compile(ai_decompile(bi)) == bi for every BI fx produces. It reads the
fx bytecode dialect (CALL_BY_NAME); the stock game BIs use the original
toolchain's linked CALL_DIRECT thunks, so fx bi decompile rejects them —
use fx bi dump to disassemble those (see § Round-Trip Notes).
File Layout¶
All multi-byte integers are little-endian.
The FA engine runs a two-part AI system:
.AI— plain-text source script compiled to bytecode at build time; defines the logic (conditions, branches, actions) in a goto-based language.BI— Phar Lap PE DLL whose CODE section contains only compiled AI bytecode (no x86 machine code). All_CTDo_*and_CTEval_*action/condition implementations live in the game executable; the.BIimports them via its.idatasection. The bytecode starts at the very first byte of the CODE section (raw file offset0x400).
At runtime the engine loads the .BI, resolves its .idata imports against
The game executable, and calls _CTExecProgram@4, which reads bytecode from the BI CODE
section and dispatches to the _CTDo_* and _CTEval_* functions in the game executable
via CALL_BY_NAME/CALL_DIRECT opcodes.
Exported Functions¶
All .BI files export two families of functions, prefixed _CT:
Action functions (_CTDo_*) — called by the bytecode interpreter when
executing an action instruction:
| Export | AI instruction |
|---|---|
_CTDo_btoh |
btoh |
_CTDo_circle |
circle |
_CTDo_exit |
exit |
_CTDo_homeangle |
homeAngle |
_CTDo_homepos |
homePos |
_CTDo_immelman |
immelman |
_CTDo_invert |
invert |
_CTDo_jink |
jink |
_CTDo_maneuver |
maneuver |
_CTDo_move |
move |
_CTDo_movetoalt |
moveToAlt |
_CTDo_restart |
restart |
_CTDo_wm_approach |
wm_approach |
_CTDo_wm_break |
wm_break |
_CTDo_wm_hspacing |
(internal wingman spacing) |
_CTDo_yoyo |
yoyo |
Simpler .BI files (AC130.BI) export only the subset of actions used by that
aircraft class.
Condition functions (_CTEval_*) — called by the bytecode interpreter
when evaluating a condition:
| Export | AI attribute / condition |
|---|---|
_CTEval_alt |
alt |
_CTEval_altdiff |
altDiff |
_CTEval_b |
b (internal counter) |
_CTEval_betterspeed |
betterSpeed |
_CTEval_bettertwr |
betterTwr |
_CTEval_canclimb |
canClimb |
_CTEval_corner |
corner / cornerSpeed |
_CTEval_disttotgt |
distToTgt |
_CTEval_do_attack |
do_attack |
_CTEval_do_evade |
do_evade |
_CTEval_do_hit |
do_hit |
_CTEval_do_ir_launch |
do_ir_launch |
_CTEval_do_nothing |
do_nothing |
_CTEval_do_radar_launch |
do_radar_launch |
_CTEval_engagep |
engageP |
_CTEval_h |
heading |
_CTEval_hdiff |
hdiff |
_CTEval_hrzdisttotgt |
hrzDistToTgt |
_CTEval_htotgt |
heading-to-target |
_CTEval_maxspeed |
(max speed attribute) |
_CTEval_minspeed |
minSpeed |
_CTEval_p |
p |
_CTEval_pdiff |
pdiff |
_CTEval_skill |
skill |
_CTEval_speed |
speed |
_CTEval_speeddiff |
speedDiff |
_CTEval_tgt |
tgt |
_CTEval_tgtahead |
tgtAhead |
_CTEval_tgtfacing |
tgtFacing |
_CTEval_tgthumancontrol |
tgtHumanControl |
_CTEval_tgtisfighter |
tgtIsFighter |
_CTEval_tgtisplane |
tgtIsPlane |
_CTEval_tgtoffbeam |
tgtOffBeam |
_CTEval_turnradius |
turnRadius |
_CTEval_wingapproach |
wingApproach |
_CTEval_wingcombat |
wingCombat |
_CTEval_wm_hspacing_is |
(wingman horizontal spacing) |
Maneuver name strings — the trilingual maneuver name strings (e.g.
"GND ATTACK;BODENANGRIFF;ATTAQUE AU SOL") documented in AI.md are
embedded as data inside F.BI (the primary fighter AI runtime).
_CTDo_maneuver reads these strings and passes the locale-appropriate segment
to the UI.
Opcode Table — confirmed¶
| Opcode | IP advance | Name | Description |
|---|---|---|---|
0x00 |
1 | NOP | No operation |
0x25 ('%') |
— | END | End of program (also the main-loop terminator) |
0x01 |
5 | PUSH_DWORD | Push (int32)(IP+1) |
0x02 |
3 | PUSH_WORD | Push (int16)(IP+1) sign-extended |
0x03 |
2 | PUSH_BYTE | Push (int8)(IP+1) sign-extended |
0x04 |
1 | EVAL | Call FUN_00465ad0 (pop eval-stack top) |
0x05 |
2 | STORE_VAR | Pop → var[(byte)(IP+1)] via FUN_004670e0 |
0x06 |
2 | LOAD_VAR | Push var[(byte)(IP+1)] via FUN_004670e0 |
0x07 |
varies | PUSH_ADDR | Push (IP+1 − base); advance IP past null-terminated string name |
0x08 |
1 | MUL | Pop a, b; push b×a |
0x09 |
1 | DIV | Pop a, b; push b/a (returns 0 if a=0) |
0x0A |
1 | MOD | Pop a, b; push b%a (returns 0 if a=0) |
0x0B |
1 | ADD | Pop a, b; push b+a |
0x0C |
1 | SUB | Pop a, b; push b−a |
0x0D |
1 | AND | Pop a, b; push b&a (bitwise) |
0x0E |
1 | OR | Pop a, b; push b|a (bitwise) |
0x0F |
1 | XOR | Pop a, b; push b^a |
0x10 |
1 | SHL | Pop a, b; push b<<a |
0x11 |
1 | SHR | Pop a, b; push b>>a (arithmetic) |
0x12 |
1 | LT | Pop a, b; push (b < a) |
0x13 |
1 | LE | Pop a, b; push (b ≤ a) |
0x14 |
1 | GE | Pop a, b; push (b ≥ a) |
0x15 |
1 | GT | Pop a, b; push (b > a) |
0x16 |
1 | EQ | Pop a, b; push (b == a) |
0x17 |
1 | NE | Pop a, b; push (b ≠ a) |
0x18 |
1 | LAND | Pop a, b; push (b≠0 && a≠0) |
0x19 |
1 | LOR | Pop a, b; push (b≠0 || a≠0) |
0x1A |
1 | ABS | Pop a; push abs(a) |
0x1B |
1 | NEG | Pop a; push −a |
0x1C |
1 | NOT | Pop a; push (a == 0) |
0x1D |
1 | RANDOM | Pop N (0–65535); push random(0..N−1) via engine RNG |
0x1E |
1 | PERCENT | Pop N; push (random_100 < N) |
0x1F |
1 | CHANCE | Pop N; scale by skill level (÷100 per level > 2); push (random_100 < scaled_N) |
0x20 |
3 | GOTO | Read s16 offset; jump to base + offset |
0x21 |
3 | PUSH_GOTO | Push (IP+1 − base), then execute GOTO with following s16 offset |
0x22 |
1 | JUMP | Pop addr; jump to base + addr |
0x23 |
3 | IF_FALSE | Pop cond; if cond==0: jump to base + s16 offset; else skip 2 bytes |
0x24 |
varies | SWITCH | Pop idx; if 0 ≤ idx < N: jump to indexed table (1+N×2 bytes); else skip table |
0x26 |
5 | CALL_DIRECT | IP += 5; call (code*)(IP+1); push return value |
0x27 |
varies | CALL_BY_NAME | Look up null-terminated name, call; push return value; self-patches to CALL_DIRECT for subsequent calls (JIT optimization) |
0x28 |
5 | FRAME | Read 2 s16 values into DAT_00546c44/DAT_00546c46; IP += 4 |
File Inventory¶
| BI file | Size | AI source size |
|---|---|---|
| AC130.BI, B.BI, HYDRO.BI, LARGE.BI, LINER.BI | 4,608 B | 960–3,970 B |
| H.BI | 8,704 B | 12,412 B |
| F.BI, F117.BI, MOTH.BI | 12,800 B | 18,423–20,616 B |
For complex scripts (F, H) the bytecode is more compact than the source text.
For simple scripts (LINER, LARGE) the PE overhead — headers, export table,
native function bodies — exceeds the bytecode size, so the BI is larger than
its .AI source. The original compiler was internal to FA's toolchain and not
distributed; fx ai compile is the working reimplementation.
Engine Notes¶
Bytecode Interpreter¶
The interpreter is _CTExecProgram@4 (CTExecProgram). It executes at most
5000 opcodes per call, then forcibly invokes CTDo_exit to prevent infinite
loops.
Runtime state globals:
| Global | Role |
|---|---|
DAT_00546bea |
Instruction pointer — char* into the loaded BI CODE section |
DAT_00546bf0 |
Current script priority level (compared against param_1 passed by caller) |
DAT_00546c94 |
Pointer to the current actor's live object record |
DAT_00546c88 |
Actor type flag: 1 if actor type is 2 or 4 (fighter/bomber) |
DAT_00546c90 |
Execution result returned to caller (non-zero = script performed an action) |
DAT_00546c98 |
Halt flag — set non-zero to stop execution early |
DAT_0050cf6e |
Current actor slot index (0 = player) |
DAT_0050d312 |
CT system enable flag — interpreter is a no-op when this is zero |
DAT_00546bc8 |
Live CT state block — 128-byte (32-dword) struct; field +0x7c/+0x7e = FRAME state (DAT_00546c44/DAT_00546c46) |
DAT_0050cf90 |
Pointer to heap-allocated checkpoint copy of the CT state block (0x80 bytes) |
End-of-program marker: '%' (0x25) — the main loop checks *ip != '%' as
its loop condition.
State save/restore: the interpreter maintains a 128-byte live CT state
block at DAT_00546bc8 and a heap-allocated checkpoint copy pointed to by
DAT_0050cf90. Three functions manage this:
FUN_004668f0(0x4668f0) — restore: ifDAT_0050cf90 != NULL, copies 128 bytes from checkpoint → live block; if NULL, zeroes the live block and clearsDAT_00546bf0.FUN_00466920(0x466920) — save/push: ifDAT_0050cf90 == NULL, allocates 0x80 bytes via@MMAllocPtr@8(0x80, 0x8000); then copies live block → checkpoint and zeroes the live block._CTRespondToCancelCmdBuf@0(0x464c9d) — cancel handler: when_cg == 2 or 4(fighter/bomber class) andDAT_00546ca4 == 0, orchestrates restore →FUN_00464cd0(1)→ save. Enables preemptible script execution with re-entry on cancel events.
Opcode dispatch: FUN_00466a80 (0x466a80) reads one opcode byte from
*DAT_00546bea and dispatches (full opcode table above).
Evaluation stack: FUN_00466290 = push; FUN_00465ad0 = pop. Stack
base = DAT_00546bf2; depth = DAT_00546c42. Max depth = 32 dwords.
FUN_00466820 reports error codes (1=syntax error, 4=stack underflow,
5=stack overflow, 0xa=unknown opcode, 0xb=call by name to unknown proc,
0xc=stack imbalance).
Base address: DAT_00546be6 is the base pointer for the loaded BI CODE
section; all jump offsets are relative to this base.
FRAME opcode consumer (0x28) — conclusion¶
The writer is confirmed: FUN_00466a80 case 0x28 reads two s16 values
from the bytecode stream and writes them to DAT_00546c44 / DAT_00546c46
(CT state block +0x7c / +0x7e).
No scalar consumer exists anywhere. Exhaustive analysis closed this item:
- BI DLLs contain bytecode, not x86 code. The F.BI CODE section starts at
0x00001000; its first byte is0x28(the FRAME opcode itself). Ghidra's auto-analysis found zero functions after analyzing the BI project — the code section is pure bytecode data, not native machine code. There is no x86 reader in the BI DLLs. - Full the game executable interpreter path traced with no consumer found:
FUN_00466a80(opcode dispatch 0–0x28): no case reads+0x7c/+0x7e;_CTExecProgram@4(interpreter loop): only callsFUN_00466a80per opcode;FUN_00464cd0(script loader) andFUN_00464db0(PC reset): no field reads. DAT_00546c44/DAT_00546c46have no direct read xrefs in the game executable.- All reads of
DAT_0050cf90(checkpoint pointer) are bulk 128-byte block copies viaFUN_004668f0(restore) andFUN_00466920(save/push) — never field-level reads.
Conclusion: FRAME is a save-state metadata instruction. The two s16 values
it stamps into +0x7c/+0x7e are captured opaquely by the bulk 128-byte
save/restore operations but are never consumed by any scalar reader. The
values likely encode the current maneuver frame or animation phase for
checkpoint purposes. This item is closed.
Argument Readers¶
FUN_00465ad0 (0x465ad0) is the raw stack-pop function — pops one dword from
the 32-entry eval stack at DAT_00546bf2[DAT_00546c42 - 1]. The _CTDo_
handlers pop their arguments by calling higher-level wrappers that
additionally validate and convert units:
| Address | Name (inferred) | Return value | Converts from |
|---|---|---|---|
FUN_00465ad0 |
read_raw |
raw dword from eval stack | raw value |
FUN_00465d40 |
read_heading |
normalized heading in binary degrees (0–359° × 182) | normalizes to [0, 359], then × 182 |
FUN_00465c90 |
read_angle |
angle in binary degrees (clamped ±90° × 182) | clamps to [−90, 90], then × 182 |
FUN_00465da0 |
read_alt |
angle in binary degrees (clamped ±180° × 182) or 0x7FFFFFFF (any) — used for roll in CTDo_move |
clamps to [−180, 180], then × 182; passthrough if = 0x7FFFFFFF |
FUN_00465de0 |
read_duration |
unsigned int 0–15 (capped) | clamps to [0, 15] |
FUN_00465e00 |
read_speed |
speed in binary degrees, clamped to aircraft [min_speed, max_speed] | reads aircraft speed bounds at runtime |
CTDo_move — confirmed arg sequence¶
Calls MVRMove(heading, alt, roll_or_any, alt_is_any, vel_x, vel_y, speed, duration):
1. heading (binary degrees, 0–359° normalized) — from read_heading
2. alt (binary degrees, ±90°) — from read_angle
3. roll (binary degrees, ±180°, or 0x7FFFFFFF = any) — from read_alt
4. alt_is_any (bool) — derived from altitude arg being 0x7FFFFFFF (the any sentinel)
5–6. velocity carry-over from previous command (_DAT_00546c9c,
_DAT_00546ca0, zeroed after use)
7. speed (binary degrees, clamped to aircraft speed range) — from read_speed
8. duration (0–15 ticks) — from read_duration
MVRMove (_MVRMove): clamps alt to ±0x3FFC (±90°); when alt_is_any = true
→ maneuver type 6 (any altitude) / roll target = 0; when false → type 1, roll
target = roll arg.
CTDo_turn — confirmed arg sequence¶
- min heading (degrees, clamped to current turn rate via
COTurnRate) - max heading (clamped similarly)
- type/mode (5 = timed, 6 =
any-time/unconditional) - target heading in binary degrees (
arg * 182, i.e.arg * 65536/360) - ctrl
- duration
Round-Trip Notes¶
fx bi decompile reconstructs AI source from BI bytecode; recompiling that
source with fx ai compile reproduces the bytecode exactly. This closes the
loop as a fixed point of the fx toolchain — for any BI that fx itself
compiled, ai_compile(ai_decompile(bi)) == bi byte-for-byte
(tests/test_ai.cpp verifies this over all 9 stock scripts). Labels are
synthesized from bytecode offsets (L####) since the original names are not
encoded, and comments are not recoverable; neither affects the emitted
bytecode.
The stock game BIs are a different dialect and are not reproducible by
fx ai compile, so they do not round-trip byte-identically:
- Call linkage. The stock BIs resolve their
_CTDo_*/_CTEval_*references through the.idataimport table and invoke them withCALL_DIRECT(0x26) thunks.fxemits self-describingCALL_BY_NAME(0x27) with the name inline — a different opcode and a different byte length.fx bi decompilereads only theCALL_BY_NAMEform and rejectsCALL_DIRECTinputs; usefx bi dump, which resolves the thunks via.idata, to disassemble stock BIs. - FRAME placement. For an inline conditional (
if <cond> goto …), the original toolchain emitsFRAMEbefore the condition expression;fxemits it after. TheFRAMEoperands (a statement index and a source-line number) are also assigned differently —fxuses a monotonic index and a zero line. BecauseFRAMEis a save-state metadata opcode with no scalar consumer (§ Engine Notes), these differences are behaviourally inert but place the two toolchains' output on distinct byte layouts.
Reproducing the original toolchain's exact encoding would require reverse
engineering that compiler and is out of scope here; the recovered source is
semantically faithful and recompiles cleanly under fx.
Related¶
Formats: AI — plain-text AI script; uses the exports of the
paired .BI as its instruction set.
Type Definitions (BRF DSL)
BRF — Brent's Relocatable Format¶
BRF is a plain ASCII text container for all game type definitions. Seven
file extensions share the same tokenizer; the struct_type field
distinguishes them. This page is the family overview — each member format has
its own spec (see Related).
The "Relocatable" in the name refers to pointer relocation. Each BRF file
opens with a pointer table — the :name lines before the first \tend — that
enumerates every ptr field in the record by symbolic name. This is a
relocation table: in the plain-text format, ptr fields hold quoted filename
strings or NULL; in the engine's in-memory representation they become actual
pointers. At load time the engine walks the pointer table and resolves each
named field to a memory address, making the loaded record independent of where
it was placed — relocatable. The mechanism is the same concept as relocation
entries in a linker object file, applied to game data.
| Extension | struct_type | Contents |
|---|---|---|
.OT |
1 | Object type (generic game object) |
.NT |
3 | NPC type (AI unit / crew) |
.PT |
5 | Plane type (aircraft aerodynamics + avionics) |
.JT |
7 | Jettison type (projectile / weapon physics) |
.SEE |
10 | Seeker type (missile guidance) |
.ECM |
9 | ECM pod definition |
.GAS |
8 | Gas / fuel tank definition |
Tools¶
fx¶
# Same pattern for all seven extensions:
fx ot info <file.OT> # human-readable field dump
fx ot unpack <file.OT> [-o out.txt] # editable text
fx ot pack <in.txt> -o out.OT # write back (byte-identical)
fx nt info / unpack / pack
fx pt info / unpack / pack
fx jt info / unpack / pack
fx see info / unpack / pack
fx ecm info / unpack / pack
fx gas info / unpack / pack
Example: fx pt info F16C.PT → thrust, max_speed, fuel, stall speed, ceiling.
Other Tools¶
BRF files are plain ASCII — open and edit directly after fx unpack, no
further conversion needed.
- VS Code — free; multi-file search useful when cross-referencing
.PThardpoint names against.JTdefinitions - Notepad++ — free, Windows; lightweight for quick field edits
- Notepad / TextEdit — free, built-in; sufficient for small edits
File Layout¶
Plain text; no binary fields. (Hex values use the $XX prefix; fixed-point /
relative values use the ^XXXXX prefix.)
[brent's_relocatable_format]
:<ptr_name1>
:<ptr_name2>
\tend
<kind> <value>
<kind> <value>
...
\tend
[<section_name>]
<kind> <value>
...
\tend
Tokens¶
| Kind | Value syntax | C++ type |
|---|---|---|
byte |
decimal integer | uint8_t |
word |
decimal integer | uint16_t / int16_t |
dword |
decimal integer | uint32_t / int32_t |
ptr |
"filename" or NULL |
std::string (may be empty) |
symbol |
NAME |
std::string |
string |
"text" |
std::string |
\tend terminates each block. The pointer table (:name lines) comes first,
then the main field block, then optional named subsections.
OT Fields (Object Type)¶
OT versioning is determined by field count: V0=49, V1=51, V2=63, V3=64.
Key fields (abridged):
struct_type byte 1=OT, 3=NT, 5=PT, 7=JT, 8=GAS, 9=ECM, 10=SEE
type_size word
instance_size word
ot_names ptr single ptr to the name record, which holds the
short name, long name, and filename strings
(e.g. "F-16C" / "General Dynamics F-16C Fighting
Falcon" / "F16C.OT")
ot_flags dword see ot_flags table below
obj_class word see obj_class table below
shape ptr 3D model filename (no extension)
shadow_shape ptr shadow/crash shape; convention: NAME_S.SH
max_vis_dist word feet (max 204 typical; over ~30000 makes object silent)
camera_dist word
laser_targeting_sig word
ir_signature word
rcs_signature word
hit_points word
dmg_planes word damage dealt to each target type
dmg_ships word
dmg_structs word
dmg_armor word
dmg_other word
explosion_type byte
crater_size byte feet
empty_weight dword pounds
dmg_debris_pos i16[3] debris spawn offset on damage (x y z, feet)
dst_debris_pos i16[3] debris spawn offset on destruction
dmg_type dword
year_available dword earliest campaign year this object appears
(An earlier version of this list showed short_name/long_name/file_name
as three separate ptr fields; the binary struct confirmed by
PT.md byte-counting holds a single ot_names ptr to the name
record, and the filename is the LIB lookup key, not a stored field.)
ot_flags values:
| Value | Meaning |
|---|---|
$6bf3 |
Flyable aircraft (player-selectable) |
$2bf3 |
Non-flyable (AI-only) |
$8xxxxxx prefix |
Hidden from in-game reference library |
obj_class values:
| Value | Meaning |
|---|---|
$8000 |
Fighter |
$4000 |
Bomber |
$2000 |
Ship |
$1000 |
Structure |
$0800 |
Vehicle / armor |
PT Fields (Plane Type)¶
PT extends OT with ~80 additional aerodynamic and avionics fields, beginning
immediately after the NT section in the BRF file. In the source text the block
is introduced by the comment divider ; ---- START OF PLANE_TYPE ----
(verified against a live .PT; the only bracketed name in the file is the
top-of-file [brent's_relocatable_format] tag — sections are comment-delimited,
OBJ_TYPE → NPC_TYPE → PLANE_TYPE).
Carrier / datalink / thrust-vectoring dword — the first dword of the PT section is a flag word controlling several systems:
| Value | Meaning |
|---|---|
$53 |
Carrier-capable, single-seat |
$57 |
Carrier-capable, two-seat |
$55 |
Land-based only (no carrier) |
$20 prefix |
ATA (air-to-air) datalink |
$40 prefix |
ATG (air-to-ground) datalink |
$60 prefix |
Both ATA + ATG datalink |
$91 suffix |
Horizontal-axis thrust vectoring |
$591 suffix |
Horizontal + vertical thrust vectoring (3D) |
Example: $4591 = ATG datalink + full 3D thrust vectoring.
Core aerodynamic fields:
carrier_flags dword see table above
env ptr → G-envelope section
neg_g_count word number of negative-G envelope entries (negative number)
pos_g_count word number of positive-G envelope entries
max_speed_sl word mph at sea level
max_speed_36k word mph at 36,000 ft
accel_runway word acceleration on runway
decel_runway word deceleration on runway
roll_speed_min word deg/sec (negative)
roll_speed_max word deg/sec
pull_rate word pitch pull rate
neg_g_limit word
; --- 59 more aero words follow here (0xD6–0x14B): control-authority limit
; vectors, roll/pitch/yaw axis limits, and the stall/spin block below,
; all code-traced — see PT.md § The 65-word aerodynamic block ---
num_engines byte
military_thrust dword lbf
afterburner_thrust dword lbf
throttle_accel word percent/sec
throttle_decel word percent/sec
tv_min_angle word thrust-vectoring min angle (−60 = 60°)
tv_max_angle word thrust-vectoring max down-angle
tv_speed word deg/sec
fuel_consumption_mil word at military power
fuel_consumption_ab word at afterburner
fuel_capacity dword pounds
aero_drag word 256 = baseline
g_drag word drag increase per G
airbrake_drag word
wheel_brake_drag word
flap_drag word
gear_drag word
weapons_bay_drag word
flaps_lift word
drag_loaded word extra drag when fully loaded
g_drag_loaded word
gear_pitch word nose-up angle on ground (e.g. 5 = taildragger)
max_landing_speed word ft/sec
max_side_speed word ft/sec
max_sink_rate word ft/sec
max_landing_pitch word degrees
max_landing_roll word ft/sec roll-out distance
structural_warn word speed limit warning (ft/sec)
structural_limit word hard speed limit (ft/sec)
mtow dword max take-off weight, pounds
misc_per_flight word maintenance man-hours per flight
repair_multiplier word repair cost multiplier
Stall / spin fields: these sit inside the aero block at PT offsets
0x128–0x13E (words 47–58), immediately before the gear/landing gate — see
PT.md § The 65-word aerodynamic block
for the exact offset map and the engine readers that confirm each name.
stall_warn_delay word clocks (1 clock = 1/256 sec)
stall_duration word
stall_severity word
stall_pitch_down word deg/sec pitch-down during stall
spin_entry_ease word 0 = harder
spin_exit_ease word negative = harder
spin_yaw_low word deg/sec
spin_yaw_high word
spin_aoa_low word degrees
spin_aoa_high word
spin_bank_low word degrees
spin_bank_high word
G-envelope section — each envelope entry covers one G-load level and lists up to 16 speed/altitude pairs defining the aircraft's performance boundary at that G:
[env_entry]
gload word e.g. -4, -3, … 9
count word number of valid speed/altitude pairs
stall_lift word index of stall boundary in data[]
max_speed word index of max-speed boundary in data[]
data[0..15]:
speed word ft/sec
altitude dword feet
Unused slots are zeroed. A typical FA aircraft has 4 negative-G and 9 positive-G entries.
Hardpoints — each PT has up to 9 hardpoints (count varies by aircraft; F16C has 9, MiG-29 has 8, some light aircraft have fewer). Per-hardpoint fields:
hld word Hardpoint Loading Data flags (see table below)
offset_x word right/left offset, feet (positive = right)
offset_y word up/down offset, feet
offset_z word fore/aft offset, feet
slew_heading word 1 deg = 182 (e.g. 364 = 2°)
slew_pitch word 1 deg = 182
slew_limit_heading word 1 deg = 182
slew_limit_pitch word 1 deg = 182
default_type ptr default weapon/store filename (e.g. "AIM9M.JT")
weight byte hundreds of pounds (max 255 = 25,500 lbs)
quantity word number of items on this hardpoint
location byte see location codes below
Hardpoint Loading Data (HLD) flags:
| Value | Meaning |
|---|---|
$8 |
Required load only (gun, built-in sensor — always loaded) |
$85 |
External HP, symmetrical load, IR-guided missile |
$465 |
External HP, symmetrical load, active-radar missile, SARH missile, store |
$520 |
Stealth, internal bay, active-radar missile, other missile, store |
$24 |
Stealth, internal bay, symmetrical load, active-radar missile |
$84 |
Stealth, internal bay, symmetrical load, IR-guided missile |
$1301 |
External HP, other missile, fuel tank, disallow air-to-air |
$17e5 |
External HP, symmetrical load, multi-role (bombs + missiles + stores) |
$5e5 |
External HP, symmetrical load, bombs + missiles |
Hardpoint location codes:
| Code | Location |
|---|---|
0 |
Centerline |
1 |
Fuselage |
2 |
Internal gun |
3 |
Internal bay |
4 |
Wing |
5 |
Wingtip |
systemDamage array — 48-byte array immediately after the MTOW field.
Each byte is a threshold controlling how much damage a subsystem can sustain
before failing. Common values: 20/22 (lightly protected), 148/150
(moderately armored), 36 (structural), 6 (critical systems).
Engine Notes¶
BRF type initialisation entry points confirmed from FA.SMS (called during game startup to load each type array into memory):
| VA | Symbol | BRF type loaded |
|---|---|---|
0x4A6EB0 |
SetupOT |
OBJ_TYPE (.OT static objects) |
0x4A7040 |
SetupNT |
NPC_TYPE (.NT vehicles) |
0x4A71C0 |
SetupPT |
PLANE_TYPE (.PT aircraft) |
0x4A7230 |
SetupJT |
PROJ_TYPE (.JT weapons) |
These four are the canonical entry points for tracing how BRF fields map to in-memory struct layouts; PT.md carries the fully byte-counted PLANE_TYPE binary layout.
Round-Trip Notes¶
- Parse → serialize produces byte-identical files for all OT/NT/PT files in
FA_2.LIB;
tests/test_brf.cppasserts raw-byte preservation. - Null pointers are written as
ptr NULL. - Integer field sign interpretation must match the type assignments in the
spec; wrong signedness produces visually wrong values in
infooutput.
Related¶
Formats: the seven member specs — OT, NT, PT, JT, SEE, ECM, GAS.
PT — Aircraft Flight Model (.PT)¶
.PT files are the per-aircraft aerodynamics and avionics records. There are
145+ .PT files in FA_2.LIB, one per aircraft variant. They use
BRF (Brent's Relocatable Format) — plain ASCII text that is parsed
at game startup by SetupPT.
Tools¶
fx¶
fx pt info F16C.PT # field dump: thrust, speed, fuel, stall, ceiling
fx pt unpack F16C.PT -o F16C.pt.txt
fx pt pack F16C.pt.txt -o F16C_mod.PT
See BRF.md for the full field reference (OT base fields + PT
extension fields, G-envelope section, hardpoints, systemDamage array).
File Layout¶
Plain text; BRF syntax. This page documents the in-memory PLANE_TYPE binary struct the text compiles into — the text-format field reference lives in BRF.md.
In-Memory Struct: PLANE_TYPE¶
At game startup _SetupPT (0x4A7220) chains through _SetupNT →
_SetupOT → FUN_004a6b10 to load each .PT file and lock its binary data
into memory. The binary layout is needed for runtime patching, network-sync
analysis, and AI parameter extraction.
Layout derived by direct byte-counting against F16C.PT (type_size = 660 =
0x294). Offsets marked confirmed were read or written directly by
decompiled the game executable code. All others are inferred from packing (BRF fields
written sequentially, no alignment padding).
file_name is not stored in the binary struct — it is the LIB lookup key
only. ot_names is a single ptr; it points to a name-record holding all
name strings.
Total layout: OBJ_TYPE 166 B (0x00–0xA5) + NPC_TYPE 20 B (0xA6–0xB9) + PLANE_TYPE main 258 B (0xBA–0x1BB) + 9 hardpoints × 24 B (0x1BC–0x293) = 660 B ✓
OBJ_TYPE section (0x00–0xA5, 166 B)¶
| Offset | Size | Field | BRF type | Notes |
|---|---|---|---|---|
0x00 |
1 | struct_type | byte | = 5 for PT |
0x01 |
2 | type_size | word | F16C = 660 |
0x03 |
2 | instance_size | word | confirmed — _T_AddObj@12 reads *(short*)(ptr+3) |
0x05 |
4 | ot_names | ptr | single ptr to name record; F16C = ptr |
0x09 |
4 | ot_flags | dword | F16C = $806bf3 (flyable, visible in library) |
0x0D |
2 | obj_class | word | confirmed — _SetupOT tests bits 0xc000/0x2000/0x1000/0x800/0x400/0x200 |
0x0F |
4 | shape | ptr | confirmed — _SetupOT passes iVar4+0x0f to FUN_004a71e0; string compared to "eject_SH" |
0x13 |
4 | shadow_shape | ptr | confirmed — _SetupOT reads *(char**)(iVar4+0x13) for shape-name derivation |
0x17 |
4 | (damage_shape_a) | ptr | filled by _SetupOT ("_a" variant); dword 0 in source BRF |
0x1B |
4 | (damage_shape_b) | ptr | filled by _SetupOT ("_b" variant); dword 0 in source BRF |
0x1F |
2 | Unknown | word | F16C = 0; possibly dst_debris_pos[0] |
0x21 |
2 | Unknown | word | F16C = 0; possibly dst_debris_pos[1] |
0x23 |
2 | Unknown | word | F16C = 30; possibly dst_debris_pos[2] |
0x25 |
4 | (damage_shape_c) | ptr | filled by _SetupOT ("_c" variant); dword 0 in source BRF |
0x29 |
4 | (damage_shape_d) | ptr | filled by _SetupOT ("_d" variant); dword 0 in source BRF |
0x2D |
2 | Unknown | word | F16C = 30; possibly dmg_debris_pos[0] |
0x2F |
2 | Unknown | word | F16C = 0 |
0x31 |
2 | Unknown | word | F16C = 0 |
0x33 |
4 | dmg_type / damage_set | dword | F16C = 0; written _Rand(2)+1 at destruction, selects the {_C,_D} wreck pair when 2 — see shape-selection.md |
0x37 |
4 | year_available | dword | F16C = 1984 (F-16C service entry year) |
0x3B |
2 | max_vis_dist | word | F16C = 148 |
0x3D |
2 | camera_dist | word | F16C = 0 |
0x3F |
2 | laser_targeting_sig | word | F16C = 88 |
0x41 |
2 | ir_signature | word | F16C = 88 |
0x43 |
2 | rcs_signature | word | F16C = 100 |
0x45 |
2 | hit_points | word | F16C = 100 |
0x47 |
2 | dmg_planes | word | F16C = 0 |
0x49 |
2 | dmg_ships | word | confirmed — _T_AddObj@12 reads as DAT_0050ce8e (crash damage threshold); F16C = 92 |
0x4B |
2 | dmg_structs | word | F16C = 255 |
0x4D |
2 | dmg_armor | word | F16C = 255 |
0x4F |
2 | dmg_other | word | F16C = 255 |
0x51 |
2 | Unknown | word | F16C = 255 |
0x53 |
2 | Unknown | word | F16C = 255 |
0x55 |
1 | explosion_type | byte | F16C = 30 |
0x56 |
1 | crater_size | byte | F16C = 0 |
0x57 |
4 | empty_weight | dword | F16C = 14,567 lbs (matches real F-16C) |
0x5B |
2 | Unknown | word | F16C = 199 |
0x5D |
16 | movement info | 8×word | speed/accel params; F16C = 0,16380,14560,−14560,16380,0,0,0 |
0x6D |
16 | movement dwords | 4×dword | F16C = ^0,^0,^300,^60000 |
0x7D |
4 | utilProc | symbol | F16C = _PLANEProc |
0x81 |
4 | loopSound | ptr | |
0x85 |
4 | secondSound | ptr | |
0x89 |
4 | engineOnSound | ptr | |
0x8D |
4 | engineOffSound | ptr | |
0x91 |
1 | (byte) | byte | F16C = 1 |
0x92 |
16 | sound params | 8×word | F16C = 15000,320,160,20,1600,0,20,60 |
0xA2 |
4 | hudName | ptr |
NPC_TYPE section (0xA6–0xB9, 20 B)¶
| Offset | Size | Field | BRF type | Notes |
|---|---|---|---|---|
0xA6 |
4 | ctType | dword | F16C = 0 |
0xAA |
4 | ctName | ptr | |
0xAE |
1 | (byte) | byte | F16C = 12 |
0xAF |
1 | (byte) | byte | F16C = 32 |
0xB0 |
1 | (byte) | byte | F16C = 20 |
0xB1 |
2 | (word) | word | F16C = 32767 |
0xB3 |
2 | (word) | word | F16C = 0 |
0xB5 |
1 | (byte) | byte | F16C = 9 (hardpoint count) |
0xB6 |
4 | hards | ptr | → hardpoints array |
PLANE_TYPE section (0xBA–0x1BB, 258 B)¶
| Offset | Size | Field | BRF type | Notes |
|---|---|---|---|---|
0xBA |
4 | carrier_flags | dword | F16C = $11 (land-based); see BRF.md carrier_flags table |
0xBE |
4 | env | ptr | → G-envelope section in same file |
0xC2 |
2 | neg_g_count | word | F16C = −4 (number of negative-G envelope entries) |
0xC4 |
2 | pos_g_count | word | F16C = 9 |
0xC6 |
2 | max_speed_sl | word | F16C = 1342 mph (sea level) |
0xC8 |
2 | max_speed_36k | word | F16C = 1934 mph (36,000 ft) |
0xCA |
130 | aero block | 65×word | control-authority, stall/spin and landing-gate parameters — every word now code-traced via the _cgt type-record mirror (0x50D268 + off); see § The 65-word aerodynamic block for the full per-word map |
0x14C |
1 | num_engines | byte | F16C = 1 |
0x14D |
2 | Unknown | word | F16C = 0; unlisted in BRF.md |
0x14F |
4 | military_thrust | dword | F16C = 17,687 lbf (≈ F100-PW-229 dry) |
0x153 |
4 | afterburner_thrust | dword | F16C = 32,000 lbf |
0x157 |
2 | throttle_accel | word | F16C = 40 %/sec |
0x159 |
2 | throttle_decel | word | F16C = 60 %/sec |
0x15B |
2 | tv_min_angle | word | F16C = 0 (no thrust vectoring) |
0x15D |
2 | tv_max_angle | word | F16C = 0 |
0x15F |
2 | tv_speed | word | F16C = 0 |
0x161 |
2 | fuel_consumption_mil | word | F16C = 2 |
0x163 |
2 | fuel_consumption_ab | word | F16C = 16 |
0x165 |
4 | fuel_capacity | dword | F16C = 6,972 lbs (matches real F-16C internal fuel) |
0x169 |
2 | aero_drag | word | F16C = 256 (baseline) |
0x16B |
2 | g_drag | word | F16C = 33 |
0x16D |
2 | airbrake_drag | word | F16C = 256 |
0x16F |
2 | wheel_brake_drag | word | F16C = 102 |
0x171 |
2 | flap_drag | word | F16C = 76 |
0x173 |
2 | gear_drag | word | F16C = 23 |
0x175 |
2 | weapons_bay_drag | word | F16C = 0 |
0x177 |
2 | flaps_lift | word | F16C = 51 |
0x179 |
2 | drag_loaded | word | F16C = 30 |
0x17B |
2 | g_drag_loaded | word | F16C = 13 |
0x17D |
2 | gear_pitch | word | F16C = 40 |
0x17F |
2 | max_landing_speed | word | F16C = 40 ft/sec |
0x181 |
2 | max_side_speed | word | F16C = 40 ft/sec |
0x183 |
2 | max_sink_rate | word | F16C = 2560 ft/sec |
0x185 |
2 | max_landing_pitch | word | F16C = 5120 |
0x187 |
45 | systemDamage[] | byte×45 | subsystem hit thresholds; F16C values range 6–150 |
0x1B4 |
2 | misc_per_flight | word | F16C = 10 (maintenance man-hours) |
0x1B6 |
2 | repair_multiplier | word | F16C = 10 |
0x1B8 |
4 | mtow | dword | F16C = 33,000 lbs |
Hardpoints section (0x1BC–0x293, 9 × 24 B)¶
F16C.PT has 9 hardpoints. Each hardpoint is 24 bytes: 8×word + ptr (4B) + byte + word + byte.
| Offset | Size | Field | Notes |
|---|---|---|---|
+0x00 |
2 | hld | loading-data flags |
+0x02 |
2 | offset_x | ft right/left |
+0x04 |
2 | offset_y | ft up/down |
+0x06 |
2 | offset_z | ft fore/aft |
+0x08 |
2 | slew_heading | 1° = 182 |
+0x0A |
2 | slew_pitch | |
+0x0C |
2 | slew_limit_heading | |
+0x0E |
2 | slew_limit_pitch | |
+0x10 |
4 | default_type | ptr to weapon filename |
+0x14 |
1 | weight | hundreds of lbs |
+0x15 |
2 | quantity | |
+0x17 |
1 | location | see BRF.md location codes |
Engine Notes¶
Setup call chain¶
_SetupPT (0x4A7220) -- thin wrapper
└─ _SetupNT (0x4A7200) -- NT-layer init
├─ FUN_004a6b10(param_1) -- MM handle → raw data ptr
└─ _SetupOT (0x4A6EB0) -- OT-layer init: resolves shape ptrs via RMAccess_8
└─ FUN_004a71e0(ptr) -- loads one SH file: *ptr = _RMAccess_8(*ptr, 0x8000)
FUN_004a71c0 (0x4A71C0) -- single-object variant; calls FUN_004a71e0 on shape fields
_SetupOT reads six shape ptr fields in the binary struct and resolves each
via _RMAccess_8 (load from LIB). For flying objects (obj_class & 0xc000 ≠
0) it also derives four damage-state shape names (_a / _b / _c / _d suffix)
from shadow_shape, writes them into the struct, and resolves them.
Entry points¶
| VA | Symbol | Role |
|---|---|---|
0x4A7200 |
_SetupNT |
NT-layer wrapper; calls FUN_004a6b10 + _SetupOT |
0x4A7220 |
_SetupPT |
PT entry point; thin wrapper calling _SetupNT |
0x4A71C0 |
FUN_004a71c0 |
Single-object shape loader; calls FUN_004a71e0 on shape fields |
0x4A6EB0 |
_SetupOT |
OT-layer shape resolver; patches +0x0f/+0x13/+0x17/+0x1b/+0x25/+0x29 |
0x4A71E0 |
FUN_004a71e0 |
Resolves one shape ptr: *ptr = _RMAccess_8(*ptr, 0x8000) |
0x4A6B10 |
FUN_004a6b10 |
MM handle → raw data ptr (*(int*)(param+0xf), with MM lock if bit 1 of +0xe) |
0x4A6B30 |
FUN_004a6b30 |
BRF record lookup + string→binary conversion via _RMType_4 / _RMFind_4 |
The 65-word aerodynamic block (0xCA–0x14B)¶
The 65 words at 0xCA–0x14B are the flight-model tuning table. They are named
here by code trace, not by value-guessing: at startup GetCurObj copies the
active aircraft's PLANE_TYPE record into the _cgt type-record mirror at base
0x50D268 byte-for-byte, so PT offset X is read by the engine as
DAT_[0x50D268 + X] (anchored by exact hits — fuel_consumption_mil at PT
0x161 = DAT_0050d3c9, military_thrust at 0x14F = DAT_0050d3b7,
g_drag at 0x16B = DAT_0050d3d3). Enumerating every reference into
0x50D332–0x50D3B3 (aero block) gives the consumer of each word. Every word
has a consumer — none are engine-unused. Words 0xCA–0xF8 are consumed as two
block-copied limit vectors (see below), which is why the middle words of those
blocks carry no direct xref of their own.
The 12 stall/spin words (0x128–0x13E) confirm and now position the fields
BRF.md already named but could not place; the remaining words are newly named.
F16C values shown; "per-a/c" = varies by aircraft, "const" = identical across
the eight sampled .PT files (F16C/F14/A10/B52/AH64/AV8/C130/F117).
Control-authority limit vectors (0xCA–0xF8, words 0–23). _COBv@0
(0x477EA0) block-copies words 0–11 into the live control buffer 0x547338;
_COBrv@0 (0x477ED0) block-copies words 12–23 into 0x547350 and scales them
down by battle-damage (DAT_0050ce8e) and control-surface loss (DAT_00522547)
— i.e. these are the max control-response limits, degraded as the airframe takes
hits.
| word | off | field | F16C | reader / evidence |
|---|---|---|---|---|
| 0 | 0xCA |
accel_runway (BRF) | −73 | _COBv block[0] |
| 1 | 0xCC |
decel_runway (BRF) | 0 | _COBv block |
| 2 | 0xCE |
roll_speed_min (BRF) | 73 | _COBv block |
| 3 | 0xD0 |
roll_speed_max (BRF) | 73 | _COBv block |
| 4 | 0xD2 |
pull_rate (BRF) | −146 | _COBv block |
| 5 | 0xD4 |
neg_g_limit (BRF) | 146 | _COBv block |
| 6–11 | 0xD6–0xE0 |
ground/low-speed handling limits (cont.) | 7,7,−146,146,73,73 · const | _COBv block words 6–11; ±-paired rate limits, roles inferential |
| 12–15 | 0xE2–0xE8 |
rotational-authority axis A {neg,pos,rate,accel} | −270,270,362,724 · per-a/c | _COBrv block; fighters ±270, bombers ±30 |
| 16–19 | 0xEA–0xF0 |
rotational-authority axis B {neg,pos,rate,accel} | 0,0,9,9 · per-a/c | _COBrv block |
| 20–23 | 0xF2–0xF8 |
rotational-authority axis C {neg,pos,rate,accel} | −45,45,90,90 · per-a/c | _COBrv block |
Stick-to-motion gains and turn coordination (0xFA–0x116, words 24–46).
The three primary control axes are proven by their _StickInput_28 calls in
_FMFlight@0 (0x47B020): each is driven by a specific pilot input, so the
axis identity is definitive.
| word | off | field | F16C | reader / evidence |
|---|---|---|---|---|
| 24 | 0xFA |
pitch-input → pitch-rate gain (×/9) |
20 · const | _FMFlight DAT_0050d362 * pitchIn / 9 |
| 25 | 0xFC |
forward-motion scale (×256) |
70 · const | _MovePlane@0 (0x476AE0) |
| 26 | 0xFE |
vertical/reverse-motion scale (×−256) |
15 · const | _MovePlane@0 |
| 27 | 0x100 |
AI visual-detection / contrail interval factor | 115 · per-a/c | FUN_0047759b (time-of-day-scaled spotting timer) |
| 28–31 | 0x102–0x108 |
rudder turn-coordination axis {neg,pos,rate,accel} | −4,4,4,9 | _FMFlight _StickInput(&DAT_0050d007, …, _rudder), speed/G-scaled |
| 32 | 0x10A |
adverse-yaw (roll→yaw) coupling | 10 · const | _FMFlight DAT_0050d02b = DAT_0050d372 * rollRate |
| 33 | 0x10C |
sideslip / yaw drag coefficient | 128 · const | FUN_0047a970 (× sideslip); also PROJGuideLoft |
| 34 | 0x10E |
yaw/heading response rate | 5 · per-a/c | _FMFlight DAT_0050d00f += d376·resp; PROJGuideLoft |
| 35–38 | 0x110–0x116 |
roll axis {neg,pos,rate,accel} | −90,90,362,724 | _FMFlight _StickInput(&DAT_0050d033, …, _stickX) |
| 39–42 | 0x118–0x11E |
pitch axis {neg,pos,rate,accel} | −90,90,362,724 | _FMFlight _StickInput(&DAT_0050d037, …, _stickY) |
| 43–46 | 0x120–0x126 |
yaw axis {neg,pos,rate,accel} | −90,90,362,724 | _FMFlight _StickInput(&DAT_0050d03b, …, _rudder) |
Stall / spin model (0x128–0x13E, words 47–58). These are the BRF.md
stall/spin fields, now pinned to their binary offsets. Names are BRF.md's;
offsets and readers are the new evidence.
| word | off | field (BRF name) | F16C | reader / evidence |
|---|---|---|---|---|
| 47 | 0x128 |
stall_warn_delay | 512 · per-a/c | ?CheckForEvents2 fires the stall-warning event; _FMFlight |
| 48 | 0x12A |
stall_duration | 512 · per-a/c | ?CheckForEvents2; _FMFlight d08d < DAT_0050d392 |
| 49 | 0x12C |
stall_severity | 256 | _FMFlight (DAT_0050d394) |
| 50 | 0x12E |
stall_pitch_down (deg/sec) | 30 · const | _FMFlight _MatchF24(&pitch, −0x5A00, …) drives nose to −90°; ?PROJEventProc |
| 51 | 0x130 |
spin_entry_ease (0 = harder) | 0 · per-class | FUN_0047ccb0 sets spin state d08c=3; fighters 0, bombers 1, helo 2 |
| 52 | 0x132 |
spin_exit_ease (neg = harder) | −2 · per-class | FUN_0047cdb0 recovery selector (−2 / −1); _FMFlight |
| 53–54 | 0x134–0x136 |
spin_yaw_low / _high (deg/sec) | 120,180 · const | _FMFlight FUN_0047ce70(lo, hi, spinPhase) |
| 55–56 | 0x138–0x13A |
spin_aoa_low / _high (deg) | 30,70 · const | _FMFlight FUN_0047ce70 |
| 57–58 | 0x13C–0x13E |
spin_bank_low / _high (deg) | 15,5 · const | _FMFlight FUN_0047ce70 |
Gear pitch and landing gate (0x140–0x14A, words 59–64). Word 59 is the
gear-down nose-up trim; words 60–64 are the universal landing-quality gate
(identical in every .PT) that _CheckLandingParms@0 (0x477140) scores —
each threshold overshoot returns 5 (warning) or 6 (bad landing).
| word | off | field | F16C | reader / evidence |
|---|---|---|---|---|
| 59 | 0x140 |
gear-down pitch-trim authority (× 0xB6) |
0 · per-a/c | _FMUpdateGearPitch@0 (0x4514C0); AV8 = 3 |
| 60 | 0x142 |
landing gate: reference speed (fail if exceeded by >0x32) |
330 · const | _CheckLandingParms; ?HUDDrawSpeed reads it as the HUD approach ref |
| 61 | 0x144 |
landing gate: max sink rate (fail if >10 over) |
51 · const | _CheckLandingParms |
| 62 | 0x146 |
landing gate: pitch/AoA limit | 95 · const | _CheckLandingParms; ?HUDDrawAlt |
| 63 | 0x148 |
landing gate: attitude limit 1 (fail if >0x14 over) |
25 · const | _CheckLandingParms |
| 64 | 0x14A |
landing gate: attitude limit 2 (fail if >0x14 over) |
10 · const | _CheckLandingParms |
Confidence: the three primary axes (roll/pitch/yaw, words 35–46) and the
stall/spin/landing blocks (words 47–64) are reader-proven. The two block-copied
limit vectors (words 0–23) have a proven consumer (_COBv/_COBrv) and proven
damage-scaling role, but the per-word breakdown inside words 6–23 is inferred
from the ±-paired numeric structure and is annotated as such. Method is
reproducible with scripts/ghidra/run_ghidra.sh over the 0x50D332–0x50D3B3
range.
Open Questions¶
1. Debris-position candidates¶
The three 2-byte unknowns at 0x1F/0x21/0x23 and three at 0x2D/0x2F/0x31 are
plausible candidates for dst_debris_pos[3] and dmg_debris_pos[3] (i16[3]
each per BRF.md), but the 0x23/0x2D values of 30 don't obviously map to
debris-offset coordinates — needs a second PT file comparison to confirm.
Status: open — re-static (#54)
Related¶
Formats: BRF — full BRF field reference for all PT fields,
hardpoints, G-envelope; OT — OT base fields that PT inherits;
JT — weapon/projectile types loaded via SetupJT (0x4A7230);
SH — 3D model format referenced by shape / shadow_shape ptr
fields.
JT — Weapon Ordnance Definition (.JT)¶
FA_2.LIB contains 135 .JT files. Each defines one weapon/ordnance type
(missiles, bombs, guns, rockets). BRF plain text (NOT a Win32 PE
DLL); decompressed sizes vary (e.g. AIM9M.JT = 2775 bytes). Loaded at runtime
by the FA object system.
Tools¶
fx¶
fx jt info <file.JT> # human-readable field dump
fx jt unpack <file.JT> [-o out.txt] # editable text
fx jt pack <in.txt> -o out.JT # write back (byte-identical)
File Layout¶
Plain text; BRF syntax (see BRF.md). Two-section structure: OBJ_TYPE (physical object base) followed by PROJ_TYPE (projectile/seeker).
Section 1: OBJ_TYPE¶
[brent's_relocatable_format]
;---------------- START OF OBJ_TYPE ----------------
;---------------- general info ----------------
byte 7 ; object category (7 = projectile)
word 315 ; hitpoints
word 52 ; object subtype ID
ptr ot_names
dword $6 ; capability flags
word $80 ; flags2
ptr shape ; 3D model reference
dword 0 ; (12 reserved dword/word fields)
...
dword 1983 ; year introduced
word 59 ; (additional physics params)
word 0
word 100 ... word 100 ; (performance percentages)
byte 30 ; (more params)
byte 3
dword 190
word 0
;---------------- movement info ----------------
word 0 ... (speed/accel/maneuver params)
symbol _PROJProc ; utilProc (projectile physics callback)
;---------------- sound info ----------------
ptr loopSound
dword 0 ... (sound attenuation params)
(word values for sound range/falloff)
Section 2: PROJ_TYPE¶
;---------------- START OF PROJ_TYPE ----------------
dword $1204f ; warhead/capability flags
word 1 ; warhead count
byte 10 ; seeker type
ptr si_names
word 0 ; seeker flags
byte $0 ; sub-type
byte 2 ; seeker mode (0=unguided, 1=radar, 2=IR, etc.)
byte $0
byte 0 ... (8 bytes target-selection params)
; Seeker lobe 1 (primary):
word 8190 ; azimuth half-angle
word 8190 ; elevation half-angle
dword ^0 ; min range
dword ^50000 ; max range
dword $80000000 ; min heading error
dword $7fffffff ; max heading error
; Seeker lobe 2 (secondary):
word 8190
word 8190
dword ^4000
dword ^24000
dword $80000000
dword $7fffffff
; Warhead/agility params (many byte values):
byte 50 ... byte 85
; Fire params:
word 546 word 1 word 16
byte 100 ... byte 34
ptr fireSound
word 6000 word 0 word 750 word 35
Labels Section¶
:ot_names
string "AIM-9M" ; short display name
string "AIM-9M Sidewinder" ; long display name
string "AIM9M.JT" ; filename (self-reference)
:shape
string "sidem.SH" ; 3D shape file
:loopSound
string "&missile.11k" ; looping motor sound
:si_names
string "AIM-9M" ; (duplicate of ot_names)
string "AIM-9M Sidewinder"
string "AIM9M.JT"
:fireSound
string "<miss.11k" ; launch sound
end
Notes:
- Gun rounds (e.g. GAU8.JT) use seeker mode byte=0 (unguided), wide cone angles (word 16380), and short ranges.
- Missiles use narrow cones and long ranges.
- The
~prefix (e.g.~BLDR.JT,~MOOSE.JT,~MOTHB.JT,~VOMIT.JT) indicates theater-specific or campaign-specific weapon variants.
Warhead capability flags (dword)¶
Confirmed flags from cross-referencing weapon types:
| Weapon | dword value |
Role |
|---|---|---|
| AIM-9M/X, AIM-120 | $1204f |
Air-to-air missile |
| AGM-65G, AGM-84A | $2a06f |
Air-to-ground missile |
| MK-82 | $22012 |
Unguided bomb (AG) |
| 20mm cannon round | $0348c4 |
Gun (AA + AG) |
The flags dword is loaded as-is into the runtime missile entity at
missile+0xa6. At launch, the engine OR-assigns runtime state bits into the
same field:
- $1204f (AIM-9M, AIM-120): bit 16 set → starts in search mode, transitions
to 0x20000 on acquisition
- $2a06f (AGM-65G): bit 17 set → starts in track mode (fire-and-forget,
locked before launch)
Confirmed bit roles from _PROJLock@24 (0x004c2f20), _PROJHitChance@28
(0x004c3380), @HARDFindJammer@4, _DAMAGEDoHit@12 (0x0040f970),
_PROJMoveProc (0x4c11b0), and _PROJAdd_40:
| Bit | Hex | Meaning | Evidence |
|---|---|---|---|
| 0 | 0x000001 | Guided missile / rocket marker | _DAMAGEDoHit@12: bit 7=0 AND bit 0=1 → play missile-hit sound; AIM-9M 0x4f has bit 0=1 ✓ |
| 1 | 0x000002 | Precision/CEP control: if set, 75% chance no scatter at launch; if clear, scatter always applied | _PROJAdd_40: (flags & 2) == 0 \|\| !_Percent_4(75%) → randomize bomb impact point; missiles have bit set (low CEP), unguided bombs clear (always scatter) |
| 4 | 0x000010 | Pk modifier gate | _PROJLock@24 range-based Pk calc |
| 5 | 0x000020 | Guidance algorithm selector: 0 → FUN_004c1630 (standard pursuit), 1 → FUN_004c1660 (alternate) |
_PROJMoveProc: if ((flags & 0x20) == 0) FUN_004c1630() else FUN_004c1660() |
| 6 | 0x000040 | Engine/propulsion tracking: when set, _PROJEngineState_0() runs each tick and adjusts thrust speed per phase (boost/cruise/coast) |
_PROJMoveProc: if ((flags & 0x40) != 0) { engine_state → speed update } |
| 7 | 0x000080 | Cannon / gun-round marker | _DAMAGEDoHit@12: bit 7=1 → play gun-hit sound; 20mm 0xc4 has bit 7=1 ✓ |
| 9 | 0x000200 | Dual-lobe seeker lock processing | gates search/track lobe dispatch in _PROJLock@24 |
| 10 | 0x000400 | Active-radar / special guidance mode | gates PushCurObj + HARDBestSeeker in _PROJLock@24 |
| 12 | 0x001000 | Seeker re-acquisition far-range bias: when set, initial CF70 (re-acq distance) set at 75–100% of launch distance (90% roll) | _PROJAdd_40: (flags & 0x1000) != 0 && _Percent_4(90%) → CF70 = rand[75%, 100%] × launch_dist |
| 13 | 0x002000 | Seeker re-acquisition near-range bias: when set (bit 12 check failed), initial CF70 set at 0–25% of launch distance | _PROJAdd_40: (flags & 0x2000) != 0 && _Percent_4(90%) → CF70 = rand[0%, 25%] × launch_dist |
| 16 | 0x010000 | Air-to-air capable / search-mode init | AA missiles and 20mm; @HARDFindJammer@4 |
| 17 | 0x020000 | Air-to-ground capable / track-mode init | AG missiles, bombs, and 20mm |
| 21 | 0x200000 | Lock-count reduction modifier | _PROJHitChance@28 |
Hit-sound dispatch (_DAMAGEDoHit@12 projectile-type case):
if ((warhead_flags & 0x80) != 0) → gun-hit sound (20mm: 0xc4 has bit 7)
else if ((warhead_flags & 0x01) != 0) → missile-hit sound (AIM-9M: 0x4f has bit 0)
else → bomb-hit sound (MK-82: 0x12 has neither)
Bits 2–3 of the lower byte are present in consistent category-level patterns
across all 135 .JT files but no function in the full the game executable decompile or any
analyzed overlay DLL tests them individually. Remaining inferred assignments:
| Bit | Hex | Inferred pattern | Notes |
|---|---|---|---|
| 2 | 0x000004 | Set for missiles, rockets, and gun rounds; clear for gravity bombs | Possible "active ordnance" marker |
| 3 | 0x000008 | Set for guided missiles and gun rounds; clear for most unguided weapons | Possible "terminal-guidance capable" marker |
Bits 1, 5, and 6 were previously listed as inferred — all three are now
confirmed from _PROJMoveProc / _PROJAdd_40 decompile and moved to the
confirmed table above.
Seeker mode byte — confirmed¶
Matches the SEE seeker type byte:
byte |
Guidance | Confirmed weapon |
|---|---|---|
| 0 | Unguided | MK-82.JT, 20MM_4.JT |
| 1 | Laser-guided | (see *L.SEE laser files) |
| 2 | IR / EO homing | AIM9M.JT, AIM9X.JT, AGM65G.JT |
| 3 | Radar (semi-active / active) | AIM120.JT, AGM84A.JT |
Range unit in PROJ_TYPE¶
The ^ range values in PROJ_TYPE seeker lobes use the same 1-foot unit as SEE
files. Confirmed data points:
- AIM9X lobe 1 max ^50000 = 8.2 nm; AIM-9X ~8 nm ✓
- AIM120 lobe 1 max ^144000 = 23.7 nm; AIM-120A ~25 nm ✓
- AGM84A lobe 1 max ^360000 = 59.3 nm; Harpoon ~60 nm ✓
Agility / hit-probability bytes¶
The PROJ_TYPE section base is at missile+0xa6 in the runtime entity
(confirmed entity base at cjt in the FA scratchpad; PROJ_TYPE base at
DAT_0050d30e). Confirmed reads from _PROJHitChance@28 (0x004c3380),
FUN_004c3960 (proximity fuze check), _PROJMoveProc (0x4c11b0),
_PROJEngineState_0 (0x4c1170), FUN_004c17a0, and FUN_004c1660:
| PROJ_TYPE offset | Global addr | Role (confirmed) | Source |
|---|---|---|---|
+0x4F |
DAT_0050d35d |
Proximity fuze range (byte) — fuze triggers when target_arm_size ≥ this; below 50% = miss |
FUN_004c3960 |
+0x55 |
DAT_0050d363 |
Min maneuver rate (short) — lower clamp in PROJSpeed velocity computation |
PROJSpeed |
+0x57 |
DAT_0050d365 |
Coast/glide speed — target speed during engine state 2 (coast phase, motor off) | _PROJMoveProc + _PROJEngineState_0 |
+0x59 |
DAT_0050d367 |
Boost phase end (ticks after launch) — transition from state 0 (boost) to state 1 (cruise) | _PROJEngineState_0 |
+0x5B |
DAT_0050d369 |
Motor burnout threshold (ticks after launch) — transition from state 1 (cruise) to state 2 (coast) | _PROJEngineState_0 |
+0x5D |
DAT_0050d36b |
Max flight time / missile TTL (ticks) — missile self-destructs when elapsed ≥ this | _PROJMoveProc |
+0x65 |
DAT_0050d373 |
Alt-guidance inner range threshold (byte) — if dist>>16 ≥ PROJ_TYPE+0x67 but < this, use inner speed | FUN_004c1660 (warhead bit 5=1) |
+0x66 |
DAT_0050d374 |
Alt-guidance inner range speed (byte) — _CreateMove_52 speed at close range |
FUN_004c1660 |
+0x67 |
DAT_0050d375 |
Alt-guidance outer range threshold (byte) — min distance for alt-guidance mode activation | FUN_004c1660 |
+0x68 |
DAT_0050d376 |
Alt-guidance outer range speed (byte) — _CreateMove_52 speed at far range |
FUN_004c1660 |
+0x69 |
DAT_0050d377 |
Seeker search angular spread half-width (angle units) — used in _Rand_4 for scan jitter |
FUN_004c17a0 |
+0x6B |
DAT_0050d379 |
Search reacquisition interval (ticks) — added to _currentTime to set next scan-angle update |
FUN_004c17a0 |
+0x6D |
DAT_0050d37b |
Active search window duration (ticks; 0 = disabled) — while elapsed < this, missile scans via random angles | _PROJMoveProc |
+0x6F |
DAT_0050d37d |
Maneuver rate percentage (byte) — (this × input_rate) / 100; upper bound in PROJSpeed |
PROJSpeed |
+0x70 |
DAT_0050d37e |
Smoke trail flags (bits 0–6 = trail count; bit 7 = continuous smoke flag) | _PROJMoveProc |
+0x71 |
DAT_0050d37f |
Smoke trail param 1 (passed to _GRAPHICAddSmokeAdder_40) |
_PROJMoveProc |
+0x72 |
DAT_0050d380 |
Smoke trail param 2 | _PROJMoveProc |
+0x73 |
DAT_0050d381 |
Smoke trail param 3 | _PROJMoveProc |
+0x74 |
DAT_0050d382 |
Smoke trail param 4 | _PROJMoveProc |
+0x75 |
DAT_0050d383 |
Pk at 0–25% of engagement range (byte) — Pk quartile table entry 0, read by FUN_004c3890 |
_PROJHitChance@28 |
+0x76 |
DAT_0050d384 |
Pk at 25–50% of engagement range (byte) — Pk quartile table entry 1 | _PROJHitChance@28 |
+0x77 |
DAT_0050d385 |
Pk at 50–75% of engagement range (byte) — Pk quartile table entry 2 | _PROJHitChance@28 |
+0x78 |
DAT_0050d386 |
Pk at 75–100% of engagement range (byte) — Pk quartile table entry 3 | _PROJHitChance@28 |
+0x79 |
DAT_0050d387 |
Approach angle tolerance byte | _PROJHitChance@28 |
+0x7A |
DAT_0050d388 |
Approach angle weight byte | _PROJHitChance@28 |
+0x7B |
DAT_0050d389 |
Angular error weight byte (left-shifted 3 for angle table lookup) | _PROJHitChance@28 |
+0x7C |
DAT_0050d38a |
Counter-maneuver jam Pk reduction byte — lowers Pk by this % | _PROJHitChance@28 |
+0x7D |
DAT_0050d38b |
Altitude/range modifier weight byte | _PROJHitChance@28 |
+0x7E |
DAT_0050d38c |
Range gap modifier byte | _PROJHitChance@28 |
+0x7F |
DAT_0050d38d |
Pk adjustment byte (applied as signed % of base Pk) | _PROJHitChance@28 |
+0x80 |
DAT_0050d38e |
Maneuver agility Pk bonus (ushort) | _PROJHitChance@28 |
File Inventory¶
| Category | Examples |
|---|---|
| Missiles (IR/radar) | AIM-9M/X, AA-10/11/12, AM-39, R-530/550, MICA, HQ-61 |
| Bombs | MK-82/84, GBU-10/28/29/30, FAB-250/500, PAVEWAY, CBU-87/89 |
| Guns | GAU8, GAU12, GSH-23/30, M61, DEFA, ADEN, BK27 |
| Rockets | LAU-10/61, B8, B13 |
| SAM rounds | SA-2/3/6/7/9/13-16/19 |
| Naval weapons | SSN-9, SEA_SPAR, ASROC |
| Campaign variants | ~BLDR, ~MOOSE, ~MOTHB, ~VOMIT |
All 135 live in FA_2.LIB.
Engine Notes¶
Engine state machine (_PROJEngineState_0 @ 0x4c1170, called from
_PROJMoveProc when warhead bit 6 is set):
// Returns: 0 = boost, 1 = cruise (motor on), 2 = coast (motor off)
if (elapsed < PROJ_TYPE[+0x59]) return 0; // boost phase
if (elapsed < PROJ_TYPE[+0x5B]) return 1; // cruise phase
return 2; // coast phase
// elapsed = _currentT − launch_time (ticks)
// PROJ_TYPE[+0x57] = coast speed target (used when state == 2)
// PROJ_TYPE[+0x5D] = TTL: missile removed when elapsed ≥ this
Seeker scan mode (enabled when PROJ_TYPE[+0x6D] != 0 and target entity
type == aircraft): the missile enters a search scan phase while elapsed flight
time < PROJ_TYPE[+0x6D]. Each PROJ_TYPE[+0x6B] ticks, FUN_004c17a0
generates a new random scan angle within ±PROJ_TYPE[+0x69] and applies it
via _ObjPlusAngleParm_8. When elapsed ≥ +0x6D, scan angles are zeroed
(passive flight until seeker acquires or TTL).
FUN_004c3890 (called by _PROJHitChance@28) is the Pk range-interpolation
function. It reads the engagement range reference at PROJ_TYPE+0x31
(secondary lobe max range), divides it into quartiles, and linearly
interpolates between the four Pk bytes at +0x75–0x78.
PROJSpeed is the velocity-clamp function: applies PROJ_TYPE+0x6F % to an
input angular rate, clamps between PROJ_TYPE+0x55 minimum and OBJ_TYPE
speed-limit fields at entity+0x67/+0x6b.
_PROJProc dispatch (confirmed via dumpAtForced):
// _PROJProc @ 0x4c1f50
undefined1 * _PROJProc(undefined1 param_1) {
switch(param_1) {
case 1: return &LAB_004c1f90; // init/startup handler
case 2: return &_PROJMoveProc; // movement / physics update (0x4c11b0)
case 3: return &_PROJEventProc; // event handler
case 4: return &_PROJDamageProc; // damage processing
default: return NULL;
}
}
Alternate guidance algorithm (FUN_004c1660 @ 0x4c1660, selected when
warhead bit 5=1): reads the target entity position, computes distance, then
selects guidance speed from a two-tier distance table (+0x65/+0x66 inner
range, +0x67/+0x68 outer range) before calling _CreateMove_52 in positional
mode. Falls through to FUN_004c1630 (standard pursuit mode) when distance is
below the outer range threshold.
Physics scratchpad (0x50CE80–0x50D267): a separate fixed-address block
preceding the entity struct (cjt base 0x50D268) holds per-tick missile
motion state. Key globals confirmed from _PROJMoveProc full decompile:
| Global | Role |
|---|---|
DAT_0050ce91 |
Missile world position (passed as &pos to _DistToObj@8, _GRAPHICAddExp) |
DAT_0050ce95 |
Remaining range accumulator — decremented by motor deceleration each tick |
DAT_0050cf5e |
Runtime state flags (bit 1 = acquired, bit 2 = fired, bit 3 = skip guidance, bit 4 = alt-guidance, bit 8 = seeker override) |
DAT_0050cf62 |
Warhead sub-parameter (passed to _PROJLock@24, _PROJSendCollateralDamages) |
DAT_0050cf64 |
Target entity slot ID (0 = no target; indexes _objPtrs[]) |
DAT_0050cf68 |
Phase timing reference — _currentT − this = elapsed ticks since phase start |
DAT_0050cf70 |
Max seeker engagement distance (compared to iVar6 = _DistToObj result) |
ram0x0050cf80 |
Motor deceleration accumulator (capped at 0x5000, subtracted from ce95 when bit 6 set) |
ram0x0050cf82 |
Seeker scan azimuth angle (passed to @ObjPlusAngleParm@8) |
DAT_0050cf84 |
Seeker scan elevation angle (passed to @ObjPlusAngleParm@8) |
DAT_0050cf86 |
Guidance update timer — next scheduled scan angle update (compared to _currentT) |
Sub-functions checked (all are one-liners, no gap field reads):
| Function | Address | Body |
|---|---|---|
FUN_004c1630 |
0x4c1630 |
_CreateMove@52(&DAT_0050ceb8, 0, 4, target_slot, 4, 0, 1, 0, 1, 0, 8, 0x10, 0x7fff) — standard pursuit, fixed params |
FUN_004c17f0 |
0x4c17f0 |
Seeker acquisition eligibility: checks atmosphere layer bit 3 + sun/heading angle constraints; no PROJ_TYPE reads |
FUN_004c1720 |
0x4c1720 |
_CreateMove@52(&DAT_0050ceb8, 0, 1, heading, 1, pitch, 1, roll, 1, 0, 8, 0x10, 0x7fff) — lock handler, uses missile own angles |
FUN_004c1760 |
0x4c1760 |
_CreateMove@52(&DAT_0050ceb8, 0, 1, _sunAngle, 1, az, 1, roll, 1, 0, 8, 0x10, 0x7fff) — acquisition, sun-angle homing |
Confirmed entity offsets 0xF0–0x16F (from DumpPROJDispatch run): the
entity-relative offsets around the PROJ_TYPE base (missile+0xa6) were
confirmed by scanning entity+0xF0–0x114:
| Runtime offset | Role (confirmed) |
|---|---|
entity+0xF0 |
Target ptr / target ID |
entity+0xF4 |
Reaction param 0 |
entity+0xF6 |
Reaction param 1 |
entity+0xF8 |
Reaction param 2 |
entity+0xFA |
Mode byte |
entity+0x100 |
Primary per-frame state byte — most-polled field in the entity update loop |
entity+0x101 |
Timeout / tick timer |
entity+0x10C |
Campaign context reference |
entity+0x114 |
Init handle |
entity+0x16F |
HUD state flags (also DAT_0050cfef = player entity + 0x16F per HUD.md) |
Open Questions¶
1. PROJ_TYPE bytes with zero the game executable xrefs¶
Full exhaustive search (Ghidra GUI): all four PROJ proc types decompiled
(_PROJMoveProc, _PROJEventProc, _PROJDamageProc, guidance proc at
LAB_004c1f90), all reachable sub-functions checked, and Ghidra xref on each
gap global address — all returned zero references:
| PROJ_TYPE offset | Global addr | Status |
|---|---|---|
+0x50 |
DAT_0050d35e |
Zero xrefs in the game executable |
+0x51 |
DAT_0050d35f |
Zero xrefs in the game executable |
+0x52 |
DAT_0050d360 |
Zero xrefs in the game executable |
+0x53 |
DAT_0050d361 |
Zero xrefs in the game executable |
+0x54 |
DAT_0050d362 |
Not in _PROJMoveProc |
+0x56 |
DAT_0050d364 |
Not in _PROJMoveProc — may be high byte of +0x55 (short) |
+0x58 |
DAT_0050d366 |
Not in _PROJMoveProc — may be high byte of +0x57 (short) |
+0x5A |
DAT_0050d368 |
Not in _PROJMoveProc — may be high byte of +0x59 (short) |
+0x5C |
DAT_0050d36a |
Not in _PROJMoveProc — may be high byte of +0x5B (short) |
+0x5E–+0x64 |
DAT_0050d36c–DAT_0050d372 |
Not in _PROJMoveProc — check FUN_004c1630 |
The scattered gap bytes +0x56/+0x58/+0x5A/+0x5C are the high bytes of
the confirmed short fields immediately preceding them (+0x55/+0x57/
+0x59/+0x5B) — not independent fields. The block +0x50–+0x54 and
isolated range +0x5E–+0x64 have zero xrefs in all of the game executable — no missile
code, no BRF code, no generic entity loop touches them. These bytes are either
dead/unused fields from an earlier engine version, or are read exclusively by
overlay DLLs. Closing this requires scanning the overlay DLL corpus for
readers.
Status: open — re-static (#54)
Related¶
Formats: BRF — family grammar; SH — 3D shapes; SEE — seeker definitions referenced in PROJ_TYPE; NT — NPC vehicles that carry weapons; OT — static objects.
OT — Static Object Definition (.OT)¶
FA_2.LIB contains 170 .OT files. Each defines one static (non-moving)
scenery or target object — buildings, bridges, trees, flags, runways, radar
dishes, etc. BRF plain text (NOT a Win32 PE DLL); decompressed sizes
vary (e.g. BLDG1.OT = 1256 bytes).
Tools¶
fx¶
fx ot info <file.OT> # human-readable field dump
fx ot unpack <file.OT> [-o out.txt] # editable text
fx ot pack <in.txt> -o out.OT # write back (byte-identical)
File Layout¶
Plain text; BRF syntax (see BRF.md for the token grammar). Single-section structure (OBJ_TYPE only — no NPC_TYPE or PROJ_TYPE).
OBJ_TYPE¶
[brent's_relocatable_format]
;---------------- START OF OBJ_TYPE ----------------
;---------------- general info ----------------
byte 1 ; object category (1 = static object)
word 166 ; hitpoints (durability)
word 0 ; object subtype ID
ptr ot_names
dword $521 ; capability flags
word $100 ; flags2
ptr shape ; 3D model reference
dword 0 ... (reserved fields, same layout as JT/NT)
dword 1956 ; year placed / era introduced
word 195 ; (param)
word 0
word 100 ... word 100 ; (performance/damage-resistance params)
byte 35 ; (armor/hardness)
byte 15
dword 0
word 0
;---------------- movement info ----------------
word 0 ... (all zeros — static objects do not move)
symbol _OBJProc ; static object callback
;---------------- sound info ----------------
dword 0 ... (all zeros — most static objects have no ambient sound)
Labels Section¶
:ot_names
string "Factory 3"
string "Factory 3"
string "BLDG1.OT"
:shape
string "bld1.SH"
end
Notes:
- Static objects have no
loopSoundptr, nosecondSound, and no hardpoints. - The movement info block is all zeros.
_OBJProchandles collision detection, destruction events, and visibility only.- The
~prefix has two distinct uses: (1) destruction state replacement —~BNK5.OTis "Damaged Shelter 5" with a destroyed model (dbk5.SH), zeroeddmg_armor, and bit 5 ofot_flagscleared; placed by the engine at the same world position after the base object is destroyed. (2) campaign/theater variant —~COLTWR.OTis an enhanced version of COLTWR with an added loop sound and bit 22 set, used in a specific campaign. Check the~prefix section in architecture.md for the full list.
obj_class word — confirmed values¶
| Value | Class | Representative files |
|---|---|---|
$40 |
Scenery / terrain feature | TREE1-2, ROAD, LOT, CITY1-3, CRATER, CROPA/B, FLAG, RES1-2, HOUS*, HOOCH, MOOSEA/B |
$100 |
Ground structure | BLDG1-3, BNK1-9, STRIP1-7, HANGR, SILO, TOWER, COMM, BUNKER, SHELT |
$40 covers both pure scenery (TREE, ROAD) and targetable-but-civilian
structures (CITY blocks, urban terrain). $100 covers all military and
airfield structures. Naval and vehicle classes appear in NT files only
($2000 = ship, $1000 = SAM, etc.).
ot_flags dword — observed bit map¶
Full survey of all 170 OT files. Bit positions confirmed by cross-category comparison.
| Bit | Mask | Label | Status | Notes |
|---|---|---|---|---|
| 0 | $1 |
targetable | confirmed | Absent on TREE, ROAD, LOT, CRATER, DEST, REACTB (pure scenery / already-destroyed states) |
| 5 | $20 |
armed / valid combat target | confirmed — FUN_0043df7b |
Hard gate in targeting acquisition: entity with bit 5 clear is immediately rejected as a valid target. Absent on civilian variants (BLDG2 $501) and ~ destroyed-state replacements. |
| 8 | $100 |
has collideable oriented geometry | confirmed — FUN_0042c9b0 |
When set with a non-zero heading, uses angle-based hit detection (calls Rotate2). Present on bridges, most structures, rocks; absent on flat terrain tiles (ROAD, LOT, TREE). |
| 10 | $400 |
civilian or dual-use infrastructure | confirmed — _Reaction_12 (0x464040), _MaskEvents_4 (0x463ea0) |
Set on BLDG, HOUS, urban blocks, airport buildings (APTLA/B/C), NUCOB, REACTR; also on SAM infrastructure sites (SA3SITE, HAWKSITE) — not exclusively civilian. Drives the event-system handlers; also toggles bay-door actuator. |
| 11 | $800 |
animated / non-static scenery | confirmed — FUN_0042c9b0 |
OBJ_TYPE+9 & 0x800 sets a ground-mobile state flag in the targeting/collision resolver. Set on FLAG, CROP, CITY1-3, BR*END (bridge endpoints), SUPPLY, PRDR1/2 (rotating radar dish), MOOSEA/B, MICRO/MICROM (comm dish), CRANE, COMM, BRD1. |
| 15 | $8000 |
airfield surface | confirmed — FUN_00425196 |
When set, forces targeting category to 4 (flight deck special class), bypassing normal flags2-based lookup. Also tested combined with bit 22 (& 0x408000) in targeting eligibility. Exclusive to STRIP1-7 and DTSTRP. |
| 17 | $20000 |
military / hardened structure | confirmed | Set on BNK, HANGR, COMM, NUCCT, PRDR, RELAY, TOWER, COLTWR, CTWR, and other military targets. |
| 20 | $100000 |
damaged runway surface | inferred | DTSTRP only; contrasts with bit 21 on intact STRIP*. |
| 21 | $200000 |
intact runway surface | inferred | STRIP1-7; absent on DTSTRP (damaged variant). |
| 22 | $400000 |
prominent/large variant | confirmed — FUN_0042c9b0 |
When set, collision geometry uses hit-point count of 16 (0x10); when clear, uses full default geometry. Also tested combined with bit 15 (& 0x408000) for targeting eligibility. Set on SHELT, BUNKER, HANGRB, CTOWR, FUEL, OILW, DOCK1, COMM, REACTR, CTYBKA/B, APTLA, ~COLTWR. |
Bits 9, 12–14, 16, 18–19, 23+ not observed in any OT file in FA_2.LIB.
Example flag combinations:
| Object type | ot_flags |
Bits active |
|---|---|---|
| TREE1, ROAD | $0 |
none |
| SILO | $21 |
0, 5 |
| ROCKA | $101 |
0, 8 |
| BLDG2 (Warehouse) | $501 |
0, 8, 10 |
| BLDG1 (Factory) | $521 |
0, 5, 8, 10 |
| SA3SITE, HAWKSITE | $401 |
0, 10 |
| SUPPLY | $820 |
5, 11 |
| FLAG* | $800 |
11 |
| CITY1 | $900 |
8, 11 |
| PRDR1/2 (Radar) | $20d21 |
0, 5, 8, 10, 11, 17 |
| IND1, RES1, HGRP1 | $121 |
0, 5, 8 |
| BNK1 | $20121 |
0, 5, 8, 17 |
| HANGR | $20121 |
0, 5, 8, 17 |
| HANGRB (large) | $420121 |
0, 5, 8, 17, 22 |
| BUNKER, SHELT | $400021 |
0, 5, 22 |
| COMM (C&C) | $400921 |
0, 5, 8, 11, 22 |
| STRIP1 | $208021 |
0, 5, 15, 21 |
| DTSTRP | $108021 |
0, 5, 15, 20 |
Hitpoint scale¶
All confirmed in the word hitpoints field (field index 23). Values are
inferred to be absolute damage points. Compare dmg_armor across JT weapon
types against object hit_points to calibrate survivability.
File Inventory¶
| Category | Examples |
|---|---|
| Structures | BLDG1-3, FACTD, FCTYA/B, FACT1, IND1/2, STORE, HANGR/HANGRB, SHELT, CASTLE |
| Nuclear facilities | NUCCT, NUCOB, NUCRD, REACTB, REACTR |
| Runways / Aprons | STRIP1-7 (+ variants), APTB1, APTLA/B/C, APTM, APTOLD, DTSTRP |
| Bridges | BR1/2/3 END+MID, BRD1-4, BRDEND, BRDMID |
| Urban | CITY1-3, TWNBKA-F, CTYBKA-G, SLUM01/02, HOUS/HOUSB, HOOCH, RES1/2 |
| Bunkers / Military | BNK1-9 (+ variants), BUNKER, CMHQ1/2, SHELT, SILO |
| Radar / Comms | PRDR1/2, SRDR1/2, COMM/COMM1/2, RELAY, TOWER, CTOWR, CTWR1/2, COLTWR, KING |
| Terrain features | ROAD/ROAD2/4/ROADC, ROCKA-E (+ variants), TREE1/2, CROPA/B, WNDMLL, MOOSEA/B |
| SAM infrastructure | SA3SITE, HAWKSITE |
| Naval / Port | DOCK1, CONT1-3, CRANE, DKHOS1/2, CGRP1-3, HGRP1-3, SGRP1-3 |
| Misc | CRATER, OILW, SUPPLY, FUEL, WTRBUF, MICRO/MICROM, LOT, REDOM, FLAGO1/2, FLAGR1/2, FLAGY1/2, AV8TNT |
| Campaign variants (~) | ~BNK5, ~BNK6, ~BNK8, ~CLEMT, ~COLTWR, ~KITT, ~MOOSE, ~NIMZ, ~WASP |
All 170 live in FA_2.LIB.
Related¶
Formats: BRF — the family grammar and shared OBJ_TYPE reference;
SH — 3D shapes referenced by shape; NT — the moving-unit
counterpart; T2 — terrain maps that place OT objects; M —
mission files that instantiate OT objects as targets.
NT — NPC Vehicle Definition (.NT)¶
FA_2.LIB contains 84 .NT files. Each defines one non-player-controlled
vehicle or unit type (tanks, ships, soldiers, SAM launchers, etc.).
BRF plain text (NOT a Win32 PE DLL); decompressed sizes vary (e.g.
M1.NT = 1805 bytes).
Tools¶
fx¶
fx nt info <file.NT> # human-readable field dump
fx nt unpack <file.NT> [-o out.txt] # editable text
fx nt pack <in.txt> -o out.NT # write back (byte-identical)
File Layout¶
Plain text; BRF syntax (see BRF.md). Two-section structure: OBJ_TYPE (physical object base — same layout as JT/OT) followed by NPC_TYPE.
Section 1: OBJ_TYPE¶
byte 3 ; object category (3 = NPC/vehicle)
word 210 ; hitpoints
word 162 ; object subtype ID
ptr ot_names
dword $821 ; capability flags
word $400 ; flags2
ptr shape
... (reserved fields)
dword 1980 ; year introduced
word 78 ; (params)
word 100 ... (performance params)
byte 21
byte 6
dword 0
word 100
;---------------- movement info ----------------
word 2730 ; max speed
word 0 ... (accel/turn params)
word 50 ; turn rate
word 50
dword ^50 dword ^50
dword ^0 dword ^0
symbol _GVProc ; ground vehicle AI callback
;---------------- sound info ----------------
ptr loopSound
ptr secondSound ; (optional — e.g. turret sound separate from engine)
... (sound attenuation params)
Section 2: NPC_TYPE¶
;---------------- START OF NPC_TYPE ----------------
dword $0 ; AI flags
dword 0 ; (reserved)
byte 20 ; aggressiveness
byte 60 ; (AI skill param)
byte 40 ; (AI param)
word 32767 ; threat range
word 0 ; (flags)
byte 1 ; hardpoint count
ptr hards
;---------------- END OF NPC_TYPE ----------------
:hards
;-------- hardpoint 0
word $8 ; hardpoint flags
word 0 ; position x
word 30 ; position y
word 0 word 0 word 0 ; position z, angles
word 0
word 8190 ; firing cone
ptr defaultTypeName0 ; default weapon type
byte 0 ; (flags)
word 32767 ; ammo count (32767 = unlimited)
byte 0
Labels Section¶
:ot_names
string "M-1"
string "M-1 Abrams"
string "M1.NT"
:shape
string "m1.SH"
:loopSound
string "&TANK.11K"
:secondSound
string "&TURRET.11K"
:defaultTypeName0
string "M1.JT" ; weapon loaded on hardpoint 0
end
Hardpoints¶
Each hardpoint references a .JT file as its default weapon type.
ammo count = 32767 indicates unlimited ammunition. Multiple hardpoints are
indexed sequentially (defaultTypeName0, defaultTypeName1, ...).
obj_class word — confirmed values (full survey)¶
| Value | Class | Representative files |
|---|---|---|
$40 |
Personnel / infantry | CATGUY, EJECT, PLTDWN, RUNNER, SOLDIER, TROOPS |
$200 |
Ground vehicle (non-combat) | HUMVEE, LTRACK, MISTRK, MULE_A/B/C, SFLUSH, SRDR1/2, TANKER, TRUCK |
$400 |
Armor / tank | BMP2, BTR80, M1, M113, M1975, M2, T72, T80, T90 |
$800 |
AAA | A_M1939, KS12, KS19, M163, M1939, ZIF31, ZSU23, ZSU57 |
$1000 |
SAM launcher | 2S6, ASA5, CHAP, FIM92, HAWK, MIS, ROLAND, SA2A, SA3, SA6, SA7, SA9, SA13–16 |
$2000 |
Naval vessel | BARGE, BUTLER, CARGO, CIM, CLEM, CYCL, FISHBT, IOWA, JIANC, JIANE, JUNK, KIEV, KIROV, KITT, KNOX, KRIVAK, LCAC, NIMZ, OILR, OLEKMA, OSCAR, PMORN, RBOAT, SACRAM, SARAN, SESHDW, SL100, SOVR, TICON, WASP |
$40 (personnel) and ground structure $100 are shared with OT files. Naval
units ($2000) include CYCL (helicopter) — the game treats helicopters as
naval-class objects. Aircraft fighter/bomber values ($8000, $4000) appear
only in PT files.
The _GVProc proc symbol is shared across ground, AAA, SAM, and naval units;
the proc is inferred to dispatch internally on obj_class.
Hardpoint flags — confirmed patterns¶
All hardpoints have bit 3 ($8) set — the "active weapon slot" marker.
| Flag | Observed in | Interpretation |
|---|---|---|
$8 |
All ground-unit hardpoints; majority of naval hardpoints | Standard weapon slot — present on all weapon types |
$a |
Subset of naval hardpoints on ships with 4+ HPs | Bit 1 ($2) set in addition to bit 3: AI-guided weapon targeting active |
Bit 1 ($2) is confirmed as the AI-guided targeting flag —
NPCWeaponsProc (GVProc draw handler, param_1=5). When iterating type-7
missile hardpoints, the handler checks *data_ptr & 2; if set, calls
PROJServiceWeapon (AI targeting update) to compute intercept geometry for
that hardpoint. Ships like IOWA and KIROV use $a on specific hardpoints
(Phalanx, Sea Sparrow, AAA30) that the ship AI actively steers toward targets;
SARAN and KRIVAK with only 3 hardpoints do not use the AI-guided path.
SA2A has 6 hardpoints (launch tubes), all $8. Ships with 4 hardpoints mix
$8 and $a in varying ratios (e.g. IOWA 2+2, TICON 1+3).
NPC_TYPE AI params — confirmed (full survey)¶
| Field | Confirmed range | Interpretation |
|---|---|---|
aggressiveness |
20 (most ground) / 40 (ships, capable SAMs) | Engagement aggression level |
skill |
20–176 | Fire control accuracy; higher = better targeting |
reaction |
20–80 | Threat acquisition speed; higher = slower (longer lock-on delay) |
Observed values by unit class:
| Unit type | aggressiveness | skill | reaction | Examples |
|---|---|---|---|---|
| Ground vehicle (unarmed) | 20 | 60 | 40 | TRUCK, HUMVEE, MULE |
| Armor | 20 | 60 | 40 | M1, T72, BMP2, BTR80 |
| Basic MANPADS / short-range SAM | 20 | 60 | 40 | SA7, SA9, SA13-16, FIM92 |
| Basic AAA | 20 | 80 | 40 | ZSU23 |
| Advanced AAA (heavy caliber) | 40 | 80–100 | 40–60 | KS12, KS19, ZSU57, M163 |
| Advanced SAM | 40 | 144 | 60 | SA2A, SA3, SA6 |
| Naval vessel | 40 | 100 | 80 | IOWA, KIROV, most ships |
TRUCK.NT (no weapons at all) and M1.NT share identical AI params
(20 / 60 / 40) — confirming these fields drive general NPC movement and
threat-response behavior rather than weapon accuracy alone.
ot_flags dword — observed NT patterns¶
Full survey of all 84 NT files.
| Flag | Units | Bits active |
|---|---|---|
$821 |
Most ground units (tanks, AAA, SAM, vehicles) | 0, 5, 11 |
$131 |
Most naval vessels | 0, 4, 5, 8 |
$331 |
OILR (oil rig) | 0, 4, 5, 8, 9 |
$521 |
Small boats (FISHBT, JUNK, RBOAT) | 0, 5, 8, 10 |
$c21 |
Infantry (SOLDIER, RUNNER) | 0, 5, 10, 11 |
$d31 |
GCI (ground-controlled intercept radar) | 0, 4, 5, 8, 10, 11 |
$801 |
Passive units (MULE_A/B/C, EJECT, PLTDWN) | 0, 11 |
$400 |
CATGUY (carrier deck officer — visual only) | 10 |
$c8331 |
Conventional carriers (CLEM, KITT, NIMZ) | 0, 4, 5, 8, 9, 15, 18, 19 |
$108331 |
VSTOL carrier (WASP) | 0, 4, 5, 8, 9, 15, 20 |
$1c8131 |
Hybrid carrier (KIEV) | 0, 4, 5, 8, 15, 18, 19, 20 |
$2000821 |
Emplaced AA artillery (KS12, KS19, M1939) | 0, 5, 11, 25 |
$4000821 |
SA2A only | 0, 5, 11, 26 |
Bit semantics:
| Bit | Mask | Label | Status | Notes |
|---|---|---|---|---|
| 0 | $1 |
Targetable | confirmed | Absent on CATGUY (purely visual) and A_M1939 (zone marker) |
| 4 | $10 |
Naval vessel | confirmed — FUN_0043df7b |
Naval-weapon gate: weapon type 3 requires this bit; absent on all ground vehicles. |
| 5 | $20 |
Armed / valid combat target | confirmed — FUN_0043df7b |
Hard gate in targeting acquisition — entity with bit 5 clear is immediately rejected as a valid target. Absent on passive MULE/EJECT. |
| 8 | $100 |
Has collideable oriented hull | confirmed — FUN_0042c9b0 |
When set with non-zero heading, uses angle-based hit detection (Rotate2). Present on large ships; absent on small boats and ground vehicles. |
| 9 | $200 |
Large hull with 3D-oriented bounding box | confirmed — FUN_0042c9b0 |
Tested combined as & 0x300 (bits 8+9): triggers full 3D bounding-box rotation (MakeViewRotationMatrix) for hit detection. Never set without bit 8. Large ships (carriers, oil rig, GCI); absent on standard destroyers/cruisers. |
| 10 | $400 |
Civilian/light type | confirmed — _Reaction_12 (0x464040), _MaskEvents_4 (0x463ea0) |
Infantry (SOLDIER, RUNNER), small civilian craft (FISHBT, JUNK, RBOAT), CATGUY. Drives the event-system handlers; also toggles bay-door actuator. Shared semantic with OT bit 10. |
| 11 | $800 |
Ground-mobile unit | confirmed — FUN_0042c9b0 |
OBJ_TYPE+9 & 0x800 sets a ground-mobile state flag in targeting/collision resolver. Present on all ground vehicles; absent on naval vessels (carriers have it via extra bits). |
| 15 | $8000 |
Flight deck present | confirmed — FUN_00425196 |
When set, forces targeting category to 4 (flight deck special class). Also tested combined with bit 22 (& 0x408000). All carrier types (CLEM, KITT, NIMZ, KIEV, WASP). |
| 18 | $40000 |
Fuel bar capacity doubler / conventional carrier | confirmed (fuel bar display) | *(uint*)(entity+1) & 0x40000 tested in two fuel bar display functions (FUN_004558f0 area at line 45759; FUN_00484e?? at line 104545): when set, fuel_capacity = OBJ_TYPE+0x49_value × 2 for the bar scale. Present: CLEM, KITT, NIMZ, KIEV; absent on VSTOL-only WASP. Original "arrestor-wire deck" label is inferred from distribution pattern only — carrier landing handler inline tests not confirmed. Actual confirmed effect: doubles the fuel bar capacity denominator so the bar correctly represents the larger fuel load of conventional carriers. |
| 19 | $80000 |
NPC burst-size modifier A | confirmed — _PROJServiceWeapon@24 (0x4c4700) |
Tested at *(uint*)(pcVar11 + 0xa6) & 0x80000 in the weapon service loop. When set, a 25% chance (_Percent_4(0x19)) scales the burst-round count to (75 + rand(50))% of the base value — a high-burst probability path. Present on conventional carriers (CLEM, KITT, NIMZ, KIEV); consistent with high-cyclic defense weapons (Phalanx CIWS). |
| 20 | $100000 |
NPC burst-size modifier B | confirmed — _PROJServiceWeapon@24 (0x4c4700) |
Tested at *(uint*)(pcVar11 + 0xa6) & 0x100000 in the same weapon service loop. When set (and bit 19 path not taken), a 75% chance (_Percent_4(0x4b)) scales burst-round count to (50 + rand(100))% of base — a medium-burst probability path. Present on VSTOL carrier WASP and hybrid KIEV. |
| 22 | $400000 |
Prominent/large variant | confirmed — FUN_0042c9b0 |
When set, collision geometry uses hit-point count of 16; also tested combined with bit 15. |
| 25 | $2000000 |
Emplaced AA artillery | dead/reserved | KS12 (85mm), KS19 (100mm), M1939 (37mm). No entity ot_flags bit 25 test found anywhere in the game executable full decompile. Only _gamePrefs & 0x2000000 hits exist — confirmed as a player-preference missile-targeting assist flag (HUD weapon targeting code, line 5239 area), unrelated to entity type. No terrain overlay DLL exists in FA_1.LIB or FA_2.LIB. Bit is set but never consumed — likely a reserved flag from a scrapped feature. Label "Emplaced AA artillery" remains inferred from unit distribution only. |
| 26 | $4000000 |
SA-2A fixed-site SAM — no post-kill evasion | confirmed — _GVEventProc (0x473f50) |
DAT_0050d271 & 0x4000000 (entity+0x09 & 0x4000000). In the kill-hit path: if damage category bits 0x1e are active AND bit 26 is NOT set, calls FUN_00474570() (creates evasion move command) + _ImmediateService_0(). Bit 26 set (SA-2A) skips this evasion branch — SA-2A does not attempt a post-kill dodge maneuver. SA2A only. |
Proc symbols¶
| Symbol | Observed in | Role |
|---|---|---|
_GVProc |
M1, ZSU23, TRUCK, IOWA, KIROV | NPC AI dispatcher (shared across all categories) |
_SHIPProc |
(inferred for naval units) | Not yet observed in a file; naval units surveyed so far use _GVProc |
_PROJProc |
(via JT) | Projectile physics |
File Inventory¶
| Category | Examples |
|---|---|
| Tanks | M1, T72, T80, T90, TYPE69 |
| APCs | M2, M113, BTR80, BMP2 |
| AAA | ZSU23, ZSU57, M163 |
| SAM launchers | SA2A, SA3, SA6, SA7, SA9, SA13-16, SA19, HAWK, ROLAND, MIM23, FIM92, HQ61 |
| Ships | IOWA, KIROV, KIEV, OSCAR, NIMZ, SFLUSH, OLEKMA, TICON, KNOX, KRIVAK, SOVR, JIANC, JIANE, PMORN, SESHDW |
| Vehicles | HUMVEE, TRUCK, LTRACK |
| Air units | CYCL (helicopter), EJECT (ejection seat) |
| Naval small | BARGE, CARGO, CARGO2, RBOAT, WASP, LCAC |
| Personnel | SOLDIER, TROOPS, CATGUY |
| Misc | SCUD, GCI, OILR, SACRAM, RUNNER |
All 84 live in FA_2.LIB.
Related¶
Formats: BRF — family grammar; JT — weapons on
hardpoints; SH — 3D shapes; OT — static counterparts;
AI — the AI system that drives _GVProc behavior.
ECM — Electronic Countermeasures Definition (.ECM)¶
FA_2.LIB contains 30 .ECM files. Each defines the electronic warfare suite
for one aircraft type (e.g. F15.ECM, AV8.ECM, B52.ECM). BRF plain
text (NOT a Win32 PE DLL); very small — F15.ECM decompresses to 352 bytes.
Loaded at runtime by the FA weapon/sensor system.
Tools¶
fx¶
fx ecm info <file.ECM> # human-readable field dump
fx ecm unpack <file.ECM> [-o out.txt] # editable text
fx ecm pack <in.txt> -o out.ECM # write back (byte-identical)
File Layout¶
Plain text; BRF syntax (see BRF.md). Hex values use $XX.
Full F15.ECM example:
[brent's_relocatable_format]
byte 9 ; type identifier (9 = ECM)
ptr si_names
word 0 ; quantity (0 = built-in; N = pod carrying capacity)
byte $0 ; category (0 = built-in suite, 1 = external pod)
word $1f0 ; ECM power level (0=none, $f0=partial, $1f0=full active jamming)
byte 30 ; effectiveness A (variable — chaff)
byte 35 ; band constant 1 (fixed across all ECM files)
byte 95 ; band constant 2 (fixed)
byte 24 ; band constant 3 (fixed)
byte 30 ; effectiveness B (variable — flare)
byte 35 ; band constant 4 (fixed)
byte 159 ; band constant 5 (fixed)
byte 31 ; band constant 6 (fixed)
byte 30 ; effectiveness C (+0x12) = radar Pk reduction (variable)
word 100 ; overall ECM strength (100 = full; 0 = none)
byte 0
byte 0
byte 40 ; secondary effectiveness (+0x17) = IR Pk reduction (variable)
word 100 ; secondary strength
byte 0
:si_names
string "" ; short name (empty for all ECM files)
string "" ; long name
string "F15.ECM" ; filename (self-reference)
end
Pod vs. Built-In¶
| Category | word (qty) |
byte (type) |
Examples |
|---|---|---|---|
| Built-in suite | 0 | $0 | F14, F15, F22, A10, B52, EA6, MIG21 |
| External pod | N | $1 | ALE40 (qty 100), ALQ72 (qty 224), ALQ167 (qty 310) |
ECM Power Levels¶
word |
Decimal | Meaning | Examples |
|---|---|---|---|
| $0 | 0 | No active jamming | MIG21, ALE40 (chaff/flare only) |
| $f0 | 240 | Radar-only active jammer | ALQ72 |
| $1f0 | 496 | Radar + IR jammer | F14, F15, F22, B52, EA6, ALQ167 |
Power field is a bitmask (confirmed via @HARDFindJammer@4 at
0x00452EA0):
| Bit | Hex | Meaning |
|---|---|---|
| 4 | 0x010 |
Radar jammer active |
| 5–7 | 0x0E0 |
Unused — always set alongside bit 4 in active-radar ECM files; no function tests these bits individually |
| 8 | 0x100 |
IR jammer active |
@HARDFindJammer@4 iterates hardpoints of type 9 (ECM) and rejects pods where
the seeker type does not match the active jammer bit: IR seekers need 0x100,
radar seekers need 0x10. A pod with $f0 = 0000 1111 0000 has bit 4 set
(radar) but not bit 8 (no IR). A pod with $0 passes neither check and
functions as a passive dispenser only.
Bits 5–7 confirmed unused: exhaustive scan of all AND/TEST instructions in the
ECM/jammer code range (0x452000–0x454000) found zero tests against these bits
of the power word. They are set in all active-radar ECM files ($f0 = bits
4–7; $1f0 adds bit 8) but the engine dispatches exclusively on bits 4 and 8.
Effectiveness Bytes — resolved¶
The 9-byte block (BRF offsets +0x0A–+0x12) contains three variable
effectiveness bytes interleaved with six fixed jamming-geometry bytes
forming two groups of three (radar at +0x0B/+0x0C/+0x0D; IR at
+0x0F/+0x10/+0x11). All roles confirmed via Ghidra (HARDCanLoad and related
callers):
| Aircraft | eff_A (+0x0A) | eff_B (+0x0E) | eff_C (+0x12) | +0x17 (IR Pk) | ECM power |
|---|---|---|---|---|---|
| EA6 (dedicated EW) | 99 | 99 | 80 | 80 | $1f0 |
| F22, A10, F14, B52 | 50 | 50 | 30–50 | 30–50 | $1f0 |
| F15 | 30 | 30 | 30 | 30 | $1f0 |
| MIG21 | 20 | 20 | 0 | 0 | $0 |
| ALE40 (chaff/flare) | 30 | 30 | 0 | 0 | $0 |
| ALQ72 (radar jammer) | 0 | 0 | 30 | 0 | $f0 |
- +0x0A = chaff effectiveness — confirmed via
_DAMAGEDoHit@12; non-zero means aircraft has chaff dispensers. - +0x0E = flare effectiveness — confirmed via
_DAMAGEDoHit@12; non-zero means aircraft has flare dispensers. - +0x12 = radar jamming effectiveness — confirmed via
_PROJHitChance@28(0x004c3380), read when the shooter has a radar seeker (missile+0xb4 == 3):Pk_final = (100 − eff_C) × Pk_base / 100. - +0x17 = IR jamming effectiveness — same function reads
ecm_ptr+0x17for IR seekers (missile+0xb4 == 2).
_DAMAGEDoHit@12 (0x0040f970) case 9 reads the dispenser bytes independently
across three hit-probability bands when the player is hit:
// 0–24%: active jammer hit — full lock break
if ((*(ushort *)(entity + 0x08) & 0x110) != 0) { *(ecm_struct + 4) = 0; }
// 25–64%: chaff dispenser hit — radar lock disruption
if (entity[0x0A] != 0) { *(ecm_struct + 6) = 0; }
// 65–99%: flare dispenser hit — IR lock disruption
if (entity[0x0E] != 0) { *(ecm_struct + 7) = 0; }
ALE40 has eff_A=30 and eff_B=30 (both dispensers present); ALQ72 has eff_A=0 and eff_B=0 (active jammer only, no passive dispensers). ✓
Jammer Geometry Bytes — confirmed¶
The six fixed bytes form two groups used by the jammer placement / seeker-defeat calculation:
| Offset | Value | Group | Role |
|---|---|---|---|
+0x0B |
35 | Radar | Effectiveness-scaling parameter — passed directly to PROJRetargetMissilesOnDevice / MPLaunchDevice |
+0x0C |
95 | Radar | Jammer azimuth half-angle — passed as 95 << 8 (fixed-point) to MakeObjRotationMatrix param_2 |
+0x0D |
24 | Radar | Jammer elevation half-angle — passed as 24 << 8 (fixed-point) to MakeObjRotationMatrix param_3 |
+0x0F |
35 | IR | Effectiveness-scaling parameter — same role as +0x0B |
+0x10 |
159 | IR | Jammer azimuth half-angle — passed as 159 << 8 to MakeObjRotationMatrix param_2 |
+0x11 |
31 | IR | Jammer elevation half-angle — passed as 31 << 8 to MakeObjRotationMatrix param_3 |
The radar group is selected when cStack_29 == 3 (radar seeker); the IR group
when cStack_29 == 2 (IR seeker). Band codes 0x0C (radar) and 0x0D (IR) are
passed to GRAPHICAddDevice as the jamming-band identifier.
MakeObjRotationMatrix confirmed (from PROJLaunchDevice decompile):
takes (output_buf, azimuth_halfangle<<8, elevation_halfangle<<8,
vertical_tilt). It computes the seeker's angular-bounds block (5 dwords) for
cone-overlap testing. When vertical tilt is 0 (all game calls), it initialises
symmetric ±azimuth / ±elevation bounds then calls sincos (sin/cos) and
FUN_004cf2d0 (rotation). The larger the half-angle byte, the wider the
jammer coverage arc — IR (159/31) is much wider in azimuth than radar (95/24).
Hypothesis disproven: the original theory that these bytes encode RWR radar-band frequencies (L, S, C, X, Ka) is incorrect. They are jammer effectiveness and beam-geometry parameters, consistent across all 30 ECM files because they describe the jammer hardware characteristics, not aircraft-specific threat coverage.
File Inventory¶
| File | Notes |
|---|---|
| A6.ECM | Intruder |
| A7.ECM | Corsair II |
| A10.ECM | Thunderbolt II |
| AC130.ECM | Spectre gunship |
| ALE40.ECM | Expendable countermeasures dispenser |
| ALQ167.ECM | ECM pod |
| ALQ72.ECM | ECM pod |
| AV8.ECM | Harrier (limited ECM) |
| B52.ECM | Stratofortress |
| EA6.ECM | Prowler (dedicated EW aircraft) |
| F4.ECM | Phantom II |
| F8.ECM | Crusader |
| F14.ECM | Tomcat |
| F15.ECM | Eagle |
| F18.ECM | Hornet |
| F22.ECM | Raptor |
| F104.ECM | Starfighter |
| KA50.ECM | Hokum |
| MIG21.ECM | Fishbed |
| MIG27.ECM | Flogger |
| MIG29.ECM | Fulcrum |
| MIG31.ECM | Foxhound |
| SEAHAR.ECM | Sea Harrier |
| SU24.ECM | Fencer |
| SU27.ECM | Flanker |
| SU37.ECM | Terminator |
| TU26.ECM | Backfire |
| TU95.ECM | Bear |
| TU160.ECM | Blackjack |
| YAK141.ECM | Freestyle |
All 30 live in FA_2.LIB.
Related¶
Formats: BRF — family grammar and the aircraft definitions that reference ECM suites; SEE — seeker/sensor definitions whose performance ECM degrades; JT — weapon seekers that ECM jams.
GAS — External Fuel Tank Definition (.GAS)¶
FA_2.LIB contains 4 .GAS files. Each defines one external fuel tank type
that can be carried as a stores item. BRF plain text (NOT a Win32 PE
DLL); very small — F150.GAS decompresses to 204 bytes. Loaded at runtime by
the FA weapon/stores system.
Tools¶
fx¶
fx gas info <file.GAS> # human-readable field dump
fx gas unpack <file.GAS> [-o out.txt] # editable text
fx gas pack <in.txt> -o out.GAS # write back (byte-identical)
File Layout¶
Plain text; BRF syntax (see BRF.md). Hex values use $XX.
[brent's_relocatable_format]
byte 8 ; type identifier (8 = fuel tank)
ptr si_names
word <empty_weight> ; empty tank structural weight (lbs)
byte $1 ; flags (always $1 = fuel-tank category)
dword <fuel_weight> ; full-tank fuel weight (lbs); used to init fuel store
:si_names
string "<short>" ; short display name
string "<long>" ; long display name
string "<file>.GAS" ; filename (self-reference)
end
Fuel weight (dword) — confirmed as pounds¶
The dword is full-tank fuel weight in pounds. It is 6.6× the gallon
count (JP-8 density = 6.6 lb/US gal):
| Tank | Gallons × 6.6 | dword |
|---|---|---|
| 150 gal | 990 | 990 |
| 250 gal | 1650 | 1650 |
| 350 gal | 2310 | 2300 |
| 500 gal | 3300 | 3300 |
HARDLoad (hardpoint load) initializes the fuel store as
dword × quantity × 256 — fixed-point (×256) representation. _BurnFuel@0
decrements this value by fuel_rate × 5 each simulation batch.
Cross-referenced against PT internal fuel fields (PLANE_TYPE + 0x165):
| Aircraft | PT fuel_capacity | Known fuel (lbs) |
|---|---|---|
| A-10 | 10700 | 10,700 lbs ✓ |
| F-16C | 6972 | 6,972 lbs (Block 25/30) ✓ |
The GAS dword is in the same unit — fuel weight in pounds. The ratio
990/150 = 6.6 lb/gal confirms JP-8 density to within rounding.
Empty weight (word) — confirmed as pounds¶
The word is empty tank structural weight in pounds (confirmed via
FMGetWeight, the loadout weight calculator):
total_tank_weight = empty_weight × count + (current_fuel >> 8).
Cross-checking against typical external fuel tank empty weights (120–260 lbs
for 150–500 gal aluminum tanks) confirms the values. This produces correct
full-tank weights:
| Tank | word (empty) + dword (fuel) = full weight |
|---|---|
| 150 gal | 108 + 990 = 1098 lbs |
| 250 gal | 198 + 1650 = 1848 lbs |
| 350 gal | 248 + 2300 = 2548 lbs |
| 500 gal | 315 + 3300 = 3615 lbs |
These match real-world 150–500 gallon external fuel tank weights (full) to within typical simulator rounding.
Flags (byte $1)¶
Always $1 across all four files. Stores-category flag: value 1 = fuel tank
(as opposed to weapon).
File Inventory¶
| File | word (empty_weight lbs) | dword (fuel_weight lbs) | Full weight | Short name | Long name |
|---|---|---|---|---|---|
| F150.GAS | 108 | 990 | 1098 | "150 gallon tank" | "150 Gallon External Fuel Tank" |
| F250.GAS | 198 | 1650 | 1848 | "250 gallon tank" | "250 Gallon External Fuel Tank" |
| F350.GAS | 248 | 2300 | 2548 | "350 gallon tank" | "350 Gallon External Fuel Tank" |
| F500.GAS | 315 | 3300 | 3615 | "500 gallon tank" | "500 Gallon External Fuel Tank" |
All 4 live in FA_2.LIB.
Related¶
Formats: BRF — family grammar, and the PT records whose base fuel capacity and consumption rates these tanks extend; JT — the stores system that GAS files participate in alongside weapons.
SEE — Seeker Sensor Definition (.SEE)¶
FA_2.LIB contains 51 .SEE files. Each defines the sensor parameters for one
seeker type — radar, infrared, targeting pod, or visual acquisition cone.
BRF plain text (NOT a Win32 PE DLL); F15R.SEE decompresses to 470
bytes. Loaded at runtime by the FA weapon guidance and AI targeting system.
Tools¶
fx¶
fx see info <file.SEE> # human-readable field dump
fx see unpack <file.SEE> [-o out.txt] # editable text
fx see pack <in.txt> -o out.SEE # write back (byte-identical)
File Layout¶
Plain text; BRF syntax (see BRF.md). Hex values use $XX;
fixed-point/relative values use ^XXXXX.
F15R.SEE — Radar Seeker Example¶
[brent's_relocatable_format]
byte 10 ; type identifier (10 = seeker/sensor)
ptr si_names
word 0 ; seeker identifier / capability flags
byte $0 ; seeker sub-type
byte 3 ; seeker type (0=visual, 1=laser, 2=IR, 3=radar)
byte $1 ; mode flags (dual-mode enable — inferred, see Open Questions)
byte 30 ; param — likely acquisition time or track rate (unconfirmed)
byte 0 ... byte 0 ; (4 bytes reserved/unused)
; --- Primary lobe ---
word 10920 ; azimuth half-angle (≈ 60° at 182 units/°)
word 10920 ; elevation half-angle
dword ^0 ; min range
dword ^911400 ; max range (feet)
dword $80000000 ; min heading error (sentinel = no limit)
dword $7fffffff ; max heading error (sentinel = no limit)
; --- Secondary lobe ---
word 8190 ; azimuth half-angle (≈ 45°)
word 8190 ; elevation half-angle
dword ^0 ; min range
dword ^607600 ; max range
dword $80000000
dword $7fffffff
byte 100 ; probability of detection lobe 1 (100 = 100%)
byte 100 ; probability of detection lobe 2
:si_names
string "" ; short name (empty for aircraft radar — not a display item)
string "" ; long name (empty)
string "F15R.SEE" ; filename (self-reference)
end
PAVEKNF.SEE — Targeting Pod (AVQ-10 Pave Knife)¶
word 425— pod identifier / capability codebyte 1— seeker type (laser)- Cone half-angles:
word 32767(approximately 180° — omnidirectional) - Short max range:
^60760 - Both lobes identical
si_namescontains display label strings (unlike aircraft radars)
VIS300.SEE — 300° Visual Acquisition Cone¶
word 0— no capability flagsbyte 0— seeker type (visual)- Azimuth:
word 27300(≈ 150° half-angle = 300° total arc) - Elevation:
word 21840(≈ 120°) - Empty
si_namesstrings
Angle Encoding¶
word 16380 = 90° (quarter circle), giving a resolution of 16380 / 90 =
182 units per degree.
| word value | Degrees |
|---|---|
| 8190 | 45° |
| 10920 | 60° |
| 13650 | 75° |
| 16380 | 90° |
| 21840 | 120° |
| 27300 | 150° |
| 32767 | ~180° |
All values are half-angles; total cone coverage is double the stored value.
Range Encoding¶
dword ^XXXXXX uses the ^ (relative/fixed-point) prefix. 1 unit = 1
foot (confirmed via cross-reference of multiple sensor files against
published system ranges):
| File | Stored value | ÷ 6076 | Known range |
|---|---|---|---|
| AV8L.SEE (laser pod) | ^60760 | 10.0 nm | TIALD pod ~10 nm ✓ |
| AIM9X lobe 1 | ^50000 | 8.2 nm | AIM-9X ~8 nm ✓ |
| AIM120 lobe 1 | ^144000 | 23.7 nm | AIM-120A ~25 nm ✓ |
| AGM65G lobe 1 | ^60000 | 9.9 nm | AGM-65G ~10 nm ✓ |
| AGM84A lobe 1 | ^360000 | 59.3 nm | Harpoon ~60 nm ✓ |
| F4BR.SEE (APQ-72) | ^303800 | 50 nm | APQ-72 ~40–50 nm ✓ |
| E3R.SEE (AWACS) | ^1215200 | 200 nm | E-3 ~200–250 nm ✓ |
6076 ft = 1 nautical mile; divide any ^ range value by 6076 to get nm. (An
earlier 11400-units/nm hypothesis from F15R.SEE alone was incorrect; the
per-foot calibration is consistent across 7 independent data points.
F15R.SEE's ^911400 ≈ 150 nm represents maximum theoretical lobe extent under
ideal conditions, not the pilot-selectable APG-63 range.)
Seeker Type Byte — confirmed¶
| Value | Type | Evidence |
|---|---|---|
byte 0 |
Visual | VIS*.SEE files |
byte 1 |
Laser | AV8L.SEE (Harrier laser designator), KA50L.SEE, SU24L.SEE, SU37L.SEE |
byte 2 |
IR / EO | AIM9M.JT, AIM9X.JT, AGM65G.JT PROJ_TYPE seeker byte |
byte 3 |
Radar (active or semi-active) | F4BR.SEE (APQ-72), AV8R.SEE (Blue Fox), E3R.SEE (AWACS), AIM120.JT, AGM84A.JT |
Type byte 1 (laser) confirmed: AV8L.SEE is the AV-8B Harrier laser designator
pod; its primary lobe max range ^60760 = 10 nm matches TIALD laser pod
operational range.
Sentinel Values¶
dword $80000000— minimum heading error sentinel: no lower limit (any heading error passes)dword $7fffffff— maximum heading error sentinel: no upper limit
Confirmed via _PROJInFOV@40 (0x004c2860): the heading error test explicitly
bypasses the check when the sentinel values are present. A lobe with both
sentinels set accepts any target heading relative to the sensor. Setting
word 32767 for both half-angles (0x7FFF) additionally triggers an
unconditional pass that skips the angle check entirely — this is the
PAVEKNF.SEE omnidirectional cone.
Dual-Lobe Structure — resolved¶
Each SEE file defines two detection lobes, each with independent azimuth/elevation half-angles, range limits, heading error limits, and probability of detection. Primary lobe = search mode; secondary lobe = track/lock mode:
| File | Lobe 1 (primary / search) | Lobe 2 (secondary / track) |
|---|---|---|
| F4BR.SEE | 50 nm, 60° half-angle | 25 nm, 45° half-angle |
| AV8R.SEE | 90 nm, 60° half-angle | 50 nm, 45° half-angle |
File Inventory¶
Aircraft Radar¶
| File | Aircraft |
|---|---|
| A6R.SEE | A-6 Intruder |
| A7R.SEE | A-7 Corsair II |
| A10R.SEE | A-10 Thunderbolt II |
| AC130R.SEE | AC-130 Spectre (radar) |
| AC130I.SEE | AC-130 Spectre (IR) |
| AV8R.SEE | AV-8 Harrier (radar) |
| AV8IR.SEE | AV-8 Harrier (IR) |
| AV8L.SEE | AV-8 Harrier (laser) |
| B52R.SEE | B-52 Stratofortress |
| E2R.SEE | E-2 Hawkeye |
| E3R.SEE | E-3 Sentry (AWACS) |
| E6R.SEE | E-6 Mercury |
| E8R.SEE | E-8 J-STARS |
| EA6R.SEE | EA-6 Prowler |
| F4R.SEE | F-4 Phantom II |
| F4BR.SEE | F-4B Phantom II |
| F4JR.SEE | F-4J Phantom II |
| F8R.SEE | F-8 Crusader |
| F14R.SEE | F-14 Tomcat |
| F15R.SEE | F-15 Eagle |
| F18R.SEE | F/A-18 Hornet |
| F22R.SEE | F-22 Raptor |
| F104R.SEE | F-104 Starfighter |
| KA50L.SEE | Ka-50 Hokum (laser) |
| MIG21R.SEE | MiG-21 Fishbed |
| MIG27R.SEE | MiG-27 Flogger |
| MIG29R.SEE | MiG-29 Fulcrum (radar) |
| MIG29I.SEE | MiG-29 Fulcrum (IR) |
| MIG31R.SEE | MiG-31 Foxhound |
| SEAHARR.SEE | Sea Harrier |
| SU24R.SEE | Su-24 Fencer (radar) |
| SU24L.SEE | Su-24 Fencer (laser) |
| SU27R.SEE | Su-27 Flanker (radar) |
| SU27I.SEE | Su-27 Flanker (IR) |
| SU35R.SEE | Su-35 |
| SU37R.SEE | Su-37 Terminator (radar) |
| SU37I.SEE | Su-37 Terminator (IR) |
| SU37L.SEE | Su-37 Terminator (laser) |
| TU26R.SEE | Tu-26 Backfire |
| TU95R.SEE | Tu-95 Bear |
| TU160R.SEE | Tu-160 Blackjack |
| YAK141R.SEE | Yak-141 Freestyle |
Targeting Pods¶
| File | System |
|---|---|
| PAVEKNF.SEE | AVQ-10 Pave Knife |
| PAVESPK.SEE | Pave Spike |
Ground / Offboard Sensors¶
| File | System |
|---|---|
| AAS38.SEE | AAS-38 FLIR pod |
| GCIR.SEE | GCI radar |
| REDCR.SEE | Red Crown radar |
Visual Acquisition Cones¶
| File | Coverage |
|---|---|
| VIS180.SEE | 180° arc |
| VIS240.SEE | 240° arc |
| VIS300.SEE | 300° arc |
| VIS340.SEE | 340° arc |
All 51 live in FA_2.LIB.
Engine Notes¶
Search/track lobe dispatch — confirmed¶
The lobe-switch trigger was confirmed via _PROJLock@24 (0x004c2f20) and
PROJServiceWeapon.
The engine maintains a seeker session context struct at fixed address
0x0050ce80. The struct has a flags word at offset +0xde (DAT_0050cf5e)
whose bits record the current lock state:
- DAT_0050cf5e & 0x10000 — search lock active (partial bracket on HUD)
- DAT_0050cf5e & 0x20000 — track lock active (full bracket on HUD)
- DAT_0050cf5e & 0x100000 — radar on; required for track-lobe checks
(set/cleared by player radar toggle, FlightKey case 0x52)
- DAT_0050cf5e & 0x400 — seeker enabled (detectable flag)
Transition writer confirmed: PROJServiceWeapon (outer guidance loop):
after _PROJLock@24 returns a lock, evaluates the angular error
probabilistically against target+0xe8/+0xea thresholds:
- Clears both bits: DAT_0050cf5e & 0xfffcffff (target out of cone or below
threshold)
- Sets 0x10000 when within the wider search-lock zone
- Sets 0x20000 when within the tighter track-lock zone
The projectile/entity's own flags at struct +0xa6 select which lobe check
applies:
- entity+0xa6 & 0x10000 set → _PROJLock@24 calls search-lobe check
(PROJRadarIsOn)
- entity+0xa6 & 0x20000 set (and 0x10000 clear) → calls track-lobe check
(FUN_004c31f0)
Both lobe-check functions receive the seeker session context pointer
(0x0050ce80) and a timer-window parameter (0x28 = 40 game ticks).
PROJRadarIsOn (search lobe, 0x004c2eb0): when the seeker has not yet
acquired (ctx+0x10 & 0x80 == 0), checks that the seeker is enabled
(ctx+0xde & 0x400) and initialises a lock-hold timer at ctx+0x11a
(DAT_0050cf9a) to now + 40 ticks. Once acquired (+0x80 set), verifies
the timer has not expired; if ctx+0x11a ≤ now the check fails and lock is
dropped.
FUN_004c31f0 (track lobe, 0x004c31f0): identical timer logic, but when
acquired additionally requires ctx+0xde & 0x100000 (radar on). If radar is
off, track check fails even if the timer is still valid.
_PROJInFOV@40 (0x004c2860) performs the range and heading-error comparison
using the lobe selected by param_3:
- param_3 == 0 → primary lobe data (SEE offset +0x0F)
- param_3 != 0 → secondary lobe data (SEE offset +0x23)
Open Questions¶
1. Mode flag and parameter byte¶
The byte $1 at offset 5 (after the seeker type byte) is inferred to be a
dual-mode enable flag — set on radars with meaningfully different search/track
lobes (F4BR, E3R), clear where lobes differ only in cone angle (AV8L, AV8R) —
and the following byte 30 is inferred to be an acquisition-time or
track-rate parameter. Neither has a traced consumer.
Status: open — re-static (#54)
Related¶
Formats: BRF — family grammar; JT — PROJ_TYPE seeker sections reference SEE definitions and share the angle/range encodings; ECM — ECM degrades seeker performance against SEE parameters.
UI & Win32 Overlays
DLG — Menu Dialog Layout (.DLG)¶
FA_2.LIB contains 92 .DLG files. Each defines one dialog box in the FA menu
system. All are Win32 PE DLLs (MZ stub + Phar Lap PE image) loaded at
runtime; they import rendering functions from main.dll (= the game executable — see
architecture.md) and embed
their label strings in the PE data section. The engine associates dialogs with
their parent MNU file; the DLG is loaded when the corresponding menu item is
selected.
Tools¶
fx¶
fx dlg info <file.DLG> # container check + CODE section geometry
fx dlg strings <file.DLG> [-n MIN] # embedded control label strings
Container-level surface for now: same MZ + Phar Lap PL family as
CAM/MNU; all 92 shipped dialogs validate and surface
their label strings. The structural record-table decode below is a larger
reverse-engineering task — the on-disk record layout differs from the
in-memory layout documented here (records begin at the draw_fn_ptr thunk
VA; the type_flags/next_record_ptr header fields are engine-written) —
and is tracked under #54.
File Layout¶
All multi-byte integers are little-endian.
Win32 PE DLL. All DLG files import from main.dll. The set of imported
drawing functions varies per dialog and reveals the control types used:
| Import | Control rendered |
|---|---|
_DrawAction |
Clickable button |
_DrawRocker |
Toggle / rocker selector |
_DrawEditBox |
Editable text input field |
_DrawText |
Static text label |
_DrawFormattedText |
Multi-line formatted text block |
_DrawCampaignList |
Campaign list box |
_cancelString |
Localized "Cancel" button label |
_okString |
Localized "OK" button label |
Dispatch Table Layout — confirmed¶
DLG files use Phar Lap PE format (signature PL\0\0 instead of PE\0\0).
There is no compiled x86 code — the CODE section is a dispatch table of
variable-size records followed by packed label strings.
Common record header — 10 bytes (all types), confirmed via _DialogSetup
(DialogSetup), the draw dispatcher (DialogDraw), and the event dispatcher
(DialogUpdate):
| Offset | Type | Field |
|---|---|---|
+0x00 |
u16 | type_flags — bits 0–14 = record type (0–9); bit 15 = disabled/dim flag. Set at runtime by DisableActionButton; cleared by DialogDeselectItem. Draw functions read bit 15 (via byte +0x01 bit 7) to choose dim vs. normal colour variant. |
+0x02 |
u32 | next_record_ptr — engine-written during _DialogSetup; zero in DLG file. Linked-list pointer to the next record; traversed by draw pass and event dispatcher via *(record+2). |
+0x06 |
u32 | draw_fn_ptr — VA of the JMP thunk for this record's draw function, stored in the DLG file. The draw dispatcher (DialogDraw) calls (**(draw_fn_ptr))(record_ptr) for each record. |
+0x0A |
i16 | x — horizontal position relative to dialog origin |
+0x0C |
i16 | y — vertical position relative to dialog origin |
Record types and sizes (from _DialogSetup switch):
| Type | Draw fn | Record size | Notes |
|---|---|---|---|
| 0 | _DrawAction |
0x26 (38) | Clickable button |
| 1 | — | 0x1F (31) | Button variant |
| 2 | _DrawEditBox |
0x18 (24) | Edit box; first type-2 record tracked as focused edit box in dialog state |
| 3 | — | 0x17 (23) | Checkbox / toggle |
| 4 | _DrawListBox |
0x26 (38) | Scrollable list container; anchor record ptr stored in dialog state +0x22 (_DrawMissList is the mission-list variant) |
| 5 | — | 0x19 (25) | Radio button (toggleable) — same field layout as type 1 + type_flags bit 0x8000 = selected |
| 6 | _DrawRocker |
0x27 (39) | Rocker switch; two independent hit zones (up and down halves) |
| 7 | — | 0x30 (48) | Scrollbar; DialogScrollThumbInit (0x4891A0) called on show for thumb-position init |
| 8 | — | 0x1F (31) | Two-state button (selected / deselected, each has its own hit zone) |
| 9 | _DrawText / _DrawFormattedText |
0x16 (22) | Static label / formatted text |
| 10 | — | — | End-of-list sentinel |
Note on the former +0x02..+0x09 gap: earlier analysis reported these bytes
as unused by draw functions. They are now fully explained:
- +0x02..+0x05: next_record_ptr — zeroed in the DLG file; engine-written
during _DialogSetup
- +0x06..+0x09: draw_fn_ptr — the thunk VA stored in the DLG file; draw
functions do not read their own address, hence appearing unused in
draw-function traces
Hit-test and event dispatch — confirmed (DialogUpdate)¶
The event dispatcher iterates records via next_record_ptr and calls
PointInBox(mouse_coords_ptr, bounding_box_ptr) for each record. The
bounding-box pointer offset differs by type because each type stores its
dimensions at a different place:
| Type | Hit-zone pointer |
|---|---|
| 0, 1, 3, 6 (up half), 8 (off→on zone) | record + 0x0E |
| 2 (edit box) | record + 0x0C |
| 4 (list container) | record + 0x0A |
| 6 (down half), 8 (on→off zone) | record + 0x16 |
| 7 (scrollbar) | FUN_00488fd0 (custom handler) |
_DialogWhatItem@0 (DialogWhatItem) returns the current value of
dialogItemPtr — a global pointer set to the last record that passed the hit
test during event dispatch. _TopCenterDialog (TopCenterDialog) positions the
dialog: screen_x = (screen_w − dialog_w) / 2,
screen_y = (screen_h − dialog_h) / 3.
Per-type record fields¶
All offsets below are from the record base; the common 10-byte header (+0x00..+0x09) is not repeated.
_DrawAction — type 0, 38 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0A |
i16 | x |
+0x0C |
i16 | y |
+0x0E |
i16 | screen_x — engine-managed; lazily written as dialog_x + x on first render |
+0x10 |
i16 | screen_y — engine-managed; lazily written as dialog_y + y on first render |
+0x12 |
i16 | render_width — engine-managed; written from width_px on first render |
+0x14 |
i16 | render_y_offset — engine-managed; constant 20 (0x14) written on first render |
+0x16 |
u32 | (engine-managed — viewport handle slot) |
+0x1A |
u32 | label_ptr — ptr to label string or icon resource |
+0x1C |
u32 | last_rendered_label — engine-managed; written after each render |
+0x1E |
u32 | icon_ptr — engine-managed; icon viewport ptr (set from actionBlueFont/4fca28/etc. on first render) |
+0x22 |
i16 | text_x — text x offset within button |
+0x24 |
i16 | text_y — text y offset within button |
Lazily initialized fields (+0x0E–+0x15, +0x1C) are zero in the DLG file; the engine writes them on the first render pass.
_DrawText — type 9, 22 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0A |
u32 | text_ptr — char* to label string |
+0x0E |
u32 | font_ptr — font override (0 → default PANELFNT/PANELFND, chosen from disabled flag) |
+0x12 |
i16 | x |
+0x14 |
i16 | y |
_DrawFormattedText — type 9 variant, 36 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0A |
i16 | x |
+0x0C |
i16 | y |
+0x0E |
i16 | width |
+0x10 |
i16 | height |
+0x12 |
i16 | secondary_display_x — x offset for secondary item display; −1 = disabled |
+0x14 |
i16 | secondary_display_y |
+0x16 |
i16 | visible_rows — items per page; written to 1 when edit-mode activates |
+0x18 |
i16 | result_val — engine-managed; stores text-scroll result ptr at render time |
+0x1A |
i16 | current_item |
+0x1C |
i16 | last_rendered |
+0x1E |
i16 | scroll_base — starting offset for secondary display |
+0x20 |
u32 | text_ptr — char** string array |
_DrawCampaignList — 36 bytes: same field layout as _DrawFormattedText.
Render logic differs: rows are campaign entries with a highlight sprite from
CAMPHI.PIC; row height is 0x4B px.
_DrawRocker — type 6, 39 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0A |
i16 | x |
+0x0C |
i16 | y |
+0x0E |
i16 | render_x — engine-managed; written as copy of x on first render |
+0x10 |
i16 | render_y — engine-managed; written as copy of y on first render |
+0x12 |
i16 | engine-managed — up-arrow icon offset A (18 or 16, based on size_flag) |
+0x14 |
i16 | engine-managed — up-arrow icon offset B (16 or 18) |
+0x16 |
i16 | engine-managed — left-state indicator x (= x on first render) |
+0x18 |
i16 | engine-managed — right-state indicator x (= x + offset) |
+0x1A |
i16 | engine-managed — down-arrow icon offset A (16 or 18) |
+0x1C |
i16 | engine-managed — down-arrow icon offset B (18 or 16) |
+0x1E |
i16 | current_value — 1 = up/left, else = down/right; updated from click_state after render |
+0x20 |
i16 | click_state — engine input: 0 = idle, 1 = click-up, else = click-down |
+0x22 |
u32 | parent_ref — ptr to linked parent control for auto-positioning |
+0x26 |
u8 | size_flag — non-zero = tall/large variant |
_DrawEditBox — type 2, 24 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0A |
i16 | char_count — field width in characters |
+0x0C |
i16 | y |
+0x0E |
i16 | x |
+0x10 |
i16 | pixel_width — engine-managed: char_count × 10 + 16; written at render time |
+0x12 |
i16 | height — always written as 24 (0x18) at render time |
+0x14 |
u32 | text_buffer — char* to editable text |
The six interactive types below were mapped from the DialogUpdate event switch
(case 1/3/4/5/7/8, keyed on type_flags & 0x7FFF), the _DrawListBox renderer, and the
scrollbar helpers (DialogScrollThumbInit 0x4891A0, DialogClampThumb 0x489220,
DialogScrollbarHit 0x488FD0). x/y at +0x0A/+0x0C follow the common pattern; the
hit-box fields are the render-coordinate rectangles passed to _PointInBox.
Type 1 — button variant (radio group member), 31 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0A |
i16 | x |
+0x0C |
i16 | y |
+0x0E |
i16×4 | hit box (_PointInBox) |
+0x16 |
u8 | pressed — set to 1 on click; cleared by DialogRadioGroupClear |
+0x17 |
i16 | radio_group — group id; peers with the same id are cleared on select |
Type 3 — checkbox / toggle, 23 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0A |
i16 | x |
+0x0C |
i16 | y |
+0x0E |
i16×4 | hit box (_PointInBox) |
+0x16 |
u8 | state — XOR-toggled (state ^= 1) on each click |
Type 4 — scrollable list container, 38 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0A |
i16×4 | hit box (_PointInBox); a click computes the row from the y within it |
+0x0C |
i16 | x |
+0x0E |
i16 | y |
+0x10 |
i16 | pixel_height — engine-managed; visible_rows × 0x12 |
+0x16 |
i16 | visible_rows — rows per page (row height 0x12 = 18 px) |
+0x18 |
i16 | engine scroll/selection state |
+0x1A |
i16 | total_items |
+0x1C |
i16 | top_index — top/selected row; 1000 sentinel = none |
+0x20 |
u32 | rows_ptr — char** row-string array |
Type 5 — radio button (toggleable), 25 bytes: same as type 1 (pressed +0x16,
radio_group +0x17), plus the type_flags bit 0x8000 carries the selected state — when
already selected, clicking calls DialogRadioGroupClear(radio_group) to release the group.
Type 7 — scrollbar, 48 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0B |
u8 | orientation — 0 = vertical, non-0 = horizontal (flips the value axis) |
+0x0C |
i16 | value — current scroll value (engine-written from thumb position) |
+0x0E |
i16 | value_max — value range (thumb-to-value divisor) |
+0x10 |
i16 | thumb_x — engine-computed thumb pixel X |
+0x12 |
i16 | thumb_y — engine-computed thumb pixel Y |
+0x14 |
i16 | track X reference (paired with +0x1C) |
+0x16 |
i16 | track Y reference (paired with +0x1E) |
+0x18 |
i16×4 | track rectangle = the hit box (x0=+0x18, y0=+0x1A, x1=+0x1C, y1=+0x1E) |
+0x24 |
u32 | on_scroll — callback invoked on a track hit (DialogScrollbarHit) |
Type 8 — two-state button, 31 bytes:
| Offset | Type | Field |
|---|---|---|
+0x0A |
i16 | x |
+0x0C |
i16 | y |
+0x0E |
i16×4 | hit box A — pressed while state == 0 → sets state = 1 |
+0x16 |
i16×4 | hit box B — pressed while state == 1 → sets state = 0 |
+0x1E |
u8 | state — 0/1; selects which hit zone is live |
JMP thunks and state dispatch table¶
At the end of the CODE section, each imported function has a 6-byte JMP thunk:
FF 25 [iat_va LE] ; JMP DWORD PTR [IAT slot]
Immediately before the thunk block there is a fixed 9-byte state machine dispatch table:
01 02 03 02 01 02 03 02 01
This same sequence appears in MUS CODE sections (after the FC opcode),
confirming it is a shared engine construct — not DLG-specific. The
draw_fn_ptr field (+0x06) in each dispatch record points to one of the JMP
thunks, identifying which draw function the record invokes.
_cancelString and _okString (button label indirection)¶
Records whose draw_fn_ptr points to the _cancelString or _okString thunk
do not embed a string directly. The label_ptr field holds the VA of the
thunk itself, which the engine dereferences at runtime to call the localized
label function.
CHOOSEAC.DLG decoded (main start screen)¶
Action-button records (type 0) in CHOOSEAC.DLG. Record addresses are PE virtual addresses within the CODE section.
| Record VA | x | y | width | Label |
|---|---|---|---|---|
| 0x1015 | 44 | 24 | 144 | Play Single Mission |
| 0x103B | 44 | 56 | 144 | Create Quick Mission |
| 0x1061 | 44 | 88 | 144 | Create Pro Mission |
| 0x1087 | 44 | 120 | 144 | Replay Last Mission |
| 0x10AD | 44 | 251 | 144 | Start New Campaign |
| 0x10D3 | 37 | 283 | 158 | Continue Old Campaign |
| 0x10F9 | 44 | 315 | 144 | View Pilot Records |
| 0x111F | 32 | 174 | 170 | Reference |
Records are spaced 0x26 (38) bytes apart, confirming type-0 record size. Y gap between 120 and 251 (131 px) separates mission-start options from management/info buttons.
_ChoosePreload header record¶
Every DLG begins with one _ChoosePreload record that initialises assets and
sets dialog state. In CHOOSEAC.DLG this record appears at PE VA 0x1000 with
params (379, 80, 238, 361).
Params decoded (Ghidra — MMAccessE DLG module descriptor getter, called
from _DialogSetup): the four i16 values are
(default_x, default_y, dialog_width, dialog_height) — loaded from the DLG
module's exported descriptor and stored in the dialog state frame:
- dialog_state +0x08/+0x0A = default screen position (x, y) — may be
overridden by _TopCenterDialog
- dialog_state +0x0C/+0x0E = dialog dimensions (width, height) — used by
_TopCenterDialog to compute centred position
For CHOOSEAC.DLG: x=379, y=80, w=238, h=361. _TopCenterDialog overrides
position to ((screen_w − 238) / 2, (screen_h − 361) / 3).
_ChoosePreload (ChoosePreload) confirmed behaviour (Ghidra):
1. Calls PushShellAlloc — pushes current screen state onto dialog stack,
sets state to 6
2. Calls FUN_00489840 (__fastcall char param_1) — loads action-button PIC
and font assets keyed by action_type:
- type 1: ACTDFDxx.PIC / ACTDFNxx.PIC (default, dim/normal); font LMR
- type 3: ACTI2Nxx.PIC / ACTI2Dxx.PIC; fonts fontact/fontacd
- type 4: ACTI3Nxx.PIC / ACTI3Dxx.PIC; same fonts
- else: ACTIONxx.PIC / ACTIODxx.PIC; same fonts
3. Decrements stack depth, pops screen state
_ChoosePreload is dispatched via computed indirect call — no direct CALL
references (confirmed by Ghidra reference scan).
File Inventory¶
Complete filename → screen mapping, derived from embedded label strings in the
.DLG PE data sections. All 92 live in FA_2.LIB.
Mission setup¶
| File | Screen |
|---|---|
| CHOOSEAC.DLG | Main start screen — Play Single Mission / Create Quick Mission / Create Pro Mission / Replay Last Mission / Start New Campaign |
| BRIEFSCR.DLG | Mission briefing paper screen |
| SNGLMISS.DLG | Single mission list picker |
| QUIKMISS.DLG | Quick mission dialog |
| LOADORD.DLG | Arm plane / ordnance loadout — Select Plane + weapons dial |
| ACFTSLEC.DLG | Aircraft selection sub-screen (Arm Plane / Brief Map tabs) |
| ACFTRPAR.DLG | Aircraft parameters dialog |
Quick Battle wizard (24 dialogs — one per wizard step)¶
| File | Prompt |
|---|---|
| QUICKB3.DLG | Choose nationality of friendly forces |
| QUICKB4.DLG | Select number of friendly pilots |
| QUICKB5.DLG | Choose skill of friendly forces |
| QUICKB6.DLG | Choose type of plane for friendly forces |
| QUICKB7.DLG | Choose altitude of friendly forces |
| QUICKB8.DLG | Choose map to fly over |
| QUICKB9.DLG | Choose time of day |
| QUICKB10.DLG | Choose weather conditions |
| QUICKB11.DLG | Choose advantage level over enemy |
| QUICKB12.DLG | Choose weapons (guns only / missiles+guns) |
| QUICKB13.DLG | Choose nationality of enemy forces |
| QUICKB14.DLG | Select number of enemy pilots — flight 1 |
| QUICKB15.DLG | Choose skill of enemy — flight 1 |
| QUICKB16.DLG | Choose plane type — enemy flight 1 |
| QUICKB17.DLG | Select number of enemy pilots — flight 2 |
| QUICKB18.DLG | Choose skill of enemy — flight 2 |
| QUICKB19.DLG | Choose plane type — enemy flight 2 |
| QUICKB20.DLG | Select number of enemy pilots — flight 3 |
| QUICKB21.DLG | Choose skill of enemy — flight 3 |
| QUICKB22.DLG | Choose plane type — enemy flight 3 |
| QUICKB23.DLG | Choose ground target |
| QUICKB24.DLG | Choose AAA defense strength |
| QUICKB25.DLG | Choose SAM defense strength |
| QUICK14.DLG | Quick mission theater / map selection list |
Campaign and pilot¶
| File | Screen |
|---|---|
| CAMPAIGN.DLG | Campaign list picker |
| SHWPILOT.DLG | Pilot roster screen — New Pilot / Delete / Copy Pilot / Select |
| CONTPLT.DLG | Continue with existing pilot — Delete / Copy Pilot / Select |
| VIEWPLT.DLG | View pilot record — Delete / Copy Pilot |
| AR_DLG.DLG | After-action report — General / Details / Videos / Photo Album / Parts List tabs |
| CALLSIGN.DLG | Choose callsign from list or enter custom |
| EDITNAME.DLG | Enter pilot name |
| EDITSIGN.DLG | Enter callsign |
| EDITSND.DLG | Enter callsign sound file (.5K or .11K) |
| EDITSQAD.DLG | Enter squadron name |
Mission Creator (MC) dialogs¶
| File | Prompt |
|---|---|
| MC_DLG.DLG | Mission Creator main options panel |
| MC_SCR.DLG | Set which screens player can access |
| MC_WETH.DLG | Set weather conditions |
| MC_TIME.DLG | Set time limit |
| MC_KILLS.DLG | Set kill count to end scenario |
| MC_KILLT.DLG | Set how kills end scenario (total / by side / by player) |
| MC_LIVES.DLG | Set number of revives |
| MC_DELAY.DLG | Set time delay before revive |
| MC_DIST.DLG | Set distance away after revive |
| MC_NAT.DLG | Assign nationalities to enemy side |
| MC_NAT2.DLG | Choose nationality of individual object |
| MC_NATF.DLG | Assign nationalities — full version (all sides) |
| MC_NAME.DLG | Enter pilot name (MC context) |
| PICKOBJ.DLG | Choose an object (mission editor object picker) |
| FORTAIRB.DLG | Multiplayer airbase — Deploy / Evacuate |
| FORTOPT.DLG | Multiplayer / fortification options |
Preferences and configuration¶
| File | Screen |
|---|---|
| GRAFPREF.DLG | Graphics preferences (640×480) |
| GRAF320.DLG | Graphics preferences (320×200) |
| SNDPREF.DLG | Sound preferences |
| SOUND320.DLG | Sound preferences (320×200) |
| AUDIOD.DLG | Audio device options |
| UCONFIGD.DLG | User configuration dialog |
Multiplayer — network¶
| File | Screen |
|---|---|
| NEWNET.DLG | New network session options |
| NETJOIN.DLG | Join network game — game list |
| NETNEW.DLG | Host new game — wait for players |
| NETDIR.DLG | Network directory — player name / address |
| NETIPX.DLG | IPX/SPX connection — answer / status |
| NETIPX2.DLG | IPX/SPX settings (default / custom) |
| NETTCP.DLG | TCP/IP settings |
| NETEDT.DLG | Network player entry edit |
| NETBEDT.DLG | NetBEUI / transport-B player edit |
| NETCEDT.DLG | Transport-C player edit |
Multiplayer — modem / serial¶
| File | Screen |
|---|---|
| MODEM.DLG | Modem connection — Answer / player name / phone |
| MODEMCOM.DLG | Modem AT command strings (init / dial / listen) |
| MODEMSTS.DLG | Modem connection status |
| MODLIST.DLG | Modem selection list |
| SERIAL.DLG | Serial / null-modem connection |
| COM.DLG | Communications dialog |
| COMLIST.DLG | Communications transport list |
Generic system dialogs¶
| File | Screen |
|---|---|
| INFO320.DLG | Generic info box — OK only (320×200) |
| INFO640.DLG | Generic info box — OK only (640×480) |
| INFO0320.DLG | Info variant 0 — OK only (320×200) |
| INFO0640.DLG | Info variant 0 — OK only (640×480) |
| INFO2320.DLG | Info variant 2 — OK + Cancel (320×200) |
| INFO2640.DLG | Info variant 2 — OK + Cancel (640×480) |
| INFO2642.DLG | Info variant 2 alternate — OK + Cancel (640×480) |
| INFOY320.DLG | Yes / No confirmation dialog (320×200) |
| MDIAG.DLG | Generic message dialog (references GrafPrefPreload) |
| CDIAG.DLG | Continue / Cancel dialog |
| DDIAG.DLG | Disconnect confirmation dialog |
| LISTTST.DLG | Developer test dialog (placeholder/lorem ipsum text) |
The CHOOSEAC.DLG labels are the top-level game start menu items — displayed
before any campaign or mission is active.
Open Questions¶
1. Record types 1, 3, 4, 5, 7, 8 field layouts¶
Mapped (#258). All ten record types
now have per-field layouts (see § Per-type record fields), recovered from the DialogUpdate
event switch, _DrawListBox, and the scrollbar helpers: types 1/5 are radio group members
(pressed +0x16, radio_group +0x17; type 5 also carries the selected state in
type_flags bit 0x8000), type 3 is a toggle checkbox (state +0x16), type 4 the scrollable
list container, type 7 the scrollbar (value/range/thumb/track + on_scroll callback), and type 8
a two-state button (two hit zones + state +0x1E).
The few unnamed interior bytes of the two largest records (type-4 list, type-7 scrollbar) are
runtime scratch — the DialogUpdate / _DrawListBox / scrollbar trace (#258)
recovered every field the engine reads from the file; the remaining bytes are written only at
runtime (selection/scroll work area) and carry no on-disk semantics, matching the scratch-field
level of the already-documented types. The per-type static layout is therefore complete.
Residual (out of #136 scope): the codec is still read-only — a byte-identical writer awaits the full on-disk per-record decode, whose layout differs from the in-memory layout mapped above. That larger structural pass is tracked under #54.
Related¶
Formats: MNU — top-level menu files that surface DLG dialogs.
Engine: architecture.md — the overlay DLL loading architecture; the fx_lib codec is tracked in #105.
FNT — Bitmap Font (.FNT)¶
FA_1.LIB contains 15 .FNT files. These define the bitmapped fonts used for
HUD text, menus, and briefing screens. Each is a Win32 PE DLL loaded at
runtime. File sizes vary — 4X12.FNT decompresses to 12800 bytes (0x3200);
the large size relative to other 4608-byte overlays reflects the glyph
function code covering the full printable ASCII range.
Tools¶
fx¶
fx fnt info <file.FNT> # font height and glyph metrics
fx fnt unpack <file.FNT> [-o dir] # render glyphs to glyph_sheet.png + metrics.csv
fx fnt pack <orig.FNT> <dir> [-o out.FNT] # recompile glyphs back into the DLL
Glyph extraction works by interpreting each glyph's x86 write pattern
against a pixel buffer; pack inverts it, recompiling edited glyph
bitmaps to x86 with the original compiler's canonical encoding (below) and
rebuilding the function table — an unedited unpack→pack loop is
byte-identical for every install font. Edited glyph code must fit the
original code region (the container is otherwise carried verbatim).
File Layout¶
All multi-byte integers are little-endian.
FNT files use Phar Lap PE format (signature PL\0\0). No imports. The
CODE section contains a FONT struct — a pointer table of compiled x86
glyph functions followed by a width table, then the glyph function bodies.
FONT struct¶
Starts at CODE section offset 0 (VA 0x1000). Confirmed from tracing
@G_Print@16 in the game executable (0x004986B0), which accesses the loaded FNT DLL via
the global ?cFont@@3PAUFONT@@A:
u32 font_height height of all glyphs in this font, in pixels
u32 glyph_fn[256] VAs of compiled glyph functions, one per ASCII 0–255
u32 glyph_width[256] advance width of each glyph in pixels
cFont[0]— font height (used in@G_Print@16clip bounds check)cFont[char + 1]— called as a function pointer:(*(code *)cFont[char + 1])(dst_ptr)cFont[char + 0x101]— advance width; passed to_G_Blit@36as the glyph width
Total struct: 1 + 256 + 256 = 513 u32 values = 2052 bytes minimum before
glyph bodies.
Glyph functions¶
Each glyph is a compiled x86 function, not encoded bitmap data.
@G_Print@16 calls each glyph function directly, passing the destination
framebuffer position in registers.
Confirmed calling convention (traced from @G_Print@16 at 0x004986B0 +
glyph body at raw file offset 0xA25):
| Register | Role |
|---|---|
EDI |
Current row pointer in destination framebuffer |
ECX |
Scanline stride (bytes per row) |
AL |
Pixel color value |
Instruction pattern:
| Sequence | Meaning |
|---|---|
03 F9 = ADD EDI, ECX |
Advance to next row |
88 07 / 88 47 NN = MOV [EDI(+NN)], AL |
1-pixel write at column 0 / NN |
66 89 07 / 66 89 47 NN = MOV [EDI(+NN)], AX |
2-pixel run write |
89 07 / 89 47 NN = MOV [EDI(+NN)], EAX |
4-pixel run write |
ADD EDI, ECX alone |
Skip a blank row (no pixel written) |
C3 = RET |
End of glyph |
G_Print primes AL/AX/EAX with the colour replicated per byte, so the
word/dword forms paint uniform horizontal runs. This vocabulary is
complete: every glyph function of all 15 install fonts decodes with it
(#97 census, 3,840 bodies).
Canonical encoding (confirmed — all 3,840 install bodies re-emit
byte-identically): per pixel row, ascending runs split greedily into
4/2/1-pixel writes (dword, then word, then byte; the short [EDI] forms
whenever the column is 0), followed by one row advance; a glyph therefore
carries exactly font_height advances — writes for row r appear before
the (r+1)-th advance, so a lit top row writes before the first ADD. An
all-blank glyph is a lone RET with no advances. Bodies are laid out
back-to-back in character order (0–255, one body per character, no sharing
and no gaps), starting immediately after the FONT struct.
0xC3 = RET — the blank/space glyph is a single-byte function that
returns immediately, writing nothing. Control characters (ASCII 0–31) and
space (ASCII 32) all point to 0xC3 bytes (VA 0x1804–0x1824).
Printable character functions begin at VA 0x1825 (raw file offset 0xA25).
Confirmed disassembly of ASCII 33 (!):
ADD EDI, ECX ; row 0 blank — advance
MOV [EDI], AL ; row 1 — lit (bar)
ADD EDI, ECX
MOV [EDI], AL ; row 2 — lit
ADD EDI, ECX
MOV [EDI], AL ; row 3 — lit
ADD EDI, ECX
ADD EDI, ECX ; row 4 blank — advance only
MOV [EDI], AL ; row 5 — lit (dot)
ADD EDI, ECX
ADD EDI, ECX ; row 6 blank (inter-line spacing)
RET
(An earlier revision put each write before its label's advance; the #97
census fixed the row accounting: writes belong to the row preceding the
next advance, and row-0 writes come before any ADD.)
7 row advances for a font named 4X6 confirms cFont[0] (font height) = 7 —
the cell is 6 glyph rows + 1 inter-line spacing row. The raw bytes
{03 F9 88 07} are two x86 instructions, not an encoded bitmap format.
Ghidra navigation note: when importing a FNT file as a raw binary with
base 0x1000, the CODE section (file offset 0x200) appears at Ghidra
address 0x1200. Loaded VAs (e.g. 0x1825) correspond to Ghidra address
0x1000 + file_offset = 0x1000 + (VA - 0x1000 + 0x200) = VA + 0x200.
$$DOSX metadata¶
The $$DOSX section (512 bytes) contains a small header. Both 4X6.FNT and
4X12.FNT show identical $$DOSX values (u16[4]=16, u16[5]=6). These are
system constants shared by all FNT files, not per-file cell dimensions.
File Inventory¶
font_height confirmed by reading the dword at CODE section offset 0 (file
offset 0x200) for all 15 files.
| File | font_height | Decompressed size | Context |
|---|---|---|---|
4X6.FNT |
7 | 8704 | Tiny fixed-pitch text (4 wide × 6 glyph rows + 1 spacing = 7) |
4X12.FNT |
12 | 12800 | Fixed-pitch 4×12 text |
HUD00.FNT |
5 | 8704 | HUD text — 320×200 mode |
HUD01.FNT |
10 | 12800 | HUD text — hi-res mode A |
HUD11.FNT |
10 | 12800 | HUD text — hi-res mode B |
HUDSYM00.FNT |
15 | 12800 | HUD symbols — 320×200 mode |
HUDSYM01.FNT |
29 | 16896 | HUD symbols — hi-res mode A |
HUDSYM11.FNT |
31 | 20992 | HUD symbols — hi-res mode B |
HUI11.FNT |
10 | 12800 | HUD interface text |
HUISYM11.FNT |
31 | 16896 | HUD interface symbols |
MAPFONT.FNT |
10 | 12800 | Theater map labels |
WII11.FNT |
10 | 12800 | Window interface text |
WIN00.FNT |
6 | 12800 | Window/dialog text — 320×200 mode |
WIN01.FNT |
12 | 16896 | Window/dialog text — hi-res mode A |
WIN11.FNT |
10 | 12800 | Window/dialog text — hi-res mode B |
All 15 live in FA_1.LIB.
Suffix semantics confirmed: 00 = 320×200 (small font, short
font_height), 01/11 = higher-resolution display modes (larger
font_height). The 01 vs 11 distinction is inferred to target different
colour depths or renderer paths — both are hi-res but 01 fonts are taller
than their 11 counterparts for the WIN/HUDSYM families.
Related¶
Formats: PIC — 8-bit indexed bitmaps, also in FA_1.LIB;
HUD — HUD layouts that consume font data (hudsym, winfont
references).
HUD — Heads-Up Display Layout (.HUD)¶
FA_2.LIB contains 46 .HUD files — one per aircraft type (e.g. A7.HUD,
F22.HUD). Each defines the cockpit HUD layout for that aircraft. Each is a
Win32 PE DLL loaded at runtime; all observed files decompress to 4608
bytes.
Tools¶
fx¶
fx hud dump <file.HUD> # gauge parameter table and sprite name references
fx hud set <file.HUD> <gauge.field=value ...> [-o out] # edit gauge params / icon_a..icon_d labels
File Layout¶
All multi-byte integers are little-endian.
HUD files use Phar Lap PE format (signature PL\0\0). The CODE section is
a pure data structure — no imports, no dispatch table, no x86 code. The
engine loads HUD assets by name at runtime. All HUD files have identical CODE
virtual size (0x2BB = 699 bytes), confirming a fixed-size struct regardless
of aircraft.
String analysis of F22.HUD and B2.HUD reveals the asset reference pattern:
| String | Role |
|---|---|
~f22 / ~b2 |
Aircraft 3D model reference |
~f22h / ~b2h |
HUD overlay image (heads-up display graphic) |
~f22s / ~b2s |
HUD symbol set |
hudsym |
HUD symbol font (HUDSYM*.FNT) |
GEAR, FLAP, BRAKE |
Indicator label strings |
~f22_p / ~b2_p |
Aircraft propulsion/engine panel reference |
~f22_w / ~b2_w |
Weapons panel reference |
winfont |
Window font (WIN*.FNT) reference |
The ~ prefix indicates LIB-resident asset references. The HUD DLL binds its
aircraft-specific assets at load time using these names.
String layout (A7.HUD)¶
| VA | String | Role |
|---|---|---|
| 00001001 | ~a7 |
Aircraft model base name |
| 0000100E | ~a7h |
High-AoA/hook variant |
| 0000101B | ~a7s |
Speed-brake variant |
| 00001038 | hud |
HUD identifier |
| 00001051 | hudsym |
HUD symbol set |
| 0000113C–000011A4 | ~a7_l/c/r, ~a7_lh/ch/rh, ~a7_ls/cs/rs |
Left/centre/right sub-panel states |
| 00001245 | GEAR, FLAP, BRAKE, HOOK |
Warning indicator labels |
| 00001275 | ~a7_p, ~a7_w |
Engine/weapons panel sprites |
| 00001297 | winfont |
Window font reference |
Sub-panel sprite suffix semantics — confirmed¶
| Suffix pattern | Meaning |
|---|---|
_l / _c / _r |
Left / centre / right panel — normal state |
_lh / _ch / _rh |
Left / centre / right panel — high-AoA state |
_ls / _cs / _rs |
Left / centre / right panel — stowed/small state |
F22.HUD omits all _l/c/r sprites entirely — the F22 has no separate
sub-panels. It also uses BAY (weapons bay indicator) instead of HOOK.
Gauge parameter layout — confirmed¶
All gauge positions are stored as signed s16 offsets from the HUD anchor
point. Confirmed by tracing the HUD draw functions (HUDInit,
?HUDDrawHeading, ?HUDDrawSpeed, ?HUDDrawAlt, HUDDrawHVel,
?HUDDrawWeaponInfo, ?HUDDrawRangeInfo) in the game executable via Ghidra.
After loading, the struct is resident at hud. Field offsets within the
copied struct:
| Struct offset | Global | Gauge | Field |
|---|---|---|---|
0x1E1 |
DAT_00521541 |
Heading tape | width (pixels) |
0x1E5 |
DAT_00521545 |
Heading tape | dy from anchor |
0x1ED |
DAT_0052154d |
Heading tape | tick spacing (dy) |
0x1F7 |
DAT_00521557 |
Speed tape | dx from anchor |
0x1FB |
DAT_0052155b |
Speed tape | dy from anchor |
0x1FF |
DAT_0052155f |
Speed tape | height (pixels) |
0x209 |
DAT_00521569 |
Speed tape | tick increment |
0x214 |
DAT_00521574 |
Altitude tape | dx from anchor |
0x218 |
DAT_00521578 |
Altitude tape | dy from anchor |
0x21C |
DAT_0052157c |
Altitude tape | height (pixels) |
0x226 |
DAT_00521586 |
Altitude tape | tick increment |
0x231 |
DAT_00521591 |
Flight path marker | dx from anchor |
0x233 |
DAT_00521593 |
Flight path marker | dy from anchor |
0x235 |
DAT_00521595 |
Flight path marker | box half-width |
0x237 |
DAT_00521597 |
Flight path marker | box half-height |
0x238 |
DAT_00521598 |
Unknown | No cross-references found |
0x239 |
DAT_00521599 |
Lock indicator flag A | 3-state lock display; checked against missile+0xa6 & 0x10 |
0x23A |
DAT_0052159a |
Lock indicator flag B | Paired with A; selects state 5 (no lock) vs 6 (partial) |
0x23B |
DAT_0052159b |
HUD center dot enable | Non-zero: draw center pip and radar velocity vector |
0x23C |
DAT_0052159c |
ECM bar enable | Non-zero: enables ECM/threat bar draw (FUN_00408c8b) |
0x23D |
DAT_0052159d |
Active-lock threat enable | Combined with 0x23C for active-lock threat bar |
0x23E |
DAT_0052159e |
HVel altitude max | i16; HVel indicator (HUDDrawHVel) hidden above this altitude; also radar lock altitude gate |
0x240 |
DAT_005215a0 |
Lead indicator enable | Non-zero: draw lead angle / velocity prediction overlay |
0x241 |
DAT_005215a1 |
Score indicator dx | i8; X offset for score/fuel threshold indicator (FUN_004078b0) |
0x243 |
DAT_005215a3 |
Score indicator dy | i8; Y offset for score/fuel threshold indicator |
0x245 |
DAT_005215a5 |
Advisory icon C | 8-byte icon data; drawn when DAT_0050cfef & 0x040 |
0x24D |
DAT_005215ad |
Advisory icon A | 8-byte icon data; drawn when DAT_0050cfef & 0x100; != ' ' → advisory active |
0x255 |
DAT_005215b5 |
Advisory icon B | 8-byte icon data; drawn when DAT_0050cfef & 0x080 |
0x25D |
DAT_005215bd |
Advisory icon D | 8-byte icon data; drawn when DAT_0050cfef & 0x200 or (& 0x400) && (DAT_0050d322 & 2) |
0x265 |
DAT_005215c5 |
Warning lights | dx from anchor (A7=65, F22=70) — FUN_00407930 |
0x267 |
DAT_005215c7 |
Warning lights | dy from anchor (A7=−38, F22=−46) |
0x269 |
DAT_005215c9 |
Throttle/engine readout | dx from anchor (A7=−65, F22=−70) — FUN_00407a00 |
0x26B |
DAT_005215cb |
Throttle/engine readout | dy from anchor (A7=−38, F22=−46) |
0x26D |
DAT_005215cd |
Weapon info | dx from anchor |
0x26F |
DAT_005215cf |
Weapon info | dy from anchor |
0x271 |
DAT_005215d1 |
Range info | dx from anchor |
0x273 |
DAT_005215d3 |
Range info | dy from anchor |
Advisory icon names (label strings embedded in the HUD file, order confirmed
from A7.HUD string block at VA 0x00001245):
| Icon | Bit | Struct offset | Label in A7.HUD | Label in F22.HUD | Subsystem |
|---|---|---|---|---|---|
| A | 0x100 |
+0x245 |
GEAR |
GEAR |
FMFlaps — gear actuator (input 0x66) |
| B | 0x080 |
+0x24D |
FLAP |
FLAP |
FMBrakes — flap actuator (input 0x62) |
| C | 0x040 |
+0x255 |
BRAKE |
BRAKE |
FMGear — speedbrake actuator (input 0x67) |
| D | 0x200/0x400 |
+0x25D |
HOOK |
BAY |
FMBay (tailhook, input 0x6f) / FMHook (bay door, input 0x68) |
Engine Notes¶
Loading mechanism¶
HUDInit (HUD init, called at aircraft load time):
1. Loads the HUD DLL by name via RMAccess
2. Bulk-copies the entire CODE section (0xAC dwords + 2 bytes = 690 bytes)
to hud
3. Scales every gauge parameter left by the display xscale/yscale factor
(bVar3/bVar4)
The anchor point (DAT_00521d94/DAT_00521d96) is not read directly from
the HUD file — it is computed dynamically at runtime via a smooth-follow
interpolation that tracks the player aircraft's screen position.
DAT_0050cfef Advisory/State Flags — confirmed¶
DAT_0050cfef is the HUD state flags word. Bits are set by the game's
subsystems at each simulation tick; FUN_00407930 and the tape render
functions read them to gate icon and display variants.
| Bit | Hex | Source function | Meaning |
|---|---|---|---|
| 0 | 0x00001 |
_DAMAGEDoHit@12 damage state 0x50d3ff |
Aircraft damage indicator level 1 |
| 1 | 0x00002 |
_DAMAGEDoHit@12 damage state 0x50d400 |
Aircraft damage indicator level 2 |
| 2 | 0x00004 |
_DAMAGEDoHit@12 damage state 0x50d401 |
Aircraft damage indicator level 3 |
| 3 | 0x00008 |
_DAMAGEDoHit@12 damage state 0x50d3f7 (also clears bit 5) |
Aircraft damage / engine-out state — cleared bit 5 indicates afterburner disabled by damage |
| 4 | 0x00010 |
_DAMAGEDoHit@12 damage state 0x50d40c |
Aircraft damage indicator level 4 |
| 5 | 0x00020 |
FUN_00407a00; cleared by _DAMAGEDoHit@12 state 0x50d3f7 |
Aircraft has afterburner AND throttle is at max (DAT_0050d06e == 0x6400) — shows "THR: AFT" instead of numeric throttle |
| 6 | 0x00040 |
HUDDrawSpeed, HUDDrawAlt, FUN_00407930; set/cleared by FMGear (speedbrake actuator) via input 0x67 |
Advisory icon C active — speedbrake deployed; speed tape swaps live reference marker to approach-speed source (DAT_0050d3aa); altitude tape draws approach-altitude bracket markers (DAT_0050d0aa, DAT_0050d3ae) |
| 7 | 0x00080 |
FUN_00407930; set/cleared by FMBrakes (flap actuator) via input 0x62 |
Advisory icon B active — flap deployed |
| 8 | 0x00100 |
FUN_00407930; set/cleared by FMFlaps (gear actuator) via input 0x66 |
Advisory icon A active — landing gear deployed (down) |
| 9 | 0x00200 |
FUN_00407930; set/cleared by FMBay (tailhook actuator) via input 0x6f |
Advisory icon D active (single-player path) — tailhook deployed |
| 10 | 0x00400 |
FUN_00407930; set/cleared by FMHook (bay-door actuator) via input 0x68 |
Advisory icon D active (multiplayer path, also requires DAT_0050d322 & 2) — weapon bay door / hook variant open |
| 11 | 0x00800 |
HARDSetFlags (weapon-state scan, each tick) |
Active weapon lock — at least one weapon has ammo and an acquired lock |
| 12 | 0x01000 |
FUN_00407a00; toggled by SetAutopilot via input 0x61 |
Flight-lock / autopilot active — replaces throttle/G readout with lock sprite (DAT_004ebf94) |
| 13 | 0x02000 |
FlightKey case 0x61 (autopilot key handler) |
Autopilot ILS/ACLS sub-mode — set alongside bit 12 when flight mode is 6 and aircraft has ACLS capability (PT+0xe9 ≠ 0); gates carrier-approach glide-slope computation |
| 14 | 0x04000 |
?MPReceive@@YGDXZ (0x46C980 → the 8.6 KB packet handler FUN_0046c98f, writes at 0x46db2b) in multiplayer. In single-player the flags word is written wholesale by the state-transition wrappers FUN_004bc177/FUN_004bc190 (DAT_0050cfef = EAX; @EnterState@4) — the value is computed by the caller, which is why no OR [mem], 0x4000 constant exists. Read in ServicePlayer during ejection states 0x11/0x12 in conjunction with DAT_0050d0b1 (nearest entity pointer) and entity+0xFB range comparison; also gates aerodynamic integrator reset (stickX/ec, rudder). |
Runtime-set — a network-synced or proximity-alert advisory state whose exact single-player trigger is re-gameplay (see Open Questions) |
| 15 | 0x08000 |
PLANECheckFuel via FMUpdatePlaneFields (fuel monitor, runs every 5 ticks) |
Bingo fuel threshold reached — @SAYLowFuelMessage@8 checks (0x8000 set) && (0x80000 clear) → plays "Bingo fuel" voice line, then sets bits 19–20 as inhibit |
| 16 | 0x10000 |
PLANECheckFuel via FMUpdatePlaneFields |
Joker fuel threshold reached — plays "Joker fuel" voice line, sets bit 20 inhibit |
| 17 | 0x20000 |
PLANECheckFuel via FMUpdatePlaneFields |
Running on fumes threshold reached — plays "Running on fumes" voice line, sets bits 19–21 inhibit |
| 18 | 0x40000 |
PLANECheckFuel via FMUpdatePlaneFields |
Out of fuel threshold reached — plays "We're out of gas / I'm out of fuel" voice line, sets bits 19–22 inhibit |
| 19 | 0x80000 |
set by @SAYLowFuelMessage@8 when Bingo or higher voice line plays |
Bingo voice line played (inhibit) — prevents replaying |
| 20 | 0x100000 |
set by @SAYLowFuelMessage@8 when any fuel warning voice line plays |
Joker/any-warning voice line played (inhibit) |
| 21 | 0x200000 |
set by @SAYLowFuelMessage@8 when Fumes or Out-of-fuel line plays |
Fumes voice line played (inhibit) |
| 22 | 0x400000 |
set by @SAYLowFuelMessage@8 when Out-of-fuel line plays |
Out-of-fuel voice line played (inhibit) |
| 28 | 0x10000000 |
HUDDrawSpeed, HUDDrawAlt; set by _DAMAGEDoHit@12 state 0x50d40e |
Classified / redacted display — speed tape substitutes string at 0x004ebfbc; altitude tape substitutes s_XXXXX_004ebfd8 ("XXXXX"); tape tick-mark rendering skipped entirely; also triggered by aircraft-out-of-control damage state |
| 29 | 0x20000000 |
_DAMAGEDoHit@12 damage state 0x50d410 |
Emergency state — aircraft critical / imminent crash indicator |
| 30 | 0x40000000 |
_DAMAGEDoHit@12 damage state 0x50d40f (conditional on Rand(3) result); also read by carrier HGR renderer (AnalyzeHGR.txt lines 4143/4315): when set, suppresses normal slot-dot rendering and fixes the approach-angle indicator at position 2 (of 0–9) instead of computing the live angle from DAT_0050ce9f |
Emergency state variant A — aircraft spinning / uncontrolled flight |
| 31 | 0x80000000 |
_DAMAGEDoHit@12 damage state 0x50d40f (conditional on Rand(3) result) |
Emergency state variant B — aircraft spinning / uncontrolled flight (alternate roll) |
The command dispatcher is FlightKey. Each input case passes
(current_bit == 0) to the actuator, which deploys the surface when TRUE (bit
clear = currently retracted) and retracts it when FALSE (bit set = currently
deployed). The actuator function updates the 3D model state and writes the
advisory bit.
Round-Trip Notes¶
hud_repack (#99) rebuilds a HUD DLL around edited gauge parameters and
advisory icon labels. The write path re-emits only what the parser models:
- Gauge parameters — written back at their fixed struct offsets, with range checks for the s8/u8 fields. All known fields must be supplied exactly once; unknown names reject rather than silently no-op.
- Advisory icon labels — written into their fixed 8-byte slots with a terminating NUL when shorter than the slot (mirroring the reader, which stops at a NUL or 8 bytes); slot bytes past the NUL carry over.
- Everything else — PE headers, asset-string regions, and unmodelled
struct bytes carry over verbatim. Asset strings are informational in
HudFileand not editable through the repack.
An unedited parse→repack is therefore byte-identical; proven per-overlay
over all 46 install HUDs (tests/test_hud.cpp, FX_FA_ROOT census).
Open Questions¶
1. Struct byte +0x238 and state-flag bit 14¶
Static analysis is exhausted on both; the residuals are runtime observations.
+0x238(DAT_00521598) — a fresh cross-reference scan confirms zero static references anywhere in the game executable. No instruction reads or writes the absolute address, so its role (if any) can only be confirmed by watching the byte in the running game.- HUD flag bit 14 (
0x04000) — the write mechanism is now identified: the multiplayer writer is the 8.6 KB packet handlerFUN_0046c98f(entry?MPReceive@@YGDXZ0x46C980, store at0x46db2b); in single-player the flags wordDAT_0050cfefis rebuilt wholesale by the state-transition wrappersFUN_004bc177/FUN_004bc190(DAT_0050cfef = EAX; @EnterState@4), so the bit is carried in a caller-computed value rather than set by a constantOR— which is why the constant search found nothing. The precise single-player state that raises bit 14 is a runtime-state question.
Status: static exhausted — re-tagged re-gameplay (#56) for the Phase 6 bench.
Related¶
Formats: BRF — .PT aircraft type records the HUD pairs with;
FNT — fonts used to render HUD text elements; PIC —
bitmap assets used for HUD graphical elements.
MNU — Menu Screen Layout (.MNU)¶
FA_2.LIB contains 12 .MNU files. Each defines one top-level in-game menu
screen. All are Win32 PE DLLs (MZ stub + PE32 image) loaded by the FA
engine at runtime; they import rendering functions from main.dll (= the game executable —
see architecture.md) and
embed their label strings directly in the PE data section.
Tools¶
fx¶
fx mnu info <file.MNU> # container check + CODE section geometry
fx mnu strings <file.MNU> [-n MIN] # embedded menu label strings
Same MZ + Phar Lap PL container family as CAM (verified against
MAINMENU.MNU); the codec delegates to the shared PL reader.
File Layout¶
All multi-byte integers are little-endian.
Win32 PE DLL (MZ DOS stub + PE32 image). All MNU files import rendering
functions from main.dll (= the game executable):
| Import | Role |
|---|---|
_DrawAction |
Clickable action button |
_DrawRocker |
Toggle/rocker control |
_DrawText |
Static text label |
Label strings are embedded as null-terminated ASCII in the PE data section.
The record/layout encoding that arranges them into menu trees has not been
mapped — see Open Questions (the confirmed DLG dispatch-table
layout is the obvious starting hypothesis, since both formats share the
_Draw* import family).
File Inventory¶
| File | Screen |
|---|---|
| AR_MENU.MNU | Aircraft reference browser |
| ARMPLANE.MNU | Mission loadout / arm aircraft |
| CHOOSEM.MNU | Mission type selection + preferences |
| FMENUD.MNU | In-flight pause menu (full option tree) |
| MAINMENU.MNU | Minimal campaign action bar |
| MB_MENU.MNU | Mission briefing map controls |
| MC_MENU.MNU | Mission creator / editor |
| MULTI.MNU | Multiplayer lobby |
| QM_MENU.MNU | Quick mission setup |
| SELMENU.MNU | Aircraft / campaign selection bar |
| SM_MENU.MNU | Single mission aircraft filter |
| V_MENU.MNU | Vehicle reference browser |
All 12 live in FA_2.LIB.
Menu Labels by File¶
AR_MENU.MNU — Aircraft Reference Browser. Object category filter: Fighters, Bombers, Helicopters, SAMs, Tanks, Ships, Other vehicles, Structures, Missiles, Misc. Pagination: Next Page (PgDn), Prev Page (PgUp). Display: Show background in 3D view, H3D Eyewear toggle, 3D effect depth controls (Ctrl-=, Ctrl-[, Ctrl-]).
ARMPLANE.MNU — Mission Loadout Screen. Weapons, Unload All, Cheat (load anything anywhere). Navigation: Airbase, Next Aircraft, Previous Aircraft. Campaign: Campaign, Replay This Mission, Exit Campaign.
CHOOSEM.MNU — Mission Selection + Preferences. Mission types: Airbase Assault (plus others surfaced via DLG dialogs). Graphics prefs: Screen resolution — 320×200, 640×480, 800×600, 1024×768. Network: Serial, Modem, IPX/SPX Network, TCP/IP Network, Disconnect.
FMENUD.MNU — In-Flight Menu. End mission (Ctrl-Q), Exit to Windows (Alt-F4).
- Control: Stick: Keyboard, Joystick, CH F-16 Flight Stick, CH F-16 Combat Stick, CH Flightstick Pro, Jane's Combat Stick, Microsoft Sidewinder 3D Pro. Rudder: Keyboard, Rudder pedals. Throttle: Keyboard, Throttle stick, Slews view, Vectors thrust.
- Pref → Graphics: HUD pitch ladder, Dim/Brighten HUD (Shift-[/]), cockpit, rear-view mirrors, large windows, authentic radar CRT, target info (Ctrl-T), IR/Laser targeting, radio silence (Alt-S).
- Pref → Time: Paused (Ctrl-P), Slow-motion (Shift-C), Accelerated time.
- View: Front, Back, Track, Player↔Missile, Player↔Wingman, Player↔Target, Target↔Player, Fly-by, External, Missile; View transitions toggle.
- Window: Envelope, Forward/IR-Laser (Shift-2), Other (Shift-3), Target/Radar (Shift-4/5), Navigation (Shift-6), System Status (Shift-7), Weapon Status (Shift-8), Radar (Shift-9), Radar Cross Section (Shift-0).
- Cheat: Damage level (Invulnerable/Normal/Realistic), Unlimited ammo, Unlimited fuel, Easy aiming, No crashes, No spins, No turbulence, Pull extra G, Ignore weapon weights, No sun/redout/blackout/screen-shake.
- AI: Enemy AI (Novice/Average/Unchanged), Ignore midair collisions, Easy targeting, Air combat guns only.
- Show: Planes, SAM sites, AAA sites, Ships, Airports, Vehicles, Other, SAM threat ranges; altitude presets (Ready for takeoff, Final approach, 10,000 ft, 40,000 ft).
- Multi: Reduce bullet/missile accuracy/damage, reduce engine thrust/radar look-down, weapon camera, player scores, display windows, pauses flight.
MAINMENU.MNU — Campaign Action Bar. Campaign, Replay This Mission, Exit Campaign — minimal bar overlaid on certain screens.
MB_MENU.MNU — Mission Briefing Map. Scroll (Left/Right/Up/Down), Center map at cursor/selection, Zoom in/out/Smart zoom. Waypoint: Delete, Create loop, Delete loop, Select prev/next waypoint. Show: Planes, SAM sites, AAA sites, Ships, Airports, Vehicles, Other, Mission items only, SAM threat ranges, Distance grid.
MC_MENU.MNU — Mission Creator / Editor. File: New mission (Ctrl-N), Load mission, Save mission. View: same scroll/zoom controls as MB_MENU. World: Set map, Set weather, Set friendly & enemy sides, Set screens; Friendly/Enemy Pilot/SAM skill (Novice/Average/Good/Expert). Object: Duplicate, Delete; Add/Remove from wing (Blue/Green/Black/White/Orange/ Purple/Yellow), Make wingleader; Add/Remove from group, Make groupleader. Waypoint: same controls as MB_MENU. Multiplayer: Time limit, Number of kills, End scenario conditions, Number of revives, Revive time delay, Revive distance. Aircraft era filter: Fly All, 1956-1976, 1956-1982, 1956-1996, 1956-Future.
QM_MENU.MNU / SM_MENU.MNU — Quick/Single Mission. Aircraft era filter: Fly all, 1956-1976, 1956-1982, 1956-1996, 1956-Future.
SELMENU.MNU — Selection Bar. Cheat: Allow Flying Any Plane. Campaign: Replay This Mission, Exit Campaign.
V_MENU.MNU — Vehicle Reference Browser. Same category structure as AR_MENU plus Weapons category.
Open Questions¶
1. Menu-tree / control layout encoding¶
Only the import surface and the embedded label strings are mapped. The CODE section structure that arranges labels into menu trees, binds shortcuts, and positions controls is undecoded — the confirmed DLG dispatch-table record format is the natural hypothesis to test first.
Status: open — re-static (#54)
Related¶
Formats: DLG — dialog box overlays nested within menus; shares
the _Draw* import family and likely the record encoding.
PTS — Aircraft Screen Assets (.PTS)¶
FA_2.LIB contains 37 .PTS files (e.g. A4E.PTS, F22.PTS). Each supplies
the screen asset references for one aircraft type — primarily the aircraft
icon shown in the hangar and selection screens. Each is a Win32 PE DLL
loaded at runtime; all observed files decompress to 4608 bytes.
(Unrelated to the community .PTS distribution rename of SH shadow shapes —
see the note in SH.md.)
Tools¶
fx¶
fx pts info <file.PTS> # container check + referenced icon PIC
Same MZ + Phar Lap PL container family as CAM/MNU
(verified against F22.PTS); pts_info extracts the one icon reference.
All 37 shipped files resolve to the inventory below.
File Layout¶
All multi-byte integers are little-endian.
Win32 PE DLL. String analysis confirms each .PTS file references exactly
one PIC asset — the aircraft icon. No .HUD, .FNT, or .5K/.11K
references are present in any .PTS file; those assets are loaded directly by
the cockpit and HUD subsystems at flight time.
The naming pattern ICON<AC>.PIC is consistent; some aircraft share an icon
(e.g. F22N.PTS reuses ICONF22.PIC, all ASTOVL variants share
ICONAST.PIC).
File Inventory¶
| PTS file | Icon PIC | Notes |
|---|---|---|
| A4E.PTS | ICONA4E.PIC | |
| A7.PTS | ICONA7.PIC | |
| A7V.PTS | ICONA7.PIC | shares A7 icon |
| AC130.PTS | ICONC130.PIC | |
| ASTOVL.PTS | ICONAST.PIC | |
| ASTOVLE.PTS | ICONAST.PIC | shares ASTOVL icon |
| ASTOVLF.PTS | ICONAST.PIC | shares ASTOVL icon |
| ASTOVLV.PTS | ICONAST.PIC | shares ASTOVL icon |
| AV8.PTS | ICONAV8.PIC | |
| B2.PTS | ICONB2.PIC | |
| E2000.PTS | ICONE20.PIC | |
| F104.PTS | ICONF104.PIC | |
| F117.PTS | ICONF117.PIC | |
| F14.PTS | ICONF14.PIC | |
| F16C.PTS | ICONF16.PIC | |
| F18.PTS | ICONF18.PIC | |
| F22.PTS | ICONF22.PIC | |
| F22N.PTS | ICONF22.PIC | shares F22 icon |
| F29.PTS | ICONF29.PIC | |
| F31.PTS | ICONF31.PIC | |
| F31E.PTS | ICONF31.PIC | shares F31 icon |
| F31F.PTS | ICONF31.PIC | shares F31 icon |
| F31V.PTS | ICONF31.PIC | shares F31 icon |
| F4B.PTS | ICONF4B.PIC | |
| F4J.PTS | ICONF4J.PIC | |
| F8J.PTS | ICONF8J.PIC | |
| GRIPEN.PTS | ICONGRI.PIC | |
| MIG17F.PTS | ICONM17F.PIC | |
| MIG21.PTS | ICONM21.PIC | |
| MIG21F.PTS | ICONM21F.PIC | |
| RAFALE.PTS | ICONRAF.PIC | |
| RAFALEE.PTS | ICONRAF.PIC | shares RAFALE icon |
| SEAHAR.PTS | ICONSEA.PIC | |
| SU33.PTS | ICONSU33.PIC | |
| SU35.PTS | ICONSU35.PIC | |
| YAK141.PTS | ICONY141.PIC | |
| ~MOTH.PTS | II~MOTH.PIC | campaign variant moth icon |
All 37 live in FA_2.LIB.
Coverage: 37 .PTS files vs 145+ .PT aircraft flight model files — most
aircraft share a generic icon, and only those 37 have a dedicated .PTS
entry. Variants of the same aircraft (ASTOVLE/F/V, F31E/F/V, RAFALEE, A7V)
typically share their base aircraft's icon.
Related¶
Formats: BRF — the .PT aircraft flight model records (one per
aircraft); PIC — the ICON<AC>.PIC aircraft icon images;
HUD — cockpit HUD definitions, loaded separately by the HUD
subsystem (not referenced from .PTS).
System & Config
BIN — Binary Lookup Tables (.BIN)¶
FA_2.LIB contains 6 .BIN files. All are flat lookup tables used by the
engine's color blending and insignia systems.
Tools¶
fx¶
fx bin info <file.BIN> # kind (from the filename) + documented-size check
The bytes carry no self-describing structure, so fx bin info classifies by
entry name (bin_classify) and verifies the size against the inventory
below; the fxs BIN editor shows the same classification above its hex view.
File Layout¶
Single-byte tables; no multi-byte integers.
MIX Tables (MIX2, MIX2L, MIX4, MIX4L)¶
Used for palette-mode transparency and color blending. When the engine blends two 8-bit palette indices, it sums their intensity values and uses a MIX table to look up the resulting index.
- MIX2L — 512-byte table;
output[i] = floor(i / 2). Exact 50% linear reduction. - MIX4L — 1024-byte table;
output[i] = floor(i / 4). Exact 25% linear reduction. - MIX2 — 512-byte non-linear variant; applies gamma correction. Maps 0→0, 255→128, 511→255 but with a non-linear curve (square-ish at low end, linear at high end).
- MIX4 — 1024-byte non-linear variant; same gamma-correction approach for quarter-intensity blending.
The L suffix = Linear (no gamma), without L = perceptually-corrected.
VFONTPAL.BIN (48 bytes = 16 × 3-byte VGA RGB entries)¶
Palette for rendering text in video briefing sequences. VGA 6-bit format (values 0–63 per channel).
| Index | 6-bit RGB | 8-bit RGB | Role |
|---|---|---|---|
| 0 | 3F 00 3F |
(255, 0, 255) | Transparent / color key |
| 1 | 0E 17 29 |
(56, 93, 166) | Blue text — bright |
| 2 | 0C 15 26 |
(48, 85, 154) | Blue text |
| 3 | 0B 14 22 |
(44, 81, 138) | Blue text |
| 4 | 09 10 1E |
(36, 65, 121) | Blue text |
| 5 | 07 0C 14 |
(28, 48, 81) | Blue text |
| 6 | 04 09 0F |
(16, 36, 60) | Blue text |
| 7 | 02 03 07 |
(8, 12, 28) | Blue text — dark |
| 8 | 04 34 3F |
(16, 211, 255) | Cyan text — bright |
| 9 | 03 2C 36 |
(12, 178, 219) | Cyan text |
| 10 | 02 24 2C |
(8, 146, 178) | Cyan text |
| 11 | 01 1D 23 |
(4, 117, 142) | Cyan text |
| 12 | 01 15 1A |
(4, 85, 105) | Cyan text |
| 13 | 01 0E 11 |
(4, 56, 69) | Cyan text |
| 14 | 00 06 08 |
(0, 24, 32) | Cyan text — dark |
| 15 | 3F 3F 3F |
(255, 255, 255) | White |
Index 0 (magenta) is the standard VGA transparency key. Indices 1–7 render a blue gradient (likely shadow or secondary text). Indices 8–14 render a cyan gradient (primary text). Index 15 is white (highlight).
INSIGMAP.BIN (256 bytes)¶
Flat 256-entry byte array. Entry 0 = 0x00; all remaining 255 entries = 0x3B
(59 decimal). The 0x3B fill suggests a "no insignia" sentinel — most insignia
slots are unused, with the actual insignia assets referenced by the pilot save
file fields at offsets 0x6E–0x94 (P.md).
File Inventory¶
| File | Size | Purpose |
|---|---|---|
| INSIGMAP.BIN | 256 B | Insignia slot → palette/asset mapping |
| MIX2.BIN | 512 B | Non-linear half-intensity reduction table (gamma-corrected) |
| MIX2L.BIN | 512 B | Linear half-intensity table: output = floor(input / 2) |
| MIX4.BIN | 1024 B | Non-linear quarter-intensity reduction table (gamma-corrected) |
| MIX4L.BIN | 1024 B | Linear quarter-intensity table: output = floor(input / 4) |
| VFONTPAL.BIN | 48 B | 16-entry VGA 6-bit palette for video briefing font rendering |
All six live in FA_2.LIB.
Related¶
Formats: P — pilot save files reference insignia asset IDs cross-referenced by INSIGMAP.BIN; PAL — main VGA palette; VFONTPAL.BIN is a separate 16-color subset for video text.
Engine: the fxs BIN editor renders these tables; the fx_lib codec is tracked in #107.
CFG — Game Configuration (.CFG)¶
Two loose configuration files in the FA install directory — neither packed
into any LIB archive. EA.CFG is the binary settings file written by FA on
first run and updated whenever the player changes settings in-game; it
persists graphics, controls, audio, and pilot selection state between
sessions. IP.CFG is a small plain-text file read by IP.EXE (the
multiplayer session launcher) on startup.
Tools¶
fx¶
fx cfg info <EA.CFG> # dump the CONFIG struct + round-trip check
cfg_read/cfg_write map every field byte-identically, verified against the
install's live EA.CFG by an FX_FA_ROOT-gated test. IP.CFG is two lines of
plain text and needs no codec.
File Layout¶
All multi-byte integers are little-endian (IP.CFG is plain text).
EA.CFG — binary CONFIG struct (347 bytes)¶
Fully mapped from ?UCONFIG_save_EA_CFG@@YGDXZ (0x004b2980) and
?UCONFIG_load_EA_CFG@@YGPAUCONFIG@@XZ (0x004b2930) decompiles in
DumpAllFunctions.txt. No gameplay diff required.
Load validation: UCONFIG_load_EA_CFG rejects the file unless magic ==
0x24 AND file size == 0x15b (347 bytes). If either check fails it returns
null and the engine falls back to defaults.
Note: CN_ReadConfig / CN_WriteConfig are for NET.DAT (3552 bytes,
CN_INFO struct — see DAT.md). EA.CFG is a separate, smaller config
handled by the UCONFIG_* functions.
| Offset | Size | Field | Notes |
|---|---|---|---|
0x000 |
4 | magic | Always 0x24 (36) — version/format tag |
0x004 |
4 | menu_video_mode (DAT_00520ac8) |
Shell/menu graphics mode — the value passed to _InitGraphicsMode for the 2D UI; validated against GG_VideoModesAvailable (fallback 2) |
0x008 |
4 | flight_video_mode (DAT_00520a08) |
In-flight 3D graphics mode — the options-menu radio selection (1/2/4/8). The engine re-inits graphics only when it differs from menu_video_mode, crossing between shell and flight |
0x00C |
4 | _stickDevice |
Joystick device index (0 = none/keyboard) |
0x010 |
4 | _rudderDevice |
Rudder pedal device index |
0x014 |
4 | _throttleDevice |
Throttle controller device index |
0x018 |
4 | _throttle100__3JA |
Throttle axis 100% calibration value |
0x01C |
192 | _mainV[0..47] |
48 × dword joystick axis mapping table (button/axis assignments) |
0x0DC |
6 | _windowTypes[6] |
Display window mode per view (6 bytes) |
0x0E2 |
1 | music_on (_musicOn) |
Music enable flag — the counterpart to sound_on at 0x0E3; read by every Music*/Score*/MIDI routine |
0x0E3 |
1 | _soundOn |
Sound enabled flag |
0x0E4 |
1 | _stereoSwap |
Stereo channel swap |
0x0E5 |
2 | _overallVol__3GA |
Overall volume |
0x0E7 |
2 | _engineVol__3GA |
Engine sound volume |
0x0E9 |
2 | _lockVol__3GA |
Radar lock / missile tone volume |
0x0EB |
2 | _rwrVol__3GA |
RWR (radar warning receiver) volume |
0x0ED |
2 | _stallVol__3GA |
Stall warning tone volume |
0x0EF |
2 | _radioVol__3GA |
Radio chatter volume |
0x0F1 |
2 | _flightMusicVol__3GA |
In-flight music volume |
0x0F3 |
2 | _otherMusicVol__3GA |
Menu / other music volume |
0x0F5 |
2 | _stereoSeparation__3GA |
Stereo separation width |
0x0F7 |
1 | _dMusic |
MIDI music device index |
0x0F8 |
4 | _gamePrefs |
Gameplay preference flags dword |
0x0FC |
4 | _gameMultiPrefs |
Multiplayer preference flags dword |
0x100 |
4 | _gameDebugPrefs |
Debug preference flags dword |
0x104 |
4 | _hudBrightness__3JA |
HUD brightness level |
0x108 |
33 | _campaignPilot[33] |
Active campaign pilot name (null-terminated string) |
0x129 |
32 | multiplayer callsign | From DAT_004f8bf9 — confirmed (see globals below) |
0x149 |
13 | squadron / wing tag | From DAT_004f8c19 — confirmed (see globals below) |
0x156 |
4 | _glasses3dAmount__3JA |
3D glasses convergence amount |
0x15A |
1 | _adCount__3EA |
AD (advertising/demo?) count byte |
The three string fields (0x108, 0x129, 0x149) are only written if the source
global is non-empty (null-check guards the copy loop). Both load and save
functions are named UCONFIG_*, distinct from the network
CN_ReadConfig/CN_WriteConfig pair which handles NET.DAT.
IP.CFG — plain-text launcher flags (27 bytes)¶
One flag per line, CRLF endings. No section headers, no =-separated
key/value pairs except for the /n= flag.
/s
/n="Fighters Anthology"
| Flag | Value | Meaning |
|---|---|---|
/s |
(none) | Start IP.EXE in server/standalone mode (not as a client join) |
/n= |
"Fighters Anthology" |
Session/game name advertised to connecting players in the lobby |
Engine Notes¶
Confirmed globals behind the EA.CFG string fields:
| Address | Size | Name | Confirmed in |
|---|---|---|---|
DAT_004f8bf9 |
32 bytes | Multiplayer callsign | _WriteConfig (0x41e8e0), FUN_004900f0 (0x4900f0) |
DAT_004f8c19 |
13 bytes | Squadron / wing tag | _WriteConfig (0x41e8e0), FUN_004900f0 (0x4900f0) |
FUN_004900f0 (entity name lookup): when param_1 == _playerId and
DAT_004f8bf9 != '\0', uses these globals as the player's display name + tag
pair (passed to FUN_0048e3f0); otherwise reads the name from entity+10.
IP.EXE notes:
- IP.EXE is an MFC Win32 application (
AfxWinMainentrypoint at0x436ef0). It readsIP.CFGat launch and applies these as default session parameters. - The session name
"Fighters Anthology"is the value shown in the LAN/IPX game browser on joining clients. FA.EXElaunchesIP.EXEas a child process when the player selects a multiplayer connection type from the main menu.
Related¶
Formats: P — pilot save files (PLTnnn.P) whose active slot is
referenced here; DAT — the multiplayer transport configs handled by
the separate CN_* function family.
DAT — Multiplayer Network Configuration (.DAT)¶
NET.DAT, MODEM.DAT, and SERIAL.DAT are binary files that store FA's
multiplayer network settings — three loose files in the FA install directory,
none packed into any LIB archive. All three share the same 3,552-byte
CN_INFO format and are read/written by the same
CN_ReadConfig/CN_WriteConfig functions.
| File | Purpose |
|---|---|
NET.DAT |
Active transport selection; IPX/TCP/IP addresses and direct-connect info |
MODEM.DAT |
Modem phone book (8 player name + phone number pairs) and COM port selection |
SERIAL.DAT |
Serial (RS-232) COM port and baud rate preferences |
Tools¶
fx¶
fx dat info <NET.DAT|MODEM.DAT|SERIAL.DAT> # dump CN_INFO + round-trip check
dat_read/dat_write type every documented field; the stored checksum, the
8-byte gap after the transport dword, and the unmapped [0x8f9]–[0xdab]
region (gap #54) pass through verbatim — byte-identical round-trip, verified
against the install's live NET.DAT by an FX_FA_ROOT-gated test. (Editing
fields would need CfigChecksum recomputed; that lands with a write verb
when something needs one.)
File Layout¶
All multi-byte integers are little-endian.
Each file is 3,552 bytes (0xDE0): a 4-byte checksum + 3,548-byte CN_INFO
struct, written by CN_WriteConfig (0x47f930) via
fwrite(param_1, 0xddc, 1, file). The filename is passed as a second
parameter so the same function handles all three files. The struct stores all
transport configurations simultaneously (IPX, TCP/IP, serial, modem) in one
unified layout — the active transport is selected at runtime by field
[0x54]; all sub-blocks are always persisted.
Session name is NOT stored in NET.DAT. The multiplayer lobby session/game
name comes from IP.CFG (/n= flag — see CFG.md), which is passed to
IP.EXE at launch. NET.DAT only holds transport configuration and the Janes.net
online identity.
Phone book in MODEM.DAT: RunModemConfigurationScreen reads/writes
MODEM.DAT separately from NET.DAT. It manages 8 phone-book slots (player name
+ phone number pairs) stored in CN_INFO [0xd0]–[0x5cf]. In NET.DAT these
bytes are always zeroed.
All file offsets below = CN_INFO struct offset + 4 (checksum header).
File off. CN_INFO Size Field
--------- ------- ---- -----
0x0000 — 4 checksum (CfigChecksum over CN_INFO block; length = 0xddc/0xdd8/0xdb0 for v3/v2/v1)
0x0004 [0x0] 4 version dword — must be 3 (current); 2 and 1 are migrated on read
0x0008 [0x4] 80 callsign / Janes.net online name (null-terminated, 80-byte field)
— seeded from _janesOnlineName by CN_SetFactoryDefaults and CN_ReadConfig
— also passed directly as name to SER_ExchangeNames in serial mode
0x0058 [0x54] 4 transport type dword — selects active protocol:
2 = modem (set by RunModemConfigurationScreen; confirmed by MODEM.DAT diff)
3 = serial / RS-232 (SER_Initialize2_5 checks for == 3)
4 = TCP/IP (doConfigurationScreen checks for == 4)
other values used by NetSetProtocol for IPX/NetBEUI
0x0064 [0x60] 4 baud rate index dword (factory default: 10)
7=9600 · 8=19200 · 9=38400 · 10=57600 · 11=57600 · 12=28800 · 13=115200
(SER_Initialize4 switch; also used by MOD_InitPortAndModem)
0x0068 [0x64] 4 serial COM port index dword (factory default: 0 = COM1; range 0–3)
— read by SER_Initialize1; written by MOD_FindModemAndInit autodetect
0x006C [0x68] 84 modem phone number / mode string (null-terminated, 84-byte field)
— dial mode (param_2==0): holds dial number, passed to _Dial_12
— listen mode (param_2≠0): RunModemConfigurationScreen writes "LISTEN"
0x00C0 [0xbc] 4 modem COM port index dword (factory default: 8 = autodetect)
0–7 = COM1–COM8 (explicit); 8 = autodetect (MOD_Initialize1 scans registry)
range valid: 0–8 checked by MOD_Initialize
0x00C4 [0xc0] 0x280 phone book player names: 8 × 0x50-byte null-terminated strings (slots 0–7)
— MODEM.DAT only; zeroed in NET.DAT; edited by RunModemConfigurationScreen
— slot n starts at CN_INFO[0xc0 + n×0x50]; stride confirmed by differential save
0x0344 [0x340] 0x280 phone book phone numbers: 8 × 0x50-byte null-terminated strings (slots 0–7)
— MODEM.DAT only; zeroed in NET.DAT; edited by RunModemConfigurationScreen
— slot n starts at CN_INFO[0x340 + n×0x50]; stride confirmed by differential save
0x05C4 [0x5c0] 0x324 (unused padding) — all-zero in every tested MODEM.DAT capture (0–8
phone-book entries, callsign set, transport set to modem, Call and
Answer modes both exercised). FA's modem config screen has no Advanced
Setup dialog; no UI path writes to this range. Confirmed unreachable.
0x08E8 [0x8e4] 8 IP address hex string — 8 ASCII hex chars, e.g. "c0a80101" for 192.168.1.1
(null-checkable; if [0x8e4]==0 then IP/MAC binary fields are zeroed)
0x08F0 [0x8ec] 13 MAC/IPX node hex string — 12 ASCII hex chars + null, e.g. "001122334455"
~~~~ [0x8f9]–[0xdab]: ~1,203 bytes reserved/unused padding — all-zero in every
tested NET/MODEM.DAT capture; no traced accessor across the modem,
serial, TCP, and IPX config screens, the modem-init path, or the
config load/save (the bracketing hex-string and appIO/binary fields are
written, this gap is not) — the same result as the [0x5c0] padding above.
Carried verbatim by the byte-identical codec.
0x0DB0 [0xdac] 4 appIO callback function pointer — set by SER/MOD/NET_Initialize from CN_INFO;
used for status dialogs during connection setup
0x0DB4 [0xdb0] ? CN_INFO_TCP sub-block start (added in v2; initialized by NetSetFactoryTCP)
— first 12 bytes (3 dwords) zeroed/set via protocol vtable slot +0x66
0x0DCA [0xdc6] 4 local IPX/SPX network number — written by spxinit via getsockname()
0x0DCE [0xdca] 4 local IPX/SPX node address bytes 0–3 — written by spxinit via getsockname()
0x0DD2 [0xdce] 2 local IPX/SPX node address bytes 4–5 — written by spxinit via getsockname()
0x0DD4 [0xdd0] 4 remote address field A — dual-use:
TCP/IP context: IP address binary (4 bytes decoded from hex string at [0x8e4] via _atohb(…,8))
IPX context: direct-connect target IPX network number (user-entered in RunIPXOptionsDialog)
0x0DD8 [0xdd4] 4 remote address field B (first 4 bytes) — dual-use:
TCP/IP: MAC address bytes 0–3
IPX: direct-connect target IPX node address bytes 0–3
0x0DDC [0xdd8] 2 remote address field B (last 2 bytes) — dual-use:
TCP/IP: MAC address bytes 4–5
IPX: direct-connect target IPX node address bytes 4–5
0x0DDE [0xdda] 1 remote address validity/flag — dual-use:
TCP/IP: 1 = IP+MAC fields successfully decoded; 0 = invalid
IPX: 1 = direct-connect address manually entered by user; 0 = not set
0x0DDF [0xddb] 1 (padding / unused; end of v3 struct)
Version migration (CN_ReadConfig)¶
| File version | Action |
|---|---|
| v3, size 0xddc | Accepted as-is; _janesOnlineName overwrites [0x4] if set |
| v2, size 0xdd8 | Upgraded: version set to 3; TCP sub-block re-initialized; IP/MAC binary decoded from hex strings at [0x8e4]/[0x8ec] |
| v1, size 0xdb0 | Upgraded: same as v2 migration |
| corrupt / missing | Factory defaults applied via CN_SetFactoryDefaults |
Engine Notes¶
Confirmed Functions¶
| Function | VA | Lines | Role |
|---|---|---|---|
CN_ReadConfig |
0x47f7a0 | 98888 | Reads config file (NET.DAT / MODEM.DAT / SERIAL.DAT); populates CN_INFO; applies _janesOnlineName at [4] |
CN_WriteConfig |
0x47f930 | 99014 | Writes 4-byte checksum + 0xddc-byte CN_INFO to named config file |
CN_SetFactoryDefaults |
0x47f6d0 | 98785 | Zeroes CN_INFO; sets version=3; seeds [4] from _janesOnlineName; calls NetSetFactoryTCP |
CfigChecksum |
0x47f740 | 98851 | Checksums CN_INFO; length driven by version dword: 0xddc/0xdd8/0xdb0 |
NetSetFactoryTCP |
0x4b0700 | 139845 | Writes 3 zero dwords to TCP sub-block start via protocol vtable slot +0x66 |
SER_Initialize |
0x44cb20 | 57979 | Serial connection setup; reads [0x54], [0x60], [0x64] |
SER_Initialize4 |
0x44c990 | 57902 | Maps [0x60] baud rate index → internal timing constant |
MOD_InitPortAndModem |
0x49a7d0 | — | Opens COM port at [0x64]; sends modem AT attention + init; called by MOD_FindModemAndInit |
MOD_FindModemAndInit |
0x49a850 | — | Scans HKLM\System\CurrentControlSet\Services for attached modem; writes autodetected COM port to [0x64] |
MOD_FindModemAndInitPCMCIA |
0x49a9b0 | — | Same as above but scans HKLM\Enum\PCMCIA for a PCMCIA modem |
MOD_Initialize1 |
0x49ad00 | — | Tries MOD_InitPortAndModem ([0xbc]), then FindModemAndInit, then FindModemAndInitPCMCIA |
MOD_DoConnect |
0x49ad70 | — | Dials [0x68] phone number or waits for incoming call; decodes carrier speed → baud index |
MOD_InitializeAndConnect |
0x49af30 | — | Calls MOD_Initialize1 then MOD_DoConnect |
MOD_Initialize |
0x49aff0 | 118822 | Top-level modem setup; called by MP_Initialize (0x494bc0); reads [0x68], [0xbc] |
MOD_Shutdown |
0x49b0d0 | — | Hangs up modem and releases COM port |
RunSerialConfigurationScreen |
0x49b1d0 | — | Serial config UI; reads/writes SERIAL.DAT; edits callsign [0x4], COM port [0x64], baud [0x60] |
RunModemAdvSetupDialog |
0x49c260 | — | Advanced modem setup dialog (CN_INFO* param); symbol confirmed, not yet decompiled |
RunModemConfigurationScreen |
0x49c780 | — | Modem config UI; reads/writes MODEM.DAT; edits callsign, phone book [0xd0]–[0x5cf], COM port |
NET_Initialize |
0x4b0830 | 140115 | TCP/IP connection setup; reads [0x54] for NetSetProtocol, [0xdac] for appIO |
NET_StartQuery |
0x4b0940 | — | Starts SAP session scan; calls proto_ptr+0x42 (sapopensocket), registers SAP callback at 0x4b22a0 |
spxinit |
0x496f40 | — | Creates/binds SPX socket; writes local IPX address to [0xdc6]–[0xdce] via getsockname |
spxinit2 |
0x497000 | — | Protocol init stub; sets NET_PROTOCOL+0xb = 1; does not access CN_INFO |
spxbuildaddress |
0x497290 | — | Builds NET_ADDRESS from local SPX address at [0xdc6]–[0xdce] |
spxlisten |
0x497010 | — | Creates server-side SPX socket; binds to socket 0x87ec |
spxconnect |
0x497150 | — | Connects SPX socket to a NET_ADDRESS |
sapopensocket |
0x4874c0 | — | Creates IPX SAP socket for session discovery; reads direct-connect address from [0xdd0]–[0xdda] |
FUN_004b22a0 |
0x4b22a0 | — | SAP response callback; dispatches received packets to FUN_004b23f0 |
FUN_004b23f0 |
0x4b23f0 | — | SAP packet dispatcher; handles type 0x2 (session ad), 0x14 (player ad), 0x18 (disconnect) |
RunIPXOptionsDialog |
0x493780 | — | IPX options UI; reads/writes direct-connect address at [0xdd0]–[0xdda] |
NET_PROTOCOL Struct (spx_proto)¶
The global ?spx_proto@@3UNET_PROTOCOL@@A at 0x00501408 is the SPX protocol
implementation selected by NetSetProtocol when the connection type is
IPX/SPX. _proto_ptr points to it after FUN_004b21f0 selects the matching
entry from the protocol list at PTR_PTR_0050c6cc.
The struct is a flat array of function pointers (C-style interface, not a C++ vtable):
| Offset | Value | Function |
|---|---|---|
+0x04 |
0x0001 |
Protocol type short (1 = SPX) |
+0x0a |
flag | Initialized flag — set to return value of +0x3a during protocol selection |
+0x0b |
flag | Init flag — set to 1 by spxinit2 |
+0x3a |
0x00496f40 |
spxinit — creates/binds SPX socket, writes local IPX address |
+0x3e |
0x00497000 |
spxinit2 — protocol init stub (called by NET_Initialize) |
+0x42 |
0x004874c0 |
sapopensocket — creates SAP socket for session discovery (called by NET_StartQuery) |
+0x46 |
0x00487670 |
Session connect function (called in SAP callback when a session is found) |
+0x4a |
— | Packet processor (vtable; called in SAP callback for multi-packet responses) |
+0x4e |
— | Post-query setup (called by NET_StartQuery after sapopensocket) |
+0x5a |
— | Address extractor (called in SAP callback before NETIsAddrLocal check) |
Source file (from error strings): E:\atf95\multi\sap.cpp,
E:\atf95\multi\ipx.cpp
Related¶
Formats: CFG — general game configuration (EA.CFG) and the IP.CFG launcher flags that carry the session name.
Engine: network.md — the full multiplayer protocol and transport documentation these files configure.
EFFECT — GRAPHIC effect-spawn data¶
The transient visual effects the game spawns — explosions, smoke, fire, debris, craters, chaff/flare, dust puffs — are driven by two small binary records. Neither is an on-disk file: they live inside the game executable and its network stream. This page specifies their byte layout so tooling can turn them into a semantic form; the full runtime that consumes them is documented, from the engine side, in objects.md § GRAPHIC effect spawning, and the effects reference SH shapes.
fx_lib provides a read-only interpreter (lib/src/effect.cpp,
api.md § effect.h) — the validation lens for this spec, in the
same relationship the SH interpreter has to SH.md. It never transcribes
game bytes: callers supply a buffer (a synthetic fixture, or the real table
sliced from the executable's .data at integration time).
Tools¶
fx¶
fx effect types # list effect type -> class / .SH shape
fx effect dump <table.bin> [-n N] # decode N 0x30-byte parameter records
fx effect spawn <record.bin> # decode a 17-byte MSG 0x8003 spawn record
fx effect types needs no game data — it prints the type→shape classification
alone. dump and spawn decode a raw buffer.
File Layout¶
Effect-parameter record (0x30 bytes, indexed by type)¶
The engine holds one fixed 0x30 (48)-byte tuning record per effect type,
in a table at FA.EXE 0x4f46c4; effect type t is table + t*0x30. Read by
_GRAPHICAddExp@28 (0x4432d0). Confirmed fields:
| Offset | Type | Field | Meaning |
|---|---|---|---|
0x04 |
s16 | intensity |
base brightness / scale (further scaled by a random factor at spawn) |
0x06 |
s16 | frame_count |
shape frame count / start frame |
0x08 |
u16 | subtype |
sub-type / shape selector; low byte bit 2 = ground burst |
0x0A |
s16 | debris_count |
secondary-debris count |
0x0C |
s16 | debris_spread |
secondary-debris spread |
0x0E.. |
u32[≤8] | sound_ptrs |
sound-effect name pointers (absolute VAs); one picked at random per spawn, list ends at the first null |
0x2E |
s16 | sound_pitch |
sound pitch / parameter |
Bytes +0x00–+0x03 and the remainder of the record are not yet individually
resolved (see Open Questions). The interpreter reports the count of populated
(non-null) sound_ptrs slots as sound_variants; the names themselves cannot
be resolved from the record alone.
Effect type → shape¶
_GRAPHICInit@0 (0x442c00) fills a per-type .SH handle table; the type
index therefore classifies the effect:
| Type(s) | Class | Shape |
|---|---|---|
0 |
none | — |
1–3 |
crater | crater.SH |
4–6 |
debris | debris.SH |
7–11 |
smoke | smoke.SH |
12 |
chaff | chaff.SH |
13 |
flare | flare.SH |
14 |
fire | fire.SH |
15–0x26 |
explosion | exp.SH |
0x28–0x2A |
dust-puff | spd.SH / mpd.SH / lpd.SH |
0x27 and anything past 0x2A are unclassified.
Network spawn record (0x11 bytes — MSG 0x8003)¶
When a machine spawns an effect it mirrors it to peers as a 17-byte message,
drained remotely by ProcessEffectMsgs (objects.md):
| Offset | Type | Field |
|---|---|---|
0x00 |
u8 | effect type |
0x01 |
s32 | x (F24.8 world position, feet = raw/256) |
0x05 |
s32 | y |
0x09 |
s32 | z |
0x0D |
u16 | owner object index (0xffff = none) |
0x0F |
u8 | flag0 |
0x10 |
u8 | flag1 |
Engine Notes¶
The static record is only the seed: at spawn time _GRAPHICAddExp@28 applies
a random per-type variation (a clear-sky explosion may promote to one of several
concrete variants), scales intensity by a random factor, and chains secondary
debris / cluster-release / smoke children. The spawned effect then lives in the
100-entry GRAPHIC pool (0x66-byte entries) and is integrated each frame by
_GRAPHICUpdate@0. All of that runtime behaviour — the pool entry layout, the
adder (continuous-emitter) mechanism, and the fuse/lifecycle — is documented in
objects.md § GRAPHIC effect spawning. This spec covers only
the two data records a tool can decode.
Open Questions¶
1. Unresolved record bytes (#54)¶
The leading bytes +0x00–+0x03 of the 0x30 record and the tail beyond
0x2E are not individually mapped; only the fields above are confirmed from the
_GRAPHICAddExp@28 decompile. The sound_ptrs slots hold absolute VAs into the
executable's string data, so resolving them to sound-effect names needs the full
image, not the record in isolation. Tracked under #54.
Related¶
Engine: objects.md § GRAPHIC effect spawning — the runtime
pool, spawn API, and lifecycle. Shapes: SH — the .SH shapes each
effect type draws. Owners: OT — the object types effects attach to.
P — Pilot Save (.P)¶
PLTnnn.P files (e.g. PLT441.P, PLT628.P) are binary pilot save files
written by the FA engine. They store the persistent state for each pilot slot.
Unlike all other FA data, pilot files are stored directly in the FA install
directory, not inside any .LIB archive. The filename number does not imply
a slot sequence — it appears to be a randomly assigned identifier. All
observed files are exactly 9,696 bytes.
Tools¶
fx¶
fx plt info PLT441.P # identity block: name, rank, campaign, ordnance
fx plt dump PLT441.P # full stats block: missions, kills, weapon accuracy
The identity block (0x01–0xAF) is fully mapped and editable via the fxs
PLT editor. The stats counters (0x1F80–0x21F7) are confirmed from RE and
displayed by fx plt dump and the fxs stats pane. The four gap regions
remain unmapped (see Open Questions).
The fx_lib write API (plt_read → edit → plt_write, api.md
§ plt.h) serializes a pilot file back to bytes, overlaying only the mapped
fields and passing the unmapped regions through verbatim — a plt_read →
plt_write round-trip is byte-identical (see § Round-Trip Notes). Editing is
done in fx_lib, not from the CLI: there is no fx plt write verb, so the
tool never takes an output path for a save file.
Other Tools¶
- FATK — free (abandonware, 1998); original GUI tool with full pilot editing support; requires a compatibility layer on 64-bit Windows
- HxD — free, Windows; use with the field table below for manual patching
- 010 Editor
$— paid; binary templates will allow a fully labelled struct view once all gaps are mapped
File Layout¶
All multi-byte integers are little-endian.
_campaignPilot global base VA = 0x004f8bb8 (from FA.SMS). File size
0x25E0 = 9,696 bytes. All offsets below are confirmed from decompile
(DumpAllFunctions.txt) or computed from base VA.
Identity block (0x00–0xAF) — confirmed¶
| Offset | Size | Type | Field |
|---|---|---|---|
0x00 |
1 | u8 | Type / version tag — observed: 0x0F |
0x01 |
63 | char[] | Pilot name, null-padded |
0x40 |
32 | char[] | Callsign, null-padded |
0x61 |
13 | char[] | Callsign voice file (e.g. ^ACID.5K), null-padded |
0x6E |
13 | char[] | Nose art ID (e.g. NOSE01), null-padded |
0x7B |
13 | char[] | Left wing decal ID (e.g. LEFT03), null-padded |
0x88 |
13 | char[] | Right wing decal ID (e.g. RIGHT03), null-padded |
0x95 |
13 | char[] | Pilot portrait ID (e.g. PILOT02), null-padded |
0xA2 |
14 | char[] | Rank string (e.g. 2nd Lieutenant), null-padded |
Text and display region (0xB0–0xDAD) — partially mapped¶
Four anchor fields confirmed from FUN_004674f0 (pilot card display, VA
0x4674f0); gaps between them remain unmapped.
| Offset | Size | Type | Field |
|---|---|---|---|
0xB0 |
18 | ? | Unknown (see Open Questions) |
0xC2 |
~13 | char[] | Secondary identity string — printed on pilot card after rank (squadron, unit, or location); exact length unknown |
0xCF |
1344 | ? | Unknown — mission log region (see Open Questions) |
0x5AF |
var | char[][] | Mission log — up to 10 null-terminated entries read sequentially; each up to 3 lines; likely mission history |
0xD7F |
13 | char[] | Campaign .CAM filename (e.g. EGYPT.CAM) — confirmed: DAT_004f9937 = _campaignPilot + 0xD7F, written by campaign init |
0xD8C |
32 | char[] | Campaign display name (e.g. Egypt 1998) — confirmed: DAT_004f9944 = _campaignPilot + 0xD8C, written by campaign init |
0xDAC |
2 | u16 | Pilot status enum — confirmed: DAT_004f9964 = _campaignPilot + 0xDAC; 0=Available, 1=On mission, 2=MIA, 3=KIA, 4=Retired From Active Duty |
Campaign data strings (0xDAE–0x1C5F) — partially mapped¶
Variable-length null-terminated strings packed sequentially from 0xDAE:
- Assigned aircraft .PT reference (e.g. F22.PT)
- Available aircraft pool (.PT references)
- Sensor/ECM loadout (.SEE, .ECM references)
- Other campaign-specific strings
Ordnance inventory (0x1C60–0x1F7F) — confirmed¶
50 entries × 16 bytes = 800 bytes. DAT_004fa818 = _campaignPilot + 0x1C60.
| Field | Offset within entry | Size | Type |
|---|---|---|---|
Weapon type filename (.JT) |
+0x00 |
14 | char[] (null-padded) |
| Quantity | +0x0E |
2 | s16 (-1 if slot unused; 0x7FFF = unlimited) |
Managed by _AddCampaignStore (0x480E10): searches by name,
increments/decrements quantity, or allocates a free slot.
Stats counters (0x1F80–0x21F7) — confirmed¶
All fields confirmed via FUN_00485380 (0x485380, end-of-mission stats flush)
and related functions. _campaignPilot base 0x4f8bb8 + listed offset = VA
of each field.
Mission and loss counters (0x1F80–0x1FAF):
| Offset | VA | Size | Field |
|---|---|---|---|
0x1F80 |
DAT_004fab38 |
u32 | Missions flown (total) |
0x1F84 |
DAT_004fab3c |
u32 | Wingman missions |
0x1F88 |
DAT_004fab40 |
u32 | Missions failed — copied to _campaignFailures (0x54e418) before campaign proc |
0x1F8C |
DAT_004fab44 |
u32 | Total shots fired — accumulated from per-mission DAT_0054ddc4 |
0x1F90 |
DAT_004fab48 |
u32 | Ejections / bail-outs |
0x1F94 |
DAT_004fab4c |
u32 | Wingman KIA |
0x1F98 |
DAT_004fab50 |
u32 | Player aircraft damage % accumulated |
0x1F9C |
DAT_004fab54 |
u32 | Wingman aircraft damage % accumulated |
0x1FA0 |
DAT_004fab58 |
u32 | Player landing count |
0x1FA4 |
DAT_004fab5c |
u32 | Wingman landing count |
0x1FA8 |
DAT_004fab60 |
u32 | Player landing quality score |
0x1FAC |
DAT_004fab64 |
u32 | Wingman landing quality score |
Kill tallies by target class (0x1FB0–0x2017):
13 kill categories; each has player u32 then wingman u32 (8 bytes per
category). Category dispatch from _KillStats_12 (0x485820) based on
obj_class word bits:
| Offset | VA (player) | Category | obj_class bits |
|---|---|---|---|
0x1FB0 |
DAT_004fab68 |
Air — aircraft / fighters | 0x8000 set |
0x1FB8 |
DAT_004fab70 |
Air — type B (fighters subtype) | 0x4000 set |
0x1FC0 |
DAT_004fab78 |
Aircraft destroyed by crash or BA weapon | obj byte 0 = 0x04 with OBJ_TYPE+0xba bit 3 |
0x1FC8 |
DAT_004fab80 |
Naval vessels | 0x2000 set |
0x1FD0 |
DAT_004fab88 |
SAM launchers | 0x1000 set |
0x1FD8 |
DAT_004fab90 |
AAA guns | 0x800 set |
0x1FE0 |
DAT_004fab98 |
Armor / tanks | 0x400 set |
0x1FE8 |
DAT_004faba0 |
APCs | 0x200 set |
0x1FF0 |
DAT_004faba8 |
Vehicles / trucks | 0x100 set |
0x1FF8 |
DAT_004fabb0 |
Infantry | 0x40 set |
0x2000 |
DAT_004fabb8 |
Friendly fire | same faction, other conditions |
0x2008 |
DAT_004fabc0 |
Air — non-0x8000 (non-fighter aerial) |
0x8000 absent, aerial |
0x2010 |
DAT_004fabc8 |
Capital ships | naval + hitpoints > 999 |
Wingman slot for each = player VA + 4.
Weapon accuracy stats (0x20B8–0x21F7) — confirmed:
8 weapon-type groups; each group = player slot (0x14 bytes) + wingman slot (0x14 bytes) = 0x28 bytes per group.
Each slot = 5 × u32: [damage_total, shots_fired, hits, type3, kills].
Dispatched by FUN_004856f0 (0x4856f0) based on OBJ_TYPE flags; accumulated
by FUN_004854a0 (0x4854a0).
| Offset | VA | Group |
|---|---|---|
0x20B8 |
DAT_004fac70 |
Air-to-air gun (OBJ_TYPE bit 0x10000) |
0x20E0 |
DAT_004fac98 |
Air-to-air missile (OBJ_TYPE bit 0x20000) |
0x2108 |
DAT_004facc0 |
Ground attack (OBJ_TYPE bits 0x20080) |
0x2130 |
DAT_004face8 |
Naval attack (OBJ_TYPE bit 0x10) |
0x2158 |
DAT_004fad10 |
Kill by aircraft (shooter = obj byte 0 0x04) |
0x2180 |
DAT_004fad38 |
Kill type B |
0x21A8 |
DAT_004fad60 |
Kill type C |
0x21D0 |
DAT_004fad88 |
Kill type D |
Wingman slot for each = player VA + 0x14.
The regions at 0x2018–0x20B7 and 0x21F8–0x25DF are unmapped — see Open
Questions.
Engine Notes¶
Confirmed engine functions (FA.SMS + DumpAllFunctions.txt):
| VA | Symbol/Name | Description |
|---|---|---|
0x467180 |
PilotSave(PILOT*, short) |
Write pilot save — takes a PILOT* and a short slot index; serialises the full struct to PLTnnn.P |
0x4674f0 |
FUN_004674f0 |
Pilot card display — renders pilot dossier text; accesses +0xC2, +0x5AF, +0xD8C, +0xDAC, +0x1F88 (formats missions-failed count into display buffer) |
0x467860 |
FUN_00467860 |
String copy helper — copies until \x01 byte (styled text terminator) |
0x480E10 |
_AddCampaignStore |
Adds or increments an ordnance entry in the ordnance inventory table at +0x1C60 |
0x481320 |
_CampaignSave |
Saves _campaignPilot to disk (copies to RM, then _SaveFile with full 0x25E0 bytes) |
0x484D90 |
_EndOfMissionStats@0 |
Computes per-mission damage %, landing, protection, and player/wm state into temp globals |
0x485040 |
_EndOfFortMissionStats@0 |
Computes fort-related kill/suppression stats into named temp globals |
0x485380 |
FUN_00485380 |
Flushes all per-mission temp stats into permanent PILOT struct fields at +0x1F80 onwards |
0x4854E0 |
_WpnStats@28 |
Per-shot weapon stats accumulator; updates shots_fired, hits, damage_total, kills in temp buffer |
0x485820 |
_KillStats@12 |
Records kill into the correct kill-category slot (13 categories at +0x1FB0) based on target's obj_class |
0x485A40 |
_LandingStats@12 |
Accumulates landing count and quality score into temp globals |
PilotSave saves the full struct as a single 9,696-byte block via
_SaveFile. The stats counters are accumulated by the functions above into
_campaignPilot directly.
Round-Trip Notes¶
plt_write is a byte-exact passthrough serializer: it starts from a copy
of the original file bytes (PltFile::raw) and overlays only the fixed-offset
mapped fields — the identity block (0x00–0xAF) and, when present, the stats
counters (0x1F80–0x21F7). Everything else — the four unmapped gap regions
and the variable-length campaign/ordnance region (0xB0–0x1F7F) — is copied
through unchanged. A plt_read → plt_write round-trip is therefore
byte-identical, and Phase 6 (#29) can map the gaps without touching the codec.
Two details keep the round-trip exact even before the gaps are understood:
- Unedited string fields are left verbatim. An identity field is only rewritten when its value differs from what the original bytes decode to. Untouched fields — including any non-zero bytes left past a field's null terminator (a shorter callsign written over a longer one in-engine) — pass through unmodified rather than being re-zero-padded.
- The variable-length campaign region is never re-encoded.
PltInfo'scam_file/aircraft/ordnanceviews come from a heuristic forward scan; they are read-only display state. The bytes themselves pass through, so the scan's imprecision cannot perturb the file.
Validation: plt_repack (read → write) is byte-identical across all 7 real
pilot files in the reference install (PLT441/628/937/991/992/993/994.P,
FX_FA_ROOT-gated) and the synthetic fixtures in tests/test_plt.cpp, which
also exercise full-width fields, stale terminator bytes, and single-field
edits.
Open Questions¶
Static Ghidra analysis (AnalyzePLT.java, 46,985-line output) and binary diff
of three fresh pilot saves (PLT441.P, PLT628.P, PLT937.P) are exhausted for
all four gaps — every one needs pilot saves taken after actual gameplay:
complete 5+ standard missions (gaps 2 and 3), a fort-assault mission (gap 4),
and a rank advance (gap 1), then diff with HxD (side-by-side compare →
Differ) or 010 Editor using the field tables above. A binary probe test
(2026-05-21, four PROBE_GAP* pilots) confirmed the pilot records screen
reads none of the gap regions.
1. Gap 0xB0–0xC1 (18 bytes)¶
No named DAT_ label or MOV/CMP instruction targeting VA
0x004f8c68–0x004f8c79 found in any function in the game executable. All zeros in all
three fresh saves. Struct context suggests these bytes are written only after
campaign assignment — possibly a score tier index, medal count, or secondary
rank fields.
Status: open — re-gameplay (#29)
2. Gap 0xCF–0x5AE (1,344 bytes)¶
This region holds variable-length null-terminated mission log text; decompile
of FUN_004674f0 shows the pilot card reader scanning from 0x5AF backwards,
implying the entries grow downward from 0x5AE. No fixed-offset accesses
within the region. All zeros in fresh saves (no missions flown). Structure
known, content unsampled: each log entry is one or more null-terminated lines
terminated by a 0x01 styled-text byte.
Status: open — re-gameplay (#29)
3. Gap 0x2018–0x20B7 (160 bytes)¶
findFunctionsReadingOffsets returned only false positives — video-decoder
functions accessing param_1 + 0x2044 where param_1 is a frame buffer, not
_campaignPilot. No genuine PILOT struct access in this range was found
anywhere in the analyzed code. All zeros in fresh saves. Sits between the
confirmed kill-tally block (ends 0x2017) and the confirmed weapon-accuracy
block (starts 0x20B8). Could be additional kill subcategories (objective
kills, suppression counts), a mission-result history array, or reserved
padding.
Status: open — re-gameplay (#29)
4. Gap 0x21F8–0x25DF (~1,000 bytes)¶
_EndOfFortMissionStats@0 (0x485040) and all callers write exclusively to
scratch globals at 0x005xxxxx — no flush path into this region was found in
the decompile. All zeros in fresh saves. Fort/campaign-phase stats scratch
globals (_statsFortKills__3PAJA, _statsFortAircraftUsed__3PAJA, etc.) are
confirmed present but their flush path into the PILOT struct was not
identified. This region is populated only after completing campaign
fort-assault missions.
Status: open — re-gameplay (#29)
Related¶
Formats: BRF — .PT (plane type) and .JT (ordnance) records
referenced by name; M — mission files whose outcomes feed the stats
counters; CAM — the campaign whose filename is stored at +0xD7F.
SMS — Game-executable Symbol Map (.SMS)¶
FA.SMS is a binary symbol map file shipped with Jane's Fighters Anthology —
a loose file in the FA install directory, not packed into any LIB archive. It
contains 3,829 MSVC-mangled C++ function and variable names paired with their
virtual addresses in the game executable. It is the single most useful resource for the game executable
reverse engineering.
Tools¶
fx¶
fx sms dump <FA.SMS> [-o out.csv] # symbol table → CSV (va, mangled, demangled)
File Layout¶
All multi-byte integers are little-endian.
| Offset | Size | Type | Description |
|---|---|---|---|
0x0000 |
4 | u32 | count — number of symbol records (3829 = 0x0EF5) |
0x0004 |
N×8 | records: N × { str_off: u32, va: u32 } | |
| — | * | string_table — null-terminated C strings, densely packed |
string_tablebase offset =4 + count × 8= 30636 (0x778C)- Total file size: 106,706 bytes
va— virtual address of the symbol in the game executable's address space (not a file offset)str_off— byte offset intostring_tableof the null-terminated symbol name- Records are stored in
str_offorder (string-table insertion order), not sorted by VA
Address Range¶
| Boundary | VA |
|---|---|
| Lowest | 0x00401000 |
| Highest | 0x005937E0 |
This covers the full the game executable image: .text (code), .data, .rdata, and .bss.
Symbol Contents¶
3,829 MSVC C++ decorated names (?-mangled) plus C-decorated names. Calling
convention breakdown:
| Convention | Decoration | Count |
|---|---|---|
| C-linkage / callbacks | no ? prefix |
2563 |
__cdecl |
@@YA |
446 |
__stdcall |
@@YG |
138 |
__fastcall |
@@YI |
122 |
Representative sample:
| VA | Symbol | Demangled |
|---|---|---|
0x00401000 |
_explode |
explode (C linkage) |
0x004BC240 |
?APEndArrestorCatch@@YAXXZ |
void APEndArrestorCatch(void) |
0x00401022 |
?APLanding@@YGDXZ |
char __stdcall APLanding(void) |
? |
?AllocVDO@@YADPAUVDO@@@Z |
char AllocVDO(struct VDO *) |
? |
?CDPATH@@3PADA |
char * CDPATH (global) |
? |
?CN_ReadConfig@@YAXPAUCN_INFO@@PAE@Z |
void CN_ReadConfig(struct CN_INFO *, unsigned char *) |
Namespace prefixes seen in the symbol set include: AP (autopilot), VDO
(video), CD (campaign/disc), CN (network config), and many more.
Engine Notes¶
Cross-Reference Verification¶
Spot-checked 10 symbols across the full VA range against the shipped FA.EXE
(ImageBase 0x00400000). All sampled VAs contain valid x86 at their computed
file offsets:
| VA | Symbol | First bytes | Notes |
|---|---|---|---|
0x00401000 |
_explode |
8B 44 24 04 53 8B |
MOV EAX,[ESP+4]; PUSH EBX |
0x00411910 |
_OnTheGround@0 |
66 A1 BC 6F 4F 00 |
MOV AX,[...] |
0x00452770 |
_HARDPtrs@12 |
8B 44 24 04 83 EC |
MOV EAX,...; SUB ESP,... |
0x0047ADD0 |
_ThrustSupport@0 |
83 EC 30 66 83 3D |
SUB ESP,0x30 |
0x004A7220 |
_SetupPT |
8B 44 24 04 50 E8 |
MOV EAX,...; PUSH; CALL |
0x004BC240 |
?APEndArrestorCatch@@YAXXZ |
33 C9 E8 E9 59 F9 |
XOR ECX,ECX; CALL |
0x004D6FB0 |
_GetCurrentThread@0 |
FF 25 C0 35 59 00 |
IAT thunk (import) |
0x004D81B0 |
__chkstk |
51 3D 00 10 00 00 |
PUSH ECX; CMP EAX,0x1000 |
0x004EC200 |
?firstMenu@@3PAUMENU@@A |
00 00 00 00 00 00 |
zero-init global |
0x005937E0 |
\177wail32_NULL_THUNK_DATA |
00 00 00 00 97 00 |
Miles Sound System IAT |
Conclusion: the symbol map matches the shipped binary. All code symbols have valid function prologues; all data symbols are in the expected sections.
Build Configuration¶
Release build. Evidence:
- No
_RTC_CheckStackVars,_RTC_CheckEsp,_RTC_Shutdown, or any other/RTCxruntime-check symbols — these are injected only by MSVC debug builds - No
_CrtDbgReport,_CrtDbgBreak, or debug CRT entry points __chkstkand__crtheapare present but both appear in release builds (stack probing for large frames; CRT heap pointer)- Mangled names show no debug-specific decorations
Key Confirmed Symbols¶
A curated list of all 3,829 named symbols is maintained in docs/fa/symbols.md. Selected high-value symbols confirmed during the main Ghidra disassembly pass:
| VA | Symbol | Notes |
|---|---|---|
0x403700 |
?usnfmain@@YAXXZ |
Main game loop entry point |
0x41E8F0 |
IsBrentDLL |
Test whether a loaded DLL uses Phar Lap BRF format |
0x41EB60 |
LoadDLL |
Generic overlay DLL loader |
0x41F240 |
LoadBrentDLL |
Load a Phar Lap BRF overlay DLL |
0x441C60 |
_ChooseScoreInit |
Score / debrief screen initialiser |
0x464C80 |
_CTDo_* range start |
AI condition/action dispatcher — in the game executable itself (see AI.md) |
0x463EA0 |
_MaskEvents_4 |
Entity flag bit 10 event-mask handler |
0x464040 |
_Reaction_12 |
Entity flag bit 10 reaction handler |
0x467110 |
_CTEval_* range end |
AI condition evaluator — in the game executable itself |
0x467180 |
PilotSave(PILOT*, short) |
Write pilot data to PLTnnn.P save file |
0x480B50 |
_MISSIONInit2_0 |
Mission system second-phase init |
0x481940 |
_CallMissionProc_8 |
Dispatch per-mission condition proc |
0x481C10 |
_MISSIONTextProc@16 |
MM text parser (tmap / tmap_named / tdic keywords) |
0x486860 |
_MISSIONCheckSuccess@0 |
Check win/loss conditions |
0x4A6EB0 |
SetupOT |
OBJ_TYPE init (BRF type init entry) |
0x4A7230 |
SetupJT |
PROJ_TYPE init |
0x4AACF0 |
T_HorizonProc |
Horizon renderer — exported by the game executable, consumed by .LAY DLLs |
0x4B4320 |
WRFogLayerUpdate |
Per-frame fog opacity jitter — confirmed the game executable export |
0x4C5D70 |
@T_Load@4 |
T2 terrain file loader entry point |
0x4D22D4 |
do_ifdestroyed |
Shape bytecode opcode handler — tests destroyed state |
Usage with Ghidra¶
A ready-to-run Java script is provided at
scripts/ghidra/ImportFASmsHeadless.java.
See scripts/ghidra/README.md
for full setup and overlay-DLL rebasing instructions.
Quick start:
- Open the game executable in Ghidra and let auto-analysis finish.
- Run it headless:
scripts/ghidra/run_ghidra.sh ImportFASmsHeadless.java(it resolvesFA.SMSfrom-scriptArg, then$FA_PROJECT/FA.SMS), or run it from the Script Manager. - All 3,829 functions and globals are labelled in one pass.
Related¶
Engine: symbols.md — the curated, subsystem-organized
symbol reference built from this file; every docs/fa engine doc cites VAs
recovered through it.
Installer
ESA — EA Installer Archive (.ESA)¶
SETUP.ESA is the single archive on the FA Disc 1 root that carries every file
the EA installer copies to disk: the game executable and symbols (FA.EXE,
FA.SMS), the installed LIB archives (FA_1/2/4B/4D.LIB), the sound and comms
drivers, the tech-support tool, and the loose install text. The installer
(SETUP.EXE) reads it under the direction of the .SSF scripts
(SSF.md), whose INSTALL_FILES directives select entries by the
label each carries. There is one .ESA in FA; the CD-resident FA_4C.LIB and
FA_7.LIB sit loose beside it and are not packed in.
The container is a flat directory of variable-length records followed by the
payloads back to back — no index, no padding, no per-payload header. PKWA
entries are raw PKWare DCL streams and NULL entries are stored; the four LIB
archives are stored, so extracting them yields byte-identical LIBs that parse
straight through the LIB codec.
Tools¶
fx¶
fx esa ls <SETUP.ESA> # directory: name, label, flags, method, sizes
fx esa info <SETUP.ESA> # entry/method counts, directory size, repack check
fx esa extract <SETUP.ESA> <NAME> [-o] # one entry (PKWA decoded, NULL copied)
fx esa unpack <SETUP.ESA> [-o dir] # every entry
fx esa repack <SETUP.ESA> <out.ESA> # container round-trip (byte-identical)
fx esa is a thin front-end over fx::esa_read_dir / esa_extract / esa_repack
(api.md). The PKWare DCL decode reuses
fx::blast_decompress — the same decoder the LIB codec uses — because a .ESA
PKWA stream is raw DCL.
File Layout¶
magic char[29] "ELECTRONIC_ARTS_ARCHIVE_FILE\0"
dir record[] variable-length records, back to back
term u8 0x00 — an empty (zero-length) name ends the directory
data blob[] payloads, contiguous, in directory order; the first begins
at the byte after the terminator, the last ends at EOF
Directory record — confirmed¶
All integers are little-endian; strings are NUL-terminated and are not 8.3 —
names may hold spaces and apostrophes (JANE'S HOME PAGE.URL).
| Field | Type | Description |
|---|---|---|
name |
char[] |
file name, NUL-terminated |
label |
char[] |
archive label — the token .SSF INSTALL_FILES selects on |
flags |
u32 |
see below |
usize |
u32 |
uncompressed size |
mtime |
u32 |
modification time (Unix epoch) |
method |
char[] |
"PKWA" (PKWare DCL) or "NULL" (stored), NUL-terminated |
csize |
u32 |
stored size; for NULL, csize == usize |
offset |
u32 |
absolute payload offset within the archive |
The directory self-terminates on an empty name, so there is no count field: the
byte after the terminator is the first payload offset. Two invariants hold across
a well-formed archive and are enforced on read — the directory does not overlap
any payload (offset >= directory_size), and the payloads exactly fill the file
(max(offset + csize) == file size). On the retail SETUP.ESA the last entry
ends at byte 109,979,167, the archive's exact length.
Entry flags — inferred¶
Every retail entry is 0x0211, except EAREMOVE.EXE and EAEXEC.EXE, which are
0x0221 — and those are exactly the two the .SSF scripts install with
INSTALL_SYSFILES (to the Windows system directory) rather than INSTALL_FILES
(to the app directory). The differing bit (0x10 vs 0x20) therefore reads as a
destination-class selector. The remaining low bits (0x001, 0x200) are
unexplained. Confirming the bitfield is a target of the SETUP.EXE reconstruction
(#54); the codec preserves flags verbatim regardless.
Compression — confirmed¶
PKWA payloads are raw PKWare DCL streams: a litmode byte, a dictbits
byte, then the LSB-first bitstream. Unlike a flags=4 LIB entry
(LIB.md § EA Compression Wrapper), a
.ESA stream carries no 4-byte EA decompressed-size prefix — the size is the
directory's usize — so it is decoded with blast_decompress, never
blast_decompress_ea. NULL payloads are stored verbatim.
File Inventory¶
The retail SETUP.ESA (Disc 1, v1.00F) — 23 entries, in directory order:
| Name | Label | Flags | Method | Usize | Csize |
|---|---|---|---|---|---|
| FA.EXE | FA_EXECUTABLE_FILES | 0x0211 | PKWA | 1,299,968 | 677,707 |
| FA.SMS | FA_EXECUTABLE_FILES | 0x0211 | PKWA | 104,452 | 50,077 |
| JANE'S HOME PAGE.URL | FA_INTERNET | 0x0211 | NULL | 49 | 49 |
| EAHELP.HLP | FA_README | 0x0211 | PKWA | 305,783 | 73,849 |
| README.TXT | FA_README | 0x0211 | PKWA | 27,816 | 10,512 |
| IP.EXE | FA_README | 0x0211 | PKWA | 708,096 | 314,488 |
| IP.CFG | FA_README | 0x0211 | NULL | 27 | 27 |
| FA_1.LIB | FA_LIBS | 0x0211 | NULL | 28,501,531 | 28,501,531 |
| FA_2.LIB | FA_LIBS | 0x0211 | NULL | 31,546,576 | 31,546,576 |
| FA_4B.LIB | FA_LIBS | 0x0211 | NULL | 34,670,738 | 34,670,738 |
| FA_4D.LIB | FA_LIBS | 0x0211 | NULL | 13,756,838 | 13,756,838 |
| CHAT.TXT | FA_MISC | 0x0211 | PKWA | 591 | 336 |
| BRIEFING.TXT | FA_MISC | 0x0211 | PKWA | 8,793 | 3,140 |
| EXAMPLE.MT | FA_MISC | 0x0211 | PKWA | 1,178 | 645 |
| LICENSE.TXT | FA_MISC | 0x0211 | PKWA | 4,672 | 2,279 |
| WAIL32.DLL | FA_SOUND_DRIVER_FILES | 0x0211 | PKWA | 135,680 | 64,347 |
| CDRVDL32.DLL | COMMDRV_DLLS_FILES | 0x0211 | PKWA | 28,672 | 12,760 |
| CDRVHF32.DLL | COMMDRV_DLLS_FILES | 0x0211 | PKWA | 29,184 | 14,121 |
| CDRVXF32.DLL | COMMDRV_DLLS_FILES | 0x0211 | PKWA | 39,424 | 20,704 |
| COMMSC32.DLL | COMMDRV_DLLS_FILES | 0x0211 | PKWA | 18,432 | 8,248 |
| EAREMOVE.EXE | REMOVER_EXECUTABLE_FILE | 0x0221 | PKWA | 325,632 | 163,684 |
| EAEXEC.EXE | EXEC_EXECUTABLE_FILE | 0x0221 | PKWA | 132,608 | 65,360 |
| PKCOMP.IDKDECODLL | SETUP_SPECIAL_FILES | 0x0211 | NULL | 19,968 | 19,968 |
Four members are also present loose on the Disc 1 root — README.TXT,
EAHELP.HLP, IP.EXE (all PKWA) and IP.CFG (NULL). Extracting them from the
archive reproduces the loose files byte-for-byte, which proves the codec from the
disc alone, with no installed game.
Build note. This is the v1.00F disc build. The official v1.02F patch
(fae102.exe) later rewrites FA.EXE, FA.SMS, FA_1.LIB
and FA_2.LIB and adds msapi.dll; the reconstruction database describes that
patched build, so a from-disc install is the earlier binary. FA.SMS here
declares 3,753 symbols versus 3,829 after the patch.
Round-Trip Notes¶
fx esa repack reads the directory and rebuilds the archive: metadata is copied
verbatim, payloads are kept stored (still PKWA where they were PKWA), offsets
are recomputed, and the terminator is rewritten. Because the records re-encode to
the same bytes, the recomputed offsets equal the originals for a contiguous,
in-order source, so the output is byte-identical — the proof the layout is fully
accounted for. A non-contiguous or reordered source normalises instead.
fx esa pack builds a fresh archive with every entry stored (method
"NULL"): fx_lib has a PKWare DCL decoder, not an encoder — the same asymmetry
as ealib_build writing flags=0. It is used to synthesise fixtures,
not to re-create a shipped SETUP.ESA.
Both claims are checked against the retail archive by the fa_disc_install
integration test (development.md § Real-media install mode):
the 110 MB SETUP.ESA repacks byte-for-byte, and every extracted entry is hashed
against a committed manifest. The decoder also has a self-oracle on the disc
itself — README.TXT, IP.EXE, IP.CFG and EAHELP.HLP are shipped both
inside the archive and loose on disc 1, and the extracted bytes must equal the
loose ones. Three of the four are PKWA, so the DCL path is proven without
reference to anything we recorded ourselves.
Open Questions¶
Entry flags bitfield¶
The flags field is 0x0211 on app-directory entries and 0x0221 on the two
INSTALL_SYSFILES entries, so one bit clearly selects the destination class, but
the low bits are unexplained and the mapping is inferred from correlation rather
than read out of SETUP.EXE.
Status: open — re-static (#54)
The malformed member name PKCOMP.IDKDECODLL¶
One entry carries a 17-character name that is not valid 8.3 and is referenced by
no .SSF directive; its payload is a single stored PE image, and its label
SETUP_SPECIAL_FILES appears nowhere in the scripts. It looks like a
name-concatenation bug in EA's archive builder (PKCOMP.IDK + …DECO.DLL?). The
codec preserves it verbatim; what consumes it is a question for the SETUP.EXE
reconstruction.
Status: open — re-static (#54)
Related¶
Formats: LIB — the four .LIB archives ESA carries, and the DCL
contrast (LIB entries wrap the stream in a 6-byte EA header; ESA does not).
SSF — the installer script whose INSTALL_FILES directives select ESA
entries by label. RGN — the other Disc 1 installer-only format.
Program: reconstruction.md — the SETUP.EXE
reconstruction that will confirm the flags bitfield and the PKCOMP member.
RTP — RTPatch Binary Patch (.RTP)¶
The retail discs ship Fighters Anthology build 1.00F, while every recovered
symbol and format in this project describes the patched 1.02F build. The gap
is closed by one downloadable updater, fae102.exe, which carries a Pocket
Soft .RTPatch Professional 4.11 payload as an overlay. Reversing that payload —
not the third-party patcher binary — lets fx install produce a 1.02F tree from
the user's own discs.
The patch carries eight file records. Six are MODIFY — a binary diff against
the 1.00F original: FA.EXE, FA.SMS, FA_1.LIB, FA_2.LIB, README.TXT, and
the installer's own EAEXEC.EXE. Two are NEW — a full file delivered whole:
msapi.dll (the online-matchmaking API) and a small ealtest.exe. Each file is
carried by the same custom codec — an order-0 adaptive Huffman over an LZSS token
stream, tagged 0xB59C, MSB-first. A MODIFY record decompresses to a program of
copy / poke / fill opcodes (§ Engine Notes) applied against the 1.00F original; a
NEW record decompresses straight to the file. The reconstruction is byte-identical
to the shipped 1.02F build.
EAEXEC.EXE and ealtest.exe are flagged for a prompted system directory
(\WINDOWS\SYSTEM), not the game directory; fx install --patch applies only the
app-directory records.
Tools¶
fx¶
fx patch inspect <patch.exe>
fx patch apply <patch.exe> --source <dir> --out <dir> [--file NAME] [--no-checksum]
inspect locates the container overlay and lists every record: name, mode,
source and target sizes, and the source checksum. apply reconstructs each file
from the matching original in --source and writes it to --out; the source is
verified against the record's §10 checksum first, and a mismatch skips the file
(the wrong version would otherwise yield a correct-sized but corrupt result).
--no-checksum forces the apply; --file limits it to one target.
$ fx patch inspect fae102.exe
container: RTPatch v0x019a (extra mode) @ offset 0x2b600
system files (installer-prompted):
EAEXEC.EXE Location of your \WINDOWS\SYSTEM directory
File Mode Source Target Source checksum
FA.EXE modify 1299968 1319424 w1=5b01cc0f w2=3f326589
...
File Layout¶
The payload is a self-contained .RTPatch container (FA carries it as an overlay
inside the updater .EXE at file offset 0x2b600; fx patch scans for the
K* magic). All fixed integers are little-endian.
+-------------------+
| header | "K*", version, flags, sizes (see below)
+-------------------+
| special-files tbl | count + (name, prompt) lp_strings — files the installer
| | relocates to a system dir (EAEXEC.EXE → \WINDOWS\SYSTEM)
+-------------------+
| directory table | count + lp_strings (empty in FA)
+-------------------+
| file record 0 |
| file record 1 |
| ... |
| EOF record | rec_hdr type nibble == 1
+-------------------+
Three encodings coexist: fixed-width little-endian integers (header, entry descriptors); a byte-oriented VLI for opcode operands and counts; and an MSB-first bit stream for the compressed diff (§ File Inventory).
Header — magic "K*", version u16, flags u16; if flags bit 15 is set an
ext_type_flags u32 follows (bit 16 = extra mode, which adds timestamp and
alt-path fields to every entry); then option_flags u16, patch_total_size u32,
reserved words, cmd_flags u16 (bit 2 gates a combine_id u32), and a final
reserved word. FA's header is version 0x019a, extra mode on.
lp_string — u8 length (including the NUL; 0xFF escapes to a following
u16), then that many bytes ending in a NUL.
VLI — one lead byte: bit 7 is the sign; the count of 1-bits from bit 6
downward gives the number of little-endian continuation bytes. count == 0 holds
a value 0..63 in the low 6 bits; otherwise the lead byte's remaining low bits
are the most-significant part.
File record — a rec_hdr u16 whose top nibble is the record type and whose
low bits gate optional fields (option override, inline name, disk-set, attributes,
explicit paths), then a 10-byte metadata block, then the type's data block. FA
uses two types: type 4 (MODIFY), a binary diff, and type 2 (NEW), a full
compressed file. (Bit 7, the disk-set flag, marks a system-directory file —
EAEXEC.EXE, ealtest.exe — versus an app-directory one.)
- A MODIFY block is
file_mod_flags u16,src_countanddst_countVLIs, a reservedu32, apayload_len u32, then the source and destination entry descriptors, then the compressed diff (payload_lenbytes). - A NEW block is
src_countVLI,usize u32(decompressed size),csize u32(the compressed length), then the entry descriptor(s), then the compressed file (csizebytes). Records are contiguous, soblock_off + csizeis the next record — the walk covers all eight without decoding anything.
Entry descriptor — a 24-byte descriptor (8.3 name; the file size is a u32 at
offset 16), a 10-byte checksum block (§10, below), and in extra mode 8 timestamp
bytes plus an alt-path lp_string carrying the full filename. The reconstructed
size is the first destination entry's file size.
File Inventory¶
The compressed diff is a single MSB-first bit stream:
| Bits | Field | Meaning |
|---|---|---|
| 16 | magic | 0xB59C |
| 8 | literal_mode | 0 → literals use adaptive Huffman; nonzero → raw 8-bit literals |
| 8 | reserved | consumed, discarded |
| 12 | init_period | Huffman frequency-reset period |
| 12 | upd_period | Huffman update period |
| 4 | window_flag | 8 → 8 KB window / 7 distance low-bits; else 4 KB / 6 |
Three adaptive-Huffman alphabets follow — literal (256 symbols), length (64),
distance (64) — all sharing the one bit cursor. Each is an order-0 adaptive model
(a level / group / slot canonical structure with periodic weight-halving rebuild);
an escape symbol introduces an unseen literal by reading a fixed number of raw
bits. FA's msapi header decodes to literal_mode=0, init_period=64,
upd_period=16, window_flag=8.
Token loop. Read one flag bit. 0 → a literal (adaptive-Huffman symbol, or 8
raw bits when literal_mode != 0); emit the byte. 1 → a back-reference:
dist_lo = window raw bits, dist_hi = a distance-alphabet symbol,
dist = (dist_hi << window) | dist_lo; dist == 0 ends the stream; otherwise
length = a length-alphabet symbol masked to 7 bits, and length bytes are
copied from output[pos − (dist + 1)], reading zero when pos < dist + 1 (a
zero-initialised window, needed for the long zero runs in a PE header). For a NEW
record these bytes are the file; for a MODIFY record they are the opcode stream.
Engine Notes¶
fx patch (lib/src/rtpatch.cpp) executes the reconstruction: it is what proves
the format understood — the byte-identical output is the proof. A MODIFY record's
decompressed bytes are a program (§9) that rebuilds the destination against the
source. The interpreter keeps a write cursor, a separate poke cursor, a
gap list of literal holes, a template dictionary, and a zero-initialised
output buffer of the destination size.
| Op | Name | Effect |
|---|---|---|
| 0x01 | END | terminate |
| 0x02 | SET_SOURCE | select source; reset write and poke cursors |
| 0x03/0x04 | COPY (+gap) | copy cnt source bytes at an absolute offset; a leading advance records a gap first |
| 0x05 | FLUSH | record a final gap to end-of-file, then fill every gap in order with literal bytes from the stream |
| 0x06 | POKE1 | poke cursor += seek; add a signed delta to the byte there |
| 0x07/0x0D/0x0E | POKE1×N | N running 1-byte delta-adds (constant or per-entry delta) |
| 0x08 | STORE | append a {source, offset, count} copy template (emits nothing) |
| 0x09/0x0A | TCOPY (+gap) | COPY through a stored template |
| 0x0B/0x0C | ZFILL (+gap) | write a run of zero bytes |
| 0x0F/0x10 | POKE16/32×N | N running 2- or 4-byte little-endian delta-adds |
| 0x11–0x16 | FILL1/2/4 (+gap) | repeat a 1/2/4-byte pattern for a byte count |
Copies address the source at an absolute offset. Pokes are delta-adds, not overwrites — the little-endian value at the poke cursor is incremented; the cursor accumulates within an op and resets at SET_SOURCE and the multi-poke ops (but not 0x06). Unchanged regions are emitted as copies, leaving holes that the single late FLUSH backfills with the stream's remaining literal bytes.
Source validation (§10). Each source entry carries a dual rolling checksum.
Both start at zero and fold each byte as w = rotl8(w ^ c) within N bits — w1
at 31 bits (0x7FFFFFFF), w2 at 30 bits (0x3FFFFFFF). fx patch apply
verifies the on-disk source's w1/w2 before applying, so a wrong 1.00F version
is refused rather than silently corrupted.
Validated. The fa_patch_apply integration test (behind FX_FA_PATCH) applies
fae102.exe to the ESA-sourced 1.00F originals and checks each output's SHA-256
against a committed manifest: FA.SMS, FA_1.LIB, FA_2.LIB, the added
msapi.dll, and the official FA.EXE all reconstruct byte-for-byte (the first
four also match a licensed install). A licensed install's FA.EXE may differ by a
byte if it carries a no-CD crack — a JNZ→JZ flip in the CD check — which is a
property of that install, not of the patch.
Open Questions¶
- Several reserved header words, the per-record 10-byte metadata block, and the
combine_idare consumed structurally but not interpreted. [#54] - fx_lib decodes and applies patches; it has no encoder, so the format is
documented
read-only (authoring needs Pocket Soft's differ).
Related¶
RGN — Installer UI Region Map (.RGN)¶
.RGN files define named rectangular regions within the EA installer's bitmap
UI assets. They are used for two purposes: hit-test regions (POSTER.RGN maps
screen clicks to button labels) and sprite-atlas lookups (BUTTONS.RGN maps
button labels to pixel regions within the button-state sprite sheet). Both
live in the Disc 1 root alongside the installer executable — not packed into
any LIB archive.
Tools¶
fx¶
fx rgn info <file.RGN> # record count + round-trip check
fx rgn dump <file.RGN> # per-record name and rectangle
rgn_read/rgn_write round-trip byte-identically and enforce the size
invariant (4 + 40 × count). The shipped files live on Disc 1 (not in any
LIB or the install directory), so the suite is synthetic-only until a bench
session with the disc mounted spot-checks POSTER.RGN/BUTTONS.RGN.
File Layout¶
All multi-byte integers are little-endian.
| Offset | Size | Type | Description |
|---|---|---|---|
0x00 |
4 | u32 | Record count |
0x04 |
40×N | Records (40 bytes each) |
Record layout:
| Offset | Size | Type | Description |
|---|---|---|---|
+0x00 |
4 | char[4] | Name — ASCII label, null-padded (e.g. B1\0\0, NE1\0) |
+0x04 |
4 | u32 | Vertex count (always 4 in both files) |
+0x08 |
32 | u32[8] | 4 × (x, y) — clockwise from top-left: (x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max) |
All 104 records across both files have vertex count = 4 (axis-aligned rectangles). The format supports arbitrary polygons (count field is variable), but all current records are rectangles.
The count field at offset 0 fully determines the file size (4 + count × 40):
| File | Count | Expected size | Actual size |
|---|---|---|---|
| POSTER.RGN | 8 | 324 bytes | 324 bytes ✓ |
| BUTTONS.RGN | 96 | 3,844 bytes | 3,844 bytes ✓ |
File Inventory¶
| File | Size | BMP companion | Role |
|---|---|---|---|
POSTER.RGN |
324 bytes | POSTER.BMP (468×451, 8bpp) |
8 hit-test click regions on the splash screen |
BUTTONS.RGN |
3,844 bytes | BUTTONS.BMP (1500×456, 8bpp) |
96 sprite-atlas entries for all button states |
POSTER.RGN — Splash Screen Click Regions¶
8 records. Coordinates are in screen pixels within the 468×451 POSTER.BMP image. Buttons cover the lower portion of the poster (y = 328–442).
| Name | x min | x max | y min | y max | Width | Height |
|---|---|---|---|---|---|---|
| B1 | 0 | 128 | 328 | 366 | 128 | 38 |
| B2 | 128 | 340 | 328 | 366 | 212 | 38 |
| B3 | 340 | 468 | 328 | 366 | 128 | 38 |
| B4 | 0 | 234 | 366 | 404 | 234 | 38 |
| B5 | 234 | 468 | 366 | 404 | 234 | 38 |
| B6 | 0 | 128 | 404 | 442 | 128 | 38 |
| B7 | 128 | 340 | 404 | 442 | 212 | 38 |
| B8 | 340 | 468 | 404 | 442 | 128 | 38 |
Layout (three rows, variable column widths):
y=328 ┌──B1──┬────B2────┬──B3──┐
y=366 ├────B4────┬────B5────┤
y=404 ├──B6──┬────B7────┬──B8──┤
y=442 └──────┴──────────┴──────┘
x=0 128 340 468
BUTTONS.RGN — Button Sprite Atlas¶
96 records. Coordinates reference pixel regions within the 1500×456 BUTTONS.BMP sprite sheet. The installer engine blits the appropriate region onto the screen when rendering button states.
Naming scheme — format: [state][row][col]
| Position | Values | Meaning |
|---|---|---|
| state | N | Normal |
| F | Focus / hover | |
| P | Pressed / active | |
| D | Disabled | |
| row | E | Top row group (y = 0–152) |
| F | Middle row group (y = 152–304) | |
| G | Bottom row group (y = 304–456) | |
| col | 1–8 | Column index within the sprite sheet |
Sprite sheet layout — each state row contains 4 sub-rows of 38px each (one per state: N/F/P/D stacked vertically). Each of the 8 columns has a fixed x-range:
| Col | x min | x max | Width |
|---|---|---|---|
| 1 | 0 | 128 | 128 |
| 2 | 128 | 340 | 212 |
| 3 | 340 | 468 | 128 |
| 4 | 468 | 702 | 234 |
| 5 | 702 | 936 | 234 |
| 6 | 936 | 1064 | 128 |
| 7 | 1064 | 1276 | 212 |
| 8 | 1276 | 1404 | 128 |
Row group y ranges: E = 0–152, F = 152–304, G = 304–456. Each group has 4 state sub-rows of 38px each. Total sprite sheet used area: 1404×456 px (96 px at the right of the 1500px-wide BMP is blank/padding).
Related¶
Formats: SSF — EA installer script that drives the UI; references button labels by name.
SSF — EA Installer Script (.SSF)¶
.SSF files are plain-text EA installer scripts that drive the FA
installation process. All three files reside in the Disc 1 root alongside the
installer executable. None are packed into any LIB archive.
Tools¶
fx¶
fx ssf info <file.SSF> # keyword summary + round-trip check
fx ssf dump <file.SSF> # every statement with its arguments
Line storage rides the shared line-preserving engine (lib/src/txt.cpp), so
any input round-trips byte-identically; keywords and their comma-separated
arguments are extracted as an overlay. Disc-1-only format (like
RGN), so the committed fixtures are synthetic; the three retail
scripts are exercised by running the install engine below against the mounted
discs.
Reading a script is half the story. Executing one is fx install, which
resolves these directives against the ESA archive and writes the
game to disk — see Engine Notes:
fx install plan <disc-dir>… [-d dir] [--minimal] [--json] # the dry run
fx install run <disc-dir>… -d <dir> [--verify] [--overwrite]
fx install verify <disc-dir>… -d <dir> # byte-compare the install to the disc
File Layout¶
Plain ASCII text. # begins a comment. Keywords are all-caps.
| Keyword | Arguments | Effect |
|---|---|---|
COMPANY_NAME |
"string" |
Sets company name string (used in registry keys and Start Menu path) |
APP_NAME |
"string" |
Sets application name string |
DEFAULT_PATH |
"\path" |
Suggested install path (no drive letter; installer picks drive) |
INSTALL_SCRIPT |
"file.SSF", "label" |
References a sub-script; label may embed locale tags (":0409:English:0C:French") |
CREATE_FOLDERS |
"[INSTALL_PATH]" |
Creates the install directory |
INSTALL_FILES |
"glob", "archive_label", "dest" |
Copies files matching glob from named archive to destination |
INSTALL_SYSFILES |
"filename", "archive_label" |
Copies a file to %WINDIR%\SYSTEM |
SKIP_ON_REMOVE |
"glob" |
Marks files to skip deletion during uninstall |
REGEXE |
"path\app.exe" |
Registers executable with the OS |
ADD_GROUP |
"Company\App" |
Creates a Start Menu program group |
GROUP_ITEM |
"group\name","path\exe" |
Adds a Start Menu shortcut |
DESKTOP_ITEM |
"name","path\exe" |
Adds a desktop shortcut |
DIRECTX |
"label",major,minor |
Invokes DirectX component install |
File Inventory¶
| File | Role |
|---|---|
SETUP.SSF |
Master installer script — sets app metadata and references the two sub-scripts |
FINSTALL.SSF |
Full install — copies all assets including FA_4B.LIB (digital audio) |
MINSTALL.SSF |
Minimal install — omits FA_4B.LIB; music uses MIDI only |
SETUP.SSF¶
Sets COMPANY_NAME "Jane's Combat Simulations", APP_NAME "Fighters
Anthology", DEFAULT_PATH "\JANES\Fighters Anthology", then references both
sub-scripts:
INSTALL_SCRIPT "MINSTALL.SSF", ":0409:Minimal Install - Midi Music:…"
INSTALL_SCRIPT "FINSTALL.SSF", ":0409:Full Install - Digital Music:…"
Both FINSTALL.SSF and MINSTALL.SSF install:¶
| File/Glob | Archive label | Destination |
|---|---|---|
FA.EXE |
FA_EXECUTABLE_FILES |
[INSTALL_PATH] |
FA.SMS |
FA_EXECUTABLE_FILES |
[INSTALL_PATH] |
*.* |
FA_README |
[INSTALL_PATH] |
*.* |
FA_INTERNET |
[INSTALL_PATH] |
FA_1.LIB |
FA_LIBS |
[INSTALL_PATH] |
*.* |
FA_MISC |
[INSTALL_PATH] |
FA_2.LIB |
FA_LIBS |
[INSTALL_PATH] |
FA_4D.LIB |
FA_LIBS |
[INSTALL_PATH] |
WAIL32.DLL |
FA_SOUND_DRIVER_FILES |
[INSTALL_PATH] |
*.DLL |
COMMDRV_DLLS_FILES |
[INSTALL_PATH] |
FINSTALL.SSF additionally installs:¶
| File/Glob | Archive label | Destination |
|---|---|---|
FA_4B.LIB |
FA_LIBS |
[INSTALL_PATH] |
FA_3.LIB is absent from both manifests — it is CD-resident and loaded directly from the disc at runtime.
Both scripts also install to the Windows system directory:¶
| File | Archive label | Destination |
|---|---|---|
EAREMOVE.EXE |
REMOVER_EXECUTABLE_FILE |
%WINDIR%\SYSTEM |
EAEXEC.EXE |
EXEC_EXECUTABLE_FILE |
%WINDIR%\SYSTEM |
These two are the EA installer's own uninstaller and launcher — the game loads
neither. They are the only entries in SETUP.ESA whose flags field is
0x0221 rather than 0x0211 (ESA § Entry flags),
which is what identifies the field as a destination-class selector.
FA_4B.LIB contains the digital audio tracks (.11K music files). FA_4D.LIB
contains additional assets installed in both configurations.
Files skipped during uninstall (SKIP_ON_REMOVE):¶
*.P (pilot files), *.BKP (pilot backups), *.M (mission files), *.MT
(mission text), *.MM (mission maps), EA.CFG, MODEM.DAT, NET.DAT,
SERIAL.DAT, *.RAW (screen captures), *.GID (WinHelp temp files)
Engine Notes¶
fx install (lib/src/install.cpp) executes these scripts. It is what proves
the format is understood: a spec of a script language is a claim about what the
script does, and the only way to check that claim is to run it and compare the
result against a real installation.
A disc is a directory — an ISO mount, an extract of one, or the drive itself. The engine is three stages, and only the ends touch a disk:
| Stage | ||
|---|---|---|
install_scan |
I/O | directory → loose files + the ESA directory + the parsed scripts |
install_plan |
pure | → an InstallPlan: every file, every directive, every byte |
install_execute / install_verify |
I/O | write it; then byte-compare it back |
The middle stage is a pure function of scanned metadata, which is why the planner is unit-tested and fuzzed with no media at all.
What the engine does with each directive¶
INSTALL_FILES "glob","label","dest" selects every SETUP.ESA entry
carrying label whose name matches glob. The glob is DOS: *.* is
everything, *.DLL is a suffix, anything else is an exact (case-insensitive)
name. Three of the eleven directives use *.* — the label, not the glob, is
doing the selecting.
The destination argument is confined. It comes off the disc, so it is
untrusted like any other parsed field: "[INSTALL_PATH]\..\..\WINDOWS" resolves
to WINDOWS/, not to somewhere above the install directory, and a drive letter
is dropped. The file half is neutralised by
esa_safe_name. The retail scripts only ever say
"[INSTALL_PATH]", so nothing real is given up.
Script choice is data-driven, not label-driven. SETUP.SSF names its two
sub-scripts with localised prose (":0409:Full Install - Digital Music:0C:…"),
so nothing machine-readable says which is the full one. The engine resolves both
and takes the larger set. On the retail disc they differ by exactly one entry,
FA_4B.LIB.
INSTALL_SYSFILES is recorded and never performed: there is no Windows
system directory to write to, and the two files it names belong to the EA
installer rather than to FA.
SKIP_ON_REMOVE is documented as an uninstall hint, but it is also the
only statement in the scripts that says which files the game writes rather
than the installer — pilots (*.P), missions (*.M/*.MT/*.MM), EA.CFG,
screen captures (*.RAW). The engine reads it as a clobber guard: a file
matching one of these globs is never overwritten, even with --overwrite.
EXAMPLE.MT is the sharp case — the archive ships it and *.MT guards it, so a
fresh install writes it and a re-install keeps the one the user edited.
The shell and registry directives (REGEXE, ADD_GROUP, GROUP_ITEM,
DESKTOP_ITEM, DIRECTX) are reported as not honored, with a reason, rather
than silently dropped. On the retail FINSTALL.SSF — 33 statements — that is 8,
plus the 2 INSTALL_SYSFILES. An install that quietly ignores ten directives is
not a documented install.
The CD-resident LIBs are a rule, not a list¶
FA_4C.LIB and FA_7.LIB (Disc 1) and all five Disc 2 LIBs appear in no
script: the game read them off the CD at run time. The engine copies every
loose *.LIB in a disc root that the archive does not supply, which yields
exactly those seven without a hard-coded manifest, and carries to the other EA
titles that share the format. Names are upper-cased on the way in, because the
case a disc shows is a property of the mount, not of the game (an ISO9660 mount
may hand back fa_4c.lib).
What a full install is¶
Running both retail discs through the planner: 27 files, 1,036,798,285 bytes
(989 MiB), of which the archive supplies 20 and the CD-resident rule 7. Two
entries are recorded and skipped (the INSTALL_SYSFILES pair), and
PKCOMP.IDKDECODLL — which no directive references — is never installed at all.
Payloads stream, so peak memory over a 989 MiB install is ~5 MB.
Verified against a real installation¶
The engine's output is compared, file by file, against a licensed 1.02F install
of the game. Of the 19 files a minimal install writes, 14 are byte-identical
and 4 differ — exactly the four the 1.02F patch rewrites (FA.EXE,
FA.SMS, FA_1.LIB, FA_2.LIB). The 19th, README.TXT, that install does not
carry at all. Nothing else diverges: the scripts, the archive, and the glob
semantics are confirmed end to end, and the only divergence is the one the patch
explains. The disc is the 1.00F build; see
ESA § File Inventory for what that means for the symbol
database, which describes 1.02F.
This is a test, not a one-off measurement — the fa_disc_install cross-build
oracle (development.md § Real-media install mode),
run on a bench that has both the discs and an install. It asserts the difference
set is exactly those four files, so an unexpected divergence fails rather than
going unnoticed; when the RTPatch codec lands, the set goes empty. The same test
pins the engine rules above: the script choice, the SKIP_ON_REMOVE clobber
guard (EXAMPLE.MT survives even --overwrite), the two recorded-not-written
INSTALL_SYSFILES, the CD-resident rule, and the fact that a plan is identical
whichever order the discs are named in.
fx install verify re-derives each file from the disc and byte-compares it, so
it also reports a legitimate divergence: edit a mission and it will tell you
that .MT no longer matches the disc.
Related¶
Formats: LIB — the archives the installer copies;
ESA — the archive the INSTALL_FILES labels select entries from, and
the other half of what fx install executes; RGN — installer UI
region maps (POSTER.RGN, BUTTONS.RGN) on Disc 1.
Text
TXT — In-Game Text and UI Layout (.TXT)¶
FA_2.LIB contains 8 .TXT files. The extension covers three distinct uses of
the same directive engine: campaign selection descriptions, interactive UI
screen templates, and plain-text content (credits). All are plain ASCII,
CRLF.
Tools¶
fx¶
fx txt info <file.TXT> # kind + directive structure + round-trip check
The parser is line-preserving — txt_read/txt_write round-trip any input
byte-identically while exposing the directive structure — and is written for
reuse by the .MT briefing codec (#108), which shares the directive engine.
File Layout¶
Plain text; no binary fields.
Directive Engine¶
The .TXT format uses the same directive engine as .MT briefing files, plus
two UI-specific additions:
| Directive | Description |
|---|---|
.section <N> |
Begin numbered section |
.header |
Switch to header render style (may be used inline: .header <text> .body) |
.body |
Switch to body render style |
.center |
Center-align subsequent text |
.left |
Left-align subsequent text |
.underline |
Enable underline |
..underline |
Disable underline |
.page |
Page break — advance to next screen without starting a new section |
.button <label> ..button |
Interactive button element; label text is between the open/close tags |
.picture |
Image placeholder — engine renders the current context image (e.g. nose art) |
Campaign Description (6 files)¶
Two-section structure. Section 2 contains only END — a sentinel the engine
uses to detect end-of-file.
.section 1
.header
<Campaign title>
.body
<One-paragraph description>
.section 2
END
Example — BALTIC.TXT:
.section 1
.header
The Baltics 2009
.body
Fly missions over Estonia, Latvia, and Lithuania, defending them from aggression.
.section 2
END
UI Layout Template (SHWPILOT.TXT)¶
Defines the Pilot Service Record screen. Uses .page to separate tabs,
.button for navigation controls, and .picture as the image render target
for nose/tail art. Button content between .button and ..button is the
button label; an empty .button ..button pair defines an editable input
field.
.center
.header PILOT SERVICE RECORD .body
.left
NAME: .button ..button
CALLSIGN: .button ..button
.page
.center
.header NOSE ART .body
.picture
.button Previous Picture ..button .button Next Picture ..button
Plain Text (CREDITS.TXT)¶
No directives. Raw ASCII credits block rendered verbatim.
File Inventory¶
| File | Size | Type |
|---|---|---|
| BALTIC.TXT | 147 B | Campaign description |
| EGYPT.TXT | 136 B | Campaign description |
| KURILE.TXT | 171 B | Campaign description |
| UKRAINE.TXT | 151 B | Campaign description |
| VIETNAM.TXT | 154 B | Campaign description |
| VLAD.TXT | 149 B | Campaign description |
| SHWPILOT.TXT | 947 B | UI layout template (Pilot Service Record screen) |
| CREDITS.TXT | 1036 B | Plain text (no directives) |
All 8 live in FA_2.LIB.
Related¶
Formats: CAM — campaign definitions paired with campaign
description .TXT files; MNU — the campaign selection menu that
renders these descriptions; MT — mission briefing text using the same
directive engine (without .button/.picture); P — pilot save data
displayed by SHWPILOT.TXT.
Engine
FA Game Architecture¶
The integrating overview of Jane's Fighters Anthology's runtime — the map that ties together the completed reconstruction. The game executable is documented as 20 named subsystems, and the binaries it ships alongside as a further set (7 binaries in all); each subsystem has its own page with recovered symbols, a struct/field map, and a theme-aware flow diagram, all indexed in the reconstruction matrix. This page gives the cross-subsystem picture — the runtime environment, the asset system, the overlay-DLL architecture, and how the pieces fit — and links out to that detail. For the file formats see formats/; for the newcomer index see the knowledge-base README.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied, now driven by the machine-readable
db/symbol database. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
Runtime Environment¶
The game executable is a Win32 application built for Windows 95/98, approximately 1.2 MB in size. It runs on modern Windows through the compatibility layer without significant modification.
A binary symbol map, FA.SMS, ships with the game and is a gold mine for RE work. It contains 3,829 MSVC C++ mangled function and variable names with their virtual addresses, spanning the range 0x00401000–0x005937E0. Loading this map into Ghidra or IDA auto-names nearly every significant function in the executable with no manual effort required. See formats/SMS.md for the file structure (4-byte count + N × 8-byte [VA, strOffset] records + null-terminated string table).
Asset System — EALIB Archives¶
All game assets are packed into .LIB files using the EALIB container format. See formats/LIB.md for the full directory entry layout.
Container structure: magic EALIB, 18-byte directory entries per file. The flags byte in each entry determines compression:
| Flag | Compression |
|---|---|
0 |
Raw (no compression) |
1 |
LZSS |
3 |
PXPK |
4 |
DCL-Blast (implemented in lib/src/blast.cpp) |
Key LIB files:
| File | Location | Contents |
|---|---|---|
FA_1.LIB |
Install | Fonts, icons (~1,986 PICs, 15 .FNT overlay DLLs) |
FA_2.LIB |
Install | Main assets — ~5,405 entries covering most game data |
FA_3.LIB |
Disk 2 | Aircraft skins and tech sheets (822 PICs, 269 INF files) |
FA_4B.LIB |
Install | Digital audio (full install only) |
FA_4C.LIB |
Disk 1 | CD audio |
FA_7.LIB |
Disk 1 | Briefing FMVs (355 VDO + 355 FBC files) |
Asset referencing: Assets are referenced by name with a ~ prefix in other files (e.g. ~f22h resolves to the F22H asset from whichever LIB contains it). Special filename prefixes carry semantic meaning:
| Prefix | Meaning |
|---|---|
& |
Looping ambient sound |
^ |
One-shot voice callout |
$ |
Weapon icon PIC |
_ |
Aircraft skin PIC |
~ |
Campaign-specific variant or destruction-state replacement. Two distinct uses: (1) destruction state — e.g. ~BNK5.OT is "Damaged Shelter 5", placed by the engine in place of BNK5.OT after destruction (different shape, zeroed armor, bit 5 of ot_flags cleared); (2) campaign/theater variant — e.g. ~COLTWR.OT adds a loop sound and sets an extra ot_flags bit for a specific theater, while ~MOOSE.JT is a stand-alone Baltic-campaign weapon with no base counterpart. Mission scripting exposes do_ifdestroyed (FA.SMS 0x004D22D4) and _MISSIONFortDestroyed for conditional destruction tracking. |
Overlay System — Win32 PE DLLs¶
Many game subsystems are implemented as hot-swappable Win32 PE DLLs loaded at runtime. These DLLs import from main.dll and export named C++ functions.
main.dll = the game executable. main.dll does not exist as a file on disk and is not packed in any LIB archive. It is a logical module name used in the overlay DLLs' PE import tables to refer to the game executable's own address space. The functions imported under this name (_DrawAction, _DrawRocker, _DrawText, _DrawFormattedText, _DrawEditBox, _DrawCampaignList, _okString, _cancelString, _T_HorizonProc, and others) are all internal the game executable routines confirmed via FA.SMS (e.g. _DrawAction at 0x00489B90, _DrawText at 0x00489AC0, _T_HorizonProc exported as T_HorizonProc). The game executable's PE export table lists only 2 functions (T_HorizonProc, WRFogLayerUpdate) — the drawing functions are not among them. The overlay loader resolves "main.dll" imports by patching the overlay DLL's IAT directly with the game executable address-space pointers.
Loading mechanism. The engine does not use Windows LoadLibrary for overlays — it maps them with its own in-process PE loader, which is why the non-existent main.dll never troubles Windows. The call site is generic across every overlay type, not .LAY-specific: the resource manager's RMFindAndLoad (0x004a6b30, reached via _RMAccess@8 / _RMAccessHandle@8) reads the raw file with _LoadFile@16, tests it with _IsDLL@4 (0x0041e910 — accepts both PE\0\0 and Phar Lap PL\0\0 magic), and hands any PE-packaged resource to _LoadDLL@16 (0x0041eb60). _LoadDLL@16 walks the section table (_FindSection over .text/.data/.rdata/.bss/.reloc/.idata), allocates a handle, copies each section, applies relocations, and resolves imports — patching the main.dll IAT entries by name against the game executable's own address space. So .LAY's per-type LAY_Load handler (ParseLayerFile/_WRInit@4 0x004b4370) is just the <type>_Load callback dispatched by RMFindAndLoad; the actual DLL mapping is the shared _LoadDLL@16 path. Windows LoadLibraryA is imported, but only at startup (a real system DLL), never for overlay assets.
Most overlay DLLs decompress to 4,608 bytes — a fixed-size slot in the engine's overlay pool. Exceptions: .FNT files vary (e.g. 4X12.FNT = 12,800 bytes), and .LAY files are substantially larger (16,896–20,992 bytes) to accommodate embedded rendering lookup tables.
All overlay DLLs use Phar Lap PE format — the signature bytes are PL\0\0, not the standard PE\0\0. The CODE section is typically a data-only structure (dispatch table, bytecode script, or lookup tables) with no compiled x86 instructions; only .BI and .MC DLLs contain actual machine code.
Overlay DLL types:
| Extension | Count | Purpose |
|---|---|---|
.HUD |
46 | Cockpit layout — one per aircraft |
.MNU |
12 | Menu screens |
.DLG |
92 | Dialog boxes |
.CAM |
6 | Campaign logic |
.MUS |
9 | Music sequencer/playlist |
.LAY |
24 | Sky and atmosphere rendering |
.HGR |
— | Hangar screen |
.FNT |
15 | Font glyph bitmaps |
.PTS |
— | Aircraft icon lookup |
.MC |
21 | Mission condition evaluator |
.BI |
9 | AI runtime library |
For disassembly: load the DLL in Ghidra, import the FA.SMS symbol list to auto-name engine imports, then trace from the exported entry point.
Object Type System — Brent's Relocatable Format (BRF)¶
Seven file types share a plain-text assembly-like DSL invented by Brent "Buzzboy" Iverson at Electronic Arts. See formats/BRF.md for the full format specification. Implemented in lib/src/brf.cpp.
Magic header: [brent's_relocatable_format]
Directives: byte, word, dword, ptr, symbol, string, end
Labels: :name
Hex literals: $XX
Fixed-point values: ^XXXXX
The struct_type byte at position 1 distinguishes object categories:
| Type byte | Extension | Object category |
|---|---|---|
1 |
.OT |
Static object |
3 |
.NT |
NPC / ground vehicle |
5 |
.PT |
Playable aircraft |
7 |
.JT |
Projectile / weapon |
8 |
.GAS |
Fuel tank |
9 |
.ECM |
ECM pod |
10 |
.SEE |
Seeker / sensor |
All types share an OBJ_TYPE section (hitpoints, shape reference, year, sounds, movement). PT and JT/NT add further sections (aerodynamics, hardpoints, seeker parameters).
utilProc symbol mapping:
| Symbol | Applies to |
|---|---|
_OBJProc |
Static objects (OT) |
_GVProc |
Ground vehicles and naval vessels (NT) — IOWA and KIROV confirmed; likely dispatches by obj_class internally |
_PROJProc |
Projectiles (JT) |
| PT-specific | Playable aircraft |
^ fixed-point range fields: Confirmed 1 unit = 1 foot across sensor and weapon files (e.g. ^60760 = 10 nm, ^360000 = 60 nm). 6,076 units = 1 nautical mile.
obj_class word — confirmed values:
| Value | Class |
|---|---|
$40 |
Scenery / terrain feature (non-targetable) |
$100 |
Ground structure (building, runway) |
$2000 |
Naval vessel |
The OpenFA project (GitLab, Rust) is the primary external reference for BRF format details, particularly for OT, NT, and PT types.
AI System¶
The AI system consists of two tightly paired file types per object category. See formats/AI.md and formats/BI.md.
.AI files (plain text): goto-based scripts. Each line follows the pattern:
LABEL: CONDITION ACTION
The interpreter evaluates conditions and dispatches to named action and condition functions by string lookup at runtime.
.BI files (Win32 PE DLL): the AI runtime library. Exports two families of functions by name:
_CTDo_*— action functions:btoh,circle,exit,homeangle,homepos,immelman,invert,jink,maneuver,move,movetoalt,restart,wm_approach,wm_break,yoyo_CTEval_*— condition functions:alt,altdiff,speed,tgt,tgtahead,tgtisfighter,engagep, and others
FA_2.LIB contains 9 .AI/.BI pairs, one per major object category (e.g. F.AI/F.BI for fighters).
Campaign & Mission System¶
Campaign Logic (.CAM)¶
Six .CAM files, each a Win32 PE DLL implementing campaign logic for one theater. Each DLL embeds mission lists (e.g. ~U01.M–~U50.M for the Ukraine campaign), weapon and sensor tables, aircraft type names, and campaign state strings. See formats/CAM.md.
Exported API includes: _AddCampaignPlane, _InitCampaignPilot, _SeqStart, plus campaign-specific functions such as _UkraineAddA7 and _VietnamMedals.
Loading mechanism: FUN_00428412 (0x428412) is the canonical mission/campaign loader — called from the mission-map screen handler FUN_00422a71. It shuts down the prior mission, selects the .mc_M campaign script by NATO mode, dispatches through _CallMissionProc_8 (0x481940), runs _MISSIONInit2_0, and finalizes terrain dictionaries. _CallMissionProc_8 is also called directly by the main loop ?usnfmain@@YAXXZ, _MISSIONCheckSuccess@0 (0x486860), and _MISSIONTextProc@16 (0x481c10).
Mission Definitions (.M, .MT, .MM, .MC, .SEQ)¶
| Extension | Count | Description |
|---|---|---|
.M |
517 | Mission definition files. See formats/M.md |
.MM |
75 | Theater/map layout files. See formats/MM.md |
.MC |
21 | Mission condition evaluator DLLs. See formats/MC.md |
.MT |
363 | Mission briefing text |
.SEQ |
126 | Cutscene sequencer scripts. See formats/SEQ.md |
.MC DLLs poll game state each tick via engine imports: @OBJAlias@8, _Dist@8, _OnTheGround@0, _PopCurObj@0, _PushCurObj@4, _playerId. They are only present for missions with non-trivial trigger logic.
MM runtime loader — MM load calls LibFileExists (0x47a130) to test whether each referenced asset exists. That function is the resource manager's general asset-existence predicate, not an MM-specific parser: it checks LIB membership first, then probes loose files on disk by extension via FUN_0047a510 (which forms a "%s\\%s" subdirectory path with Sprintf and tests it) — see memory-resource.md § LIB name resolution. Any trailing integer on a layer line (the slot index 0, 1, or 4 seen in shipped files) is left unconsumed and has no runtime effect.
Pilot Save Files (.PLT)¶
Binary, approximately 3.4 KB. Stores pilot name, rank, stats, campaign progress, and loadout. The campaign block contains the CAM filename, aircraft .PT reference, ordnance inventory (.JT filename + u8 quantity per slot), and sensor/ECM loadout. See formats/P.md. Field layout from offset 0xB0 to the campaign block start is not yet fully mapped.
HUD & UI System¶
Cockpit HUD (.HUD)¶
46 .HUD files, one per aircraft — each a Win32 PE DLL binding cockpit assets. References per-aircraft assets by ~<ac>h (HUD overlay PIC), ~<ac>s (symbol set PIC), ~<ac>_p (propulsion panel), ~<ac>_w (weapons panel), plus the hudsym and winfont fonts. Gauge positions are stored as signed s16 offset pairs relative to a per-aircraft anchor point (e.g. A7: 320,100 / F22: 249,100 / F14: 349,114). All HUD files have a fixed CODE virtual size of 0x2BB. See formats/HUD.md.
Fonts (.FNT)¶
15 .FNT files — Win32 PE DLLs with embedded glyph bitmap data. Filenames encode context (HUD*, WIN*, MAP*) and variant (00/01/11). 4X12.FNT is 12,800 bytes (larger than the standard 4,608 due to glyph payload). See formats/FNT.md.
Menu Screens (.MNU)¶
12 .MNU files — Win32 PE DLLs containing all UI label strings in the PE data section. Import _DrawAction, _DrawRocker, _DrawText from main.dll (= the game executable). One DLL per major menu screen. See formats/MNU.md.
Dialog Boxes (.DLG)¶
92 .DLG files — Win32 PE DLLs for dialog boxes. Import _DrawAction, _DrawRocker, _DrawEditBox, _DrawText, _DrawFormattedText, _DrawCampaignList, _cancelString, _okString from main.dll (= the game executable). See formats/DLG.md.
Bitmap Assets (.PIC, .PAL)¶
.PIC files are 8-bit indexed bitmaps. Distribution across LIBs:
| LIB | PIC count | Contents |
|---|---|---|
| FA_1.LIB | 1,986 | UI elements, icons |
| FA_2.LIB | 1,158 | General game assets |
| FA_3.LIB | 822 | Aircraft skin textures |
.PAL is the global color palette. See formats/PIC.md and formats/PAL.md.
Audio System¶
| Extension | Count | Description |
|---|---|---|
.XMI |
78 | Extended MIDI sequences for music and events. See formats/XMI.md |
.MUS |
9 | Win32 PE DLL music sequencer/playlist. See formats/MUS.md |
.11K |
— | Raw uncompressed PCM at 11 kHz |
.5K |
— | Raw uncompressed PCM at 5 kHz |
.8K |
— | Raw uncompressed PCM at 8 kHz |
PCM files use filename prefixes: & for looping ambient, ^ for one-shot voice callouts. Thousands of audio files are distributed across FA_2.LIB and FA_4B/4C/4D.LIB. See formats/11K.md.
.MUS CODE sections are bytecode scripts (not x86 code). Key opcodes: FF = playlist ID string, FA <sub> <u32> = setup/config, FB <mode> <idx> = play XMI track, FE <u32> = conditional branch, FD <u24> = loop/jump, FC = shuffle marker. All 9 playlists have been decoded; game-state IDs include "air", "deck", "launch", "valk", "brief", "menu", "eject", "succ", "home". See formats/MUS.md.
Game Loop & Initialization¶
Entry point: WinMain at 0x476120. Registers the window class, creates the DirectDraw surface, calls _LoadFAResources, then enters the shell event pump.
Separate game thread: ?CreateGameThread@@YAHXZ (0x476660) spawns the simulation thread. The shell runs on the main Win32 thread; all gameplay runs on the game thread.
Simulation loop: ?FlyingLoop@@YAXXZ (0x404C70) — NOT ?MainLoop. Per-frame sequence:
_timerTicks(hardware interrupt counter) gated by_frameTicksto enforce fixed timestep._TIMEUpdate— advances_currentTime,_currentTicks, and_timeCompression._OBJUpdate— iterates_objPtrsarray, calls each entity'sutilProcdispatcher._MISSIONCheckSuccess@0(0x486860) — polls active.MCDLL each tick._NetworkFrame/?MPReceive@@YGDXZ— if multiplayer, synchronizes entity state.render_3d→T_DefaultHorizon→ HUD overlay composite.
Mission init: ?_MISSIONInit1@@YGXXZ allocates the 300,000-byte object pool and chains 19 subsystem inits in order: TIME → OBJ → TERRAIN → NETWORK → AUDIO → HUD → ... → MISSION. Called by FUN_00428412 (the mission/campaign loader) immediately after DLL setup.
Object pool: _objPtrs (0x553848) is the entity list. _nextObjId (0x553838) is the free-ID counter. _cg (0x50CE80) is the current-object mirror — _ServiceObjects copies the object being updated into it (via GetCurObj) so handlers can read fixed addresses; _curId (0x4F6FBC) holds that object's ID.
See objects.md for the object/entity system (service chain, the _cg mirror, proc dispatch) and game-loop.md for the full WinMain init sequence, per-frame call order, and all subsystem init VAs.
Physics & Flight Model¶
Core flight tick: _FMFlight@0 at 0x47B020. Called once per frame per PT entity via _OBJUpdate. Reads the PT_TYPE struct (aerodynamics constants from the .PT BRF file) and the entity runtime struct, then integrates:
- Lift/drag/thrust: from PT fields —
one_g_stall_speed(word at +0x50), thrust/weight ratio, induced drag coefficient. - Stall logic:
_oneGStallSpeed__3JAsymbol; stall onset at computed speed, with 25-knot hysteresis on recovery. - Fuel consumption:
@FMFuelConsumption@4(0x451E50) reads PT fields for fuel flow at current throttle/altitude.
Collision: _Collision@56 — 14-parameter function covering missile–aircraft, missile–terrain, and aircraft–terrain cases. Terrain height query is _GetGround@0 (0x47AF70), which reads T2 tile elevation bands. See formats/T2.md for the tile record layout.
_PROJProc dispatch: _PROJProc at 0x4C1F50 is confirmed (2026-05-19 via dumpAtForced). It is a vtable-style dispatcher: case 1 = init/startup; case 2 = _PROJMoveProc (movement/physics, 0x4C11B0); case 3 = _PROJEventProc; case 4 = _PROJDamageProc. Both _PROJProc and _PROJMoveProc have no auto-created Ghidra function (vtable-only call pattern) — dumpAtForced is required to decompile them. The physics gap PROJ_TYPE+0x50–+0x6E is accessed through _PROJMoveProc.
Dark zone 0x4D0000–0x4EFFFF contains the software 3D rasterizer (matrix multiply, world-to-screen projection, polygon fill), not aerodynamics. The rasterizer entry is _GRTo2d@8; the matrix globals (m2–m9, _scaled_matrix) are 16-bit fixed-point elements at 0x515F44–0x515F54.
See physics.md for the full flight model equations, stall parameters, and collision function signatures.
Terrain & 3D System¶
Terrain Maps (.T2)¶
16 .T2 files — one per theater map. See formats/T2.md.
Structure: BIT2 magic, 128-byte header, then N_tiles × 195 bytes. Each tile contains 65 × 3-byte records:
- Record 0: tile summary
- Records 1–64: 8×8 sub-tile grid
Each 3-byte record encodes [surface_class, elevation_band, texture_variant]. 0xFF = water.
3D Shapes (.SH)¶
1,275 .SH files covering aircraft, vehicles, weapons, buildings, and terrain features. See formats/SH.md.
Sky & Atmosphere (.LAY)¶
24 .LAY files — Win32 PE DLLs implementing sky and atmosphere rendering. References wave1.SH and ocean*06.PIC. Imports _T_HorizonProc from main.dll (= the game executable) — the engine provides the horizon renderer; the LAY file supplies lookup table data only. See formats/LAY.md.
the game executable atmosphere subsystem — the engine maintains the following globals for the current atmosphere state:
| Global | Role |
|---|---|
currentTintTable |
Primary active-layer pointer (LAYER struct in loaded DLL) |
currentShadeTable |
Secondary active-layer pointer — overwritten each frame by SetActiveLayerByAngle based on camera elevation angle |
DAT_005843c4, DAT_005843c8 |
Additional active-layer slots (transition blending) |
hdr–DAT_00580e24 |
DLL data header block — 30 dwords copied verbatim from the loaded LAY DLL's CODE section at offset 0x1000 |
Per-frame update pipeline:
ParseLayerFile(0x004b4370, =_WRInit@4) — loads the LAY DLL through the shared_RMAccess@8→RMFindAndLoad→_LoadDLL@16overlay loader (not WindowsLoadLibrary— see Overlay System note above), copies header block tohdr, initialises active-layer pointers to the DLL's default LAYER entry, callsFindNearestColorEntry(0x004b3ad0) for each LAYER entry to populatecolour_entry_ptr, then loads cloud/sky PIC wildcards.UpdateSkyState(0x004b3d90) — per-frame: smooth-transitions all atmosphere parameters and applies the result to the working palette.WRFogLayerUpdate(0x004b4320) — per-frame: adds random jitter (±25, clamped to [217, 235]) to each LAYER'sfog_densityfield.SetActiveLayerByAngle(0x004cc4b4) — per-frame: reads camera elevation angle from AX, multiplies bysky_angle_scaleorbelow_angle_scale(from the header block), and writes the indexed LAYER pointer intocurrentShadeTable.GetFogColour(0x004b3410) — linearly interpolates the fog visibility ramp (vis_lo/vis_hioverfog_alt_low/fog_alt_high) and returns a palette colour from the LAYER's colour array.
The colour entry table (pointed to by header offset +0x6C) uses stride-0x30 entries: [terminator_byte (0 = valid)][R][G][B][count:u32][colour_array:u32[count]]. FindNearestColorEntry walks entries computing Manhattan RGB distance to find the best palette match for each LAYER's base_rgb.
3D Rendering Pipeline¶
Algorithm: Painter's sort — no z-buffer. Polygons are depth-sorted by centroid before submission to the rasterizer.
Scene entry: T_DefaultHorizon (0x4AACF0) — the terrain/sky scene renderer, exported from the game executable as T_HorizonProc. Called from the game loop render step after object updates. Drives the full frame render.
Pipeline stages (VA range 0x4B4200–0x4BEDFF):
- Shape loading — JPEG decoder cluster at
0x4B4BB0–0x4B7700handles.SHfile decompression. Decoded vertex/polygon data is cached in the shape pool. - World-to-screen projection —
_GRTo2d@8reads the rotation matrix globals (m2–m9at0x515F44–0x515F54), applies fixed-point transform, writes projected coordinates to_xv/_yv/_zv(0x51CDAA–0x51CDAE). - Clip testing —
codes_or/codes_andclip-code accumulators at0x515F83/0x515F82. Clip rectangle globals:_clipLeft(0x55BE20),_clipTop(0x556D60),_clipRight(0x55BEDC),_clipBottom(0x55C024). - Polygon fill —
render_3dentry (0x58F0E0scratch pointer). Overflow buffer at_overflow_ptr; line statistics at_lineStats(0x510288). - HUD overlay — composited after 3D scene. Advisory icon renderer at
FUN_00407930handles bits 6–11 of the HUD flags word (DAT_0050cfef).
WR atmosphere subsystem: WRFogLayerUpdate (0x004B4320), UpdateSkyState (0x004B3D90), SetActiveLayerByAngle (0x004CC4B4). Fog, palette animation, lens flare, and texture remap cache all live in 0x4B4200–0x4BEDFF.
Dispatch table: vector_table (0x5183A0) is the render dispatch vector table (function pointer array, 141 xrefs).
See renderer.md for the full pipeline, shape system, and camera/viewport details.
Briefing & Reference System¶
| Extension | Count | Location | Description |
|---|---|---|---|
.INF |
269 | FA_3.LIB | Aircraft tech sheets. See formats/INF.md |
.VDO |
355 | FA_7.LIB | Briefing video sequences. See formats/VDO.md |
.FBC |
355 | FA_7.LIB | Briefing file companion data. See formats/FBC.md |
.CB8 |
— | — | MPEG/video format for FMV cutscenes. See formats/CB8.md |
.BIN |
6 | — | Lookup tables and palette data. See formats/BIN.md |
.INF files use a custom dot-command markup: .body, .title, .center, .left directives for display, plus a machine-readable footer with LENGTH/HEIGHT/WINGSPAN/WEIGHT/PERFORMANCE key-value pairs parsed directly by the engine.
.BIN named files: MIX2L/MIX4L = floor(x/N) lookup tables; MIX2/MIX4 = gamma-corrected variants; VFONTPAL = 16 VGA 6-bit RGB palette entries for the video briefing font.
Multiplayer & Configuration¶
| File | Size | Description |
|---|---|---|
EA.CFG |
347 bytes | Binary game configuration (graphics, controls, audio, pilot slot). See formats/CFG.md |
NET.DAT |
3,552 bytes | Binary multiplayer network settings (IPX/TCP addresses, session config), mostly null-padded. See formats/DAT.md |
CN_INFO struct — total 0xDDC bytes. Written by CN_WriteConfig; read by CN_ReadConfig. Key fields:
| Offset | Size | Field |
|---|---|---|
| +0x00 | u32 | transport type (0=IPX, 1=TCP, 2=serial, 3=modem) |
| +0x04 | u8 | player name (32 bytes) |
| +0x28 | u32 | session host flag |
| +0x2C | u32 | max players |
| +0xDB0 | — | TCP config block (IP address string, port) |
Full 50+ field mapping in network.md.
Multiplayer frame sync: ?MPReceive@@YGDXZ (0x46C980) is the network frame entry. Dispatches on packet type byte (0x10–0x51). Key types: 0x10 = entity position update, 0x20 = weapon fire, 0x30 = kill notification, 0x40 = mission state sync. Serial transport uses SERIAL_QUEUE reliability layer with sequence numbers and ACKs.
Key globals: _numComputers (0x4EB604) = player count; _thisComputer (0x4EB608) = this machine's index; _gamePrefs (0x4EB6F8) = SP prefs struct pointer; _gameMultiPrefs (0x4EB6FC) = MP prefs struct pointer.
See network.md for the full protocol, transport layer details, and IP.EXE role.
Runtime Entity Struct — Key Offsets¶
The FA entity struct (one instance per live game object, base pointer stored per-object at runtime) has 80+ confirmed field offsets from Ghidra analysis. Selected key fields:
| Offset | Size | Role | Evidence |
|---|---|---|---|
+0x00 |
u16 | Object ID | _OBJInit@4, _nextObjId |
+0x02 |
u8 | Object type (_cgt) |
_T_AddObj@12 |
+0x04 |
u16 | Object class (obj_class) |
BRF utilProc dispatch |
+0x08 |
u32 | utilProc function pointer |
BRF loader |
+0x10 |
s16[3] | World position X/Y/Z | _GRTo2d@8, flight model |
+0x20 |
s16[3] | Velocity X/Y/Z | _FMFlight@0 |
+0x30 |
s16[3] | Orientation matrix row 0 | matrix multiply |
+0x40 |
u16 | Health / hitpoints remaining | _DAMAGEDoHit@12 |
+0x44 |
u16 | Armor value | BRF OBJ_TYPE load |
+0x60 |
u8 | AI state | _CTEval_* condition fns |
+0xF0 |
u32 | Target entity ID (NPC nav) | GAS init cases 0x03/0x05 |
+0xF4 |
u16 | Reaction parameter 1 | GAS init |
+0xF6 |
u16 | Reaction parameter 2 | GAS init; _Reaction_12 (0x464040), _MaskEvents_4 |
+0xF8 |
u16 | Reaction parameter 3 | GAS init |
+0xFA |
u8 | Mode byte | GAS init; _CTEval_tgt / _CTEval_p |
+0xFF |
— | Confirmed read by: _Kill@0 (0x473c10), @AmmoForClass@4 (0x474740) |
0xFF scan |
+0x100 |
u8 | Primary per-frame state byte — most-polled field in flight loop | _FMFlight@0, _MovePlane@0, _GVEventProc, CheckForEvents2, and ~15 others |
+0x101 |
u16 | Timeout timer | GAS init |
+0x10A |
— | Speed/energy field | _MaxSpeed@8 (0x477d50) |
+0x10C |
— | Campaign/init context — read by _CampaignSave, _CallCampaignProc@4, _MISSIONLoadCommonResources@0 |
0x10C scan |
+0x114 |
— | Init handle / capability field — read by ?InitCobra@@YAGPAUGlobalData@@@Z, ?InitVideo@@YAGPAUGlobalData@@@Z |
0x114 scan |
+0x16F |
u32 | HUD state flags word (DAT_0050cfef) — advisory bits, damage state, ejection triggers |
ChangePlaneType |
Full 80+ field table with all confirmed offsets is in structs.md.
Weapon / projectile entity offsets are documented separately in formats/JT.md (PROJ_TYPE runtime mapping at missile+0xA6 onward).
RE Resources¶
FA.SMS — Binary symbol map shipping with the game. 3,829 MSVC-mangled C++ symbols with virtual addresses. Load into Ghidra or IDA to auto-name all functions. Structure: u32 count + N × [VA u32, strOffset u32] + null-terminated string table (string table base at byte offset 30,636). See formats/SMS.md.
symbols.md — Organized reference of all 3,829 FA.SMS symbols, grouped by subsystem with demangled names. See symbols.md.
OpenFA project (GitLab, Rust) — Primary external reference for BRF/OT/NT/PT formats and the EALIB parser.
fighters-codex (this repo):
- fx CLI — LIB archive operations
- fxs — GUI editor for LIB archives
- lib/src/blast.cpp — DCL-Blast decompressor
- lib/src/brf.cpp — BRF format parser
FA Game Loop Architecture¶
Analysis of the game executable's main loop, initialization sequence, per-frame object dispatch, frame timing, mission init, and shutdown.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
Overview¶
The game executable's execution model is a multi-threaded Win32 application. The Win32 message loop lives on the main thread; the simulation runs on a dedicated game thread. A separate timer thread drives _timerTicks. The in-flight simulation loop (?FlyingLoop@@YAXXZ) is a do-while loop that runs until a scenario-end condition or player exit is detected.
1. WinMain / CRT Entry Point¶
_WinMainCRTStartup — 0x004D9D00 (FA.SMS: _WinMainCRTStartup)¶
The true PE entry point. It is the MSVC CRT startup stub:
- Calls
GetVersion()and stores the result in___winmajor,___winminor,___winver, andDAT_0051e754. - Initialises the MSVC heap (
__heap_init), multithreading (__mtinit), I/O (__ioinit), and the multibyte character table (___initmbctable). - Retrieves the command line (
GetCommandLineA) and environment (___crtGetEnvironmentStringsA); exits with code -1 if either is null. - Parses the command-line string (handling quoted paths) and calls
__setargv,__setenvp,__cinit. - Calls
GetStartupInfoAandGetModuleHandleA(NULL), then invokes_WinMain_16. - Does not return — calls
_exit(iVar3)with WinMain's return value.
_WinMain@16 — 0x00476120 (FA.SMS: _WinMain@16)¶
The application WinMain. Steps:
- Calls
?InitApplication@@YAHPAX@Z(0x004764B0), passing theHINSTANCE. If this returns zero (failure), WinMain returns 0 immediately. - Enters the standard Win32 message loop:
GetMessageA→DispatchMessageAuntilGetMessageAreturns 0. - Returns
msg.wParamas the exit code.
The window title is registered as "Fighters Anthology" (string at 0x004EB834), referenced from ?InitApplication@@YAHPAX@Z at 0x00476568 and from ?DisplayCopyright@@YAXPAX@Z at 0x0047682E.
?usnfmain@@YAXXZ — 0x00403700 (FA.SMS: ?usnfmain@@YAXXZ)¶
This is the game's own "main" — the outer shell / mission state machine that runs for the
whole session, complementing the inner in-flight loop FlyingLoop (§4). After one-time setup
(config load, _MOUSEInit, _InitGraphicsMode/_GRInit3d, _ShellMusic) it enters a
do { switch (_curScreen) … } while (_curScreen != 0x12) loop: each iteration renders one shell
screen, and the screen handler returns the next _curScreen. When a mission has been selected the
state reaches the launch state 0x12, at which point usnfmain calls
_CallMissionProc_8(&_missionName, …) to build the mission and then ?FlyingLoop@@YAXXZ (§4) to
fly it; on return it runs a post-mission dispatch (debrief / results) that sets _curScreen back
into the shell, and the outer loop repeats. Multiplayer sessions are kept in step throughout via
_MPConnect / _MPShellSendScreen / _MPWaitEveryoneStatus / _MPCheckConnection.
The _curScreen states (the switch arms):
| State | Screen / action | Handler |
|---|---|---|
0 |
Shell main menu (activity chooser) | _ChooseActivity@0 (0x4a08a0) |
1 |
Single Mission | _SingleMission_0 |
2 |
Quick-Mission creator | _CreateQuickMission_4 |
3 |
Pro-Mission creator | _CreateProMission_0 |
4 |
Replay | _CanReplay_0 |
5 |
Start Campaign | _StartCampaign_0 → _LoadCampaignProc / _CallCampaignProc_4 |
6 |
Continue Campaign | _ContinueCampaign_0 |
8 |
Aircraft Reference (CD-gated) | _AircraftReference_0, _CheckCD |
0xA |
View Pilots | _ViewPilots_0 |
0x12 |
Launch — run the loaded mission | _CallMissionProc_8 → _FlyingLoop |
0x17/0x18 |
Serial / Modem configuration | _RunSerialConfigurationScreen / _RunModemConfigurationScreen |
0x19/0x1A |
Net configuration (modes 1 / 4) | _RunNetConfigurationScreen_4 |
0x1B |
Disconnect | _RunDisconnectScreen |
0x1D |
Fort-Mission creator | _CreateFortMission_4 |
It also references the "Fighters Anthology" window-class string at 0x004EB760 (from offset
0x00403D41).
2. Initialization Sequence¶
Thread Launch¶
Before the simulation loop starts, ?CreateGameThread@@YAHXZ (0x00476660) spawns the game thread:
| Step | Detail |
|---|---|
CreateThread |
Spawns ?StartGameThread@@YAKPAK@Z (0x00436320) as the game thread entry |
_hGameThread / _dwGameThreadID |
Handles stored as globals |
| Handshake | Main thread polls GetExitCodeThread for exit code 0x103 (STILL_ACTIVE) with 30-second timeout; returns 0 on timeout |
| Timer thread | A separate _hTimeThread / _dwTimeThreadID also exists; managed symmetrically by ?EndGame@@YAXXZ |
FUN_00476780 (0x00476780) checks SystemParametersInfoA(0x10, ...) (SPI_GETSCREENSAVER) before the thread is spawned and saves the result to DAT_004F7EF0; FUN_004767D0 (0x004767D0) restores the screensaver setting on exit.
?SetupCobra@@YAGPAUGlobalData@@@Z — 0x0046B4E0¶
This is the top-level subsystem initializer. It calls three init functions in fixed order and rolls back on failure:
SetupCobra:
InitCobra → if fail: return error code
InitVideo → if fail: CleanCobra, return error code
InitAudio → if fail: CleanVideo, CleanCobra, return error code
return 0 (success)
All three take a GlobalData* pointer (referred to as param_1 throughout).
?InitCobra@@YAGPAUGlobalData@@@Z — 0x0046AE10¶
Opens the movie/FMV data file via _LibOpen_8 and validates the file header magic:
| Magic (u32 LE) | Meaning |
|---|---|
0x43425241 (CBRA) |
Cobra A |
0x43425242 (CBRB) |
Cobra B |
0x43425243 (CBRC) |
Cobra C — all three treated as error code 3 |
0x43425244 (CBRD) |
Valid movie data; proceeds with setup |
On CBRD, if the frame count is below 0x67, it configures the movie buffer layout: line stride (param_1+0xD0), scale factor (param_1+0xD2), buffer pointers (_junkBuff__3PAEA, _buffMyMovie__3PAEA), and reads the first two frame headers. Sets error status at param_1+0xAE.
?InitVideo@@YAGPAUGlobalData@@@Z — 0x0046B120¶
Configures the VESA/display subsystem:
- Copies mode dimensions from globals (
DAT_0055C06Awidth,DAT_0055C06Cheight) intoparam_1+0xC142andparam_1+0xC144. - Points
param_1+0xC14Aandparam_1+0xC14Eat the fake VESA info structures (_fakeVESAInfo__3UVGA_Info_t__A,_fakeVESAModeInfo__3UVGA_Mode_Info_t__A). - Validates available VRAM against the required framebuffer size for the current color depth (8, 15, 16, or 32 bpp). Sets error code
5if VRAM is insufficient. - Selects a video mode code stored at
param_1+0xC140:
| Code | Meaning |
|---|---|
1 |
8-bpp, non-DirectDraw |
2 |
8-bpp, DirectDraw |
3 |
16-bpp VESA (linear) |
4 |
16-bpp DirectDraw |
- Calls
?SetVESATop@@YAXGG@Zto set the display start address.
?InitAudio@@YAGPAUGlobalData@@@Z — 0x0046B4C0¶
Returns 0 immediately. Audio subsystem initialization is handled elsewhere — this stub exists to complete the SetupCobra chain.
_SMInit@0 — 0x0046A370¶
Initializes the symbol/script manager. Calls _GetExecutablePath_8 to locate the executable directory, then calls _RMChangeType_12 to build a path with a modified extension, and loads a data file via _LoadFile_16. Falls back to a "RELEASE" default if the file is not found. The remaining init steps beyond the file load are not transcribed here.
3. Main Loop Body¶
?MainLoop@@YAGPAUGlobalData@@@Z — 0x0046B560¶
This function drives the FMV/cutscene playback loop (Cobra video player), not the in-flight simulation. It is named MainLoop in FA.SMS but operates on a GlobalData struct that describes a movie context. Key behaviors:
- Takes a
GlobalData*asparam_1. - Snapshots
_timerTicksat entry asiVar8(the frame-timing baseline). - Iterates
local_14from0toparam_1[0xE6]-1(frame count loop). - Per frame: reads frame data from a LIB file via
_LibRead, decodes via_DecodeFrame__YAXPAUMovieContext__PAUFrameHeader__PAUGlobalData___Z, and may call_WritePalette_8when a palette change frame is detected. - Timing: computes elapsed cobra-time as
(_timerTicks - iVar8) * _constConv__1__cobraTime__YAJJ_Z_4JA, result stored atparam_1+0xC194. Busy-waits (spin loop) if the wall clock is behind the frame timestamp. - Frame counter:
param_1[0xC1B0]is incremented each frame; running average frame time accumulated inlocal_18/local_28. - At the end of each frame outer loop iteration, calls
FUN_0046BD90(key/mouse check) and_G_Flush_4. - Returns
param_1[0xAE]as a status code (0 = normal end, 1 = user abort, 2 = read error, 0xFF = quit).
Replay controls (__myReplay__3JA, __myFForward__3JA, __myRewind__3JA, __myPause__3JA) are zeroed on return.
FUN_0046BD90 — 0x0046BD90¶
Per-frame user-abort check used inside the cobra loop:
- Clears
__cobraKeyFlag__3JA. - Calls
_GetKey(). If the key is0x20(Space) or0x1B(Escape), or if_cobraATQuit__3DAis non-zero, sets__cobraKeyFlag__3JA = 1. - If
_mouseButtonPressesorDAT_00560EF1are non-zero, also sets the flag. - Returns the flag value.
4. In-Flight Simulation Loop¶
?FlyingLoop@@YAXXZ — 0x00404C70¶
The core per-frame simulation loop for the in-flight screen. This is a do-while that runs until a scenario end or explicit exit condition. Per-frame sequence:
| Step | Call | Notes |
|---|---|---|
| 1 | _TIMEUpdate_0() |
Advances the simulation clock; updates _frameTicks, _currentTime, _currentT |
| 2 | _MISSIONEndScenario_0() |
Returns non-zero → exits loop |
| 3 | Time-limit check | _currentTime > 0x7F9A or _currentT > 0x7F9A → exits loop |
| 4 | _MPMaybePausedMsg__YGXXZ() |
Multiplayer: sends/receives pause state |
| 5 | _MISSIONCheckSuccess_0() |
Evaluates win/lose conditions |
| 6 | _GetPlayerControl_0() |
Reads joystick/keyboard input |
| 7 | _WRUpdate_4(DAT_004eb654) |
World renderer update (skipped if IFM active) |
| 8 | Gated on _frameTicks > 0 |
Object sim, graphics, zone, streamers, projectile lock updates |
| 8a | _ServiceObjects() |
Per-frame object dispatch (see §5) |
| 8b | _GRAPHICUpdate_0() |
Particle / explosion graphics update |
| 8c | _ZONEUpdate_0() |
Zone trigger evaluation |
| 8d | _StreamersUpdate_0() |
Smoke/streamer trail update |
| 8e | _PROJLockUpdate_0() |
Missile seeker lock update |
| 9 | _MPSend__YGXXZ() |
Multiplayer: send state packets |
| 10 | _MPReceive__YGDXZ() |
Multiplayer: receive and apply remote state |
| 11 | _MPCheckDisconnect__YGDXZ() |
Returns zero → exits loop (disconnect) |
| 12 | VIEWCanSeeTarget(&_mainV) |
padlock visibility test (_WRCanSee); returns a view-active flag, 0 clears the external/IFM view. Part of the VIEW subsystem |
| 13 | VIEWApplyMode(&_mainV) |
external-view dispatch — when the view-mode word (_mainV[0x5A]) is set, delegates to the view builder VIEWBuild |
| 14 | VIEWFromObject(&_mainV) |
spot/track-view update — positions the view from the tracked object ((&_objPtrs)[_mainV[0x0E]]) |
| 15 | VIEWReplayRecordGate(&_mainV) |
replay record-gate — inside the _timerTicks capture window (_replayWindowStart/End) sets _replayActive |
| 16 | _ServiceSounds__YIXXZ() |
Audio: update positional sounds |
| 17 | _T_Make_12(&_mainV, 0) |
Build render scene (skipped if IFM active) |
| 18 | _G_SetClipBox_16(...) |
Set render clip rect to cockpit area |
| 19 | _T_Render_4(&_mainV) |
Render 3D scene to backbuffer (target list built around this call) |
| 20 | _WRLightUpdate_0() |
Update world light state |
| 21 | _WRLensFlare_0() |
Lens flare post-pass |
| 22 | _HUDDraw_4(0) |
Draw cockpit HUD overlay |
| 23 | _G_SetFullClipBox_0() |
Restore full-screen clip |
| 24 | VIEWReplayPlayback(&_mainV) |
replay playback — when _replayActive, copies the 0x30-dword saved-view buffer (_replaySaveBuf) back into _mainV |
| 25 | _FPSUpdate_0() |
Framerate counter update |
| 26 | Key read loop | _GetKey() / _GetFakeKey_0() — classifies Space (weapon) and Tab (gun) |
| 27 | _CPDraw_8(key, ...) |
Draw control panel (cockpit or IFM variant) |
| 28 | _G_Flush_4() |
Blit backbuffer to screen |
| 29 | _SoundPoints__YIXXZ() |
Update 3D sound listener position |
| 30 | _ChooseScore() |
Score evaluation tick |
| 31 | _ScoreUpdate__YIXXZ() |
Score display update |
| 32 | _SlewKey(key) |
Slew mode key handling |
| 33 | _MPKey__YIGG_Z(key) |
Multiplayer key handling |
| 34 | _FlightKey_4(key) |
Flight key dispatch (see below) |
IFM mode (_ifmOn__3DA != 0): the 3D render pipeline is replaced by a solid-color fill (_G_URect_16), and _HUDDraw_4(1) is called instead.
Key dispatch from _FlightKey_4 (notable cases):
| Key code | Action |
|---|---|
0x1B (Escape) |
_MPSetPaused__YIXD_Z(1) |
0x43 |
Toggle IFM if active, else uVar3 = 0xFFFF |
0x44 |
_HUDReprintMessages_0() |
0x4D |
Toggle IFM on/off (_ifmOn__3DA) |
0x63 (99) |
_TIMESetCompression_4(...) — cycles time compression: ½×, 1×, 2×, 4×, 8×, PAUSED |
0x1002 |
End mission (single/campaign calls _CallCampaignProc_4(6); multiplayer broadcasts and returns) |
Time compression display strings (from ?FlyingLoop@@YAXXZ):
_timeCompression |
HUD message |
|---|---|
0 |
"Time compression: 1X" |
1 |
"Time compression: 2X" |
2 |
"Time compression: 4X" |
3 |
"Time compression: 8X" |
-1 |
"Time compression: 1/2" |
0x7FFF |
"GAME PAUSED" |
In-flight view / replay — the VIEW subsystem (0x0040D7A0–0x0040F6B0)¶
Steps 12–15 and 24 call the VIEW subsystem — the external/spot camera and the flight replay
recorder over the main-view struct _mainV: an external-view dispatcher (VIEWApplyMode →
builder VIEWBuild), a spot/track-view update driven by the tracked object (VIEWFromObject),
and a replay recorder/player pair that gates on the _timerTicks capture window and copies a
0x30-dword saved-view buffer (_replaySaveBuf) in and out of _mainV (VIEWReplayRecordGate,
VIEWReplayPlayback, _replayActive flag). This was a gap in the original map (a subsystem the
19-subsystem set missed, the same way the .SEQ player and the SPX path surfaced as
#240/#241); it is now named and documented in view.md — #257.
5. Per-Frame Object Dispatcher¶
Object List¶
The object list is a flat array managed by _OBJInit@4 / _OBJAdd@8. Key globals:
| Global | Role |
|---|---|
DAT_004FFE34 / DAT_00553828 |
Base pointer / bump-allocator current pointer into the object pool |
DAT_00553840 |
Pool capacity (bytes) — set to 300,000 by _MISSIONInit1 |
_nextObjId |
Next slot index (starts at 1; 0 is unused) |
_objPtrs |
Array of pointers, indexed by object ID |
DAT_00553120 |
Parallel array of object sizes (u16 per slot) |
_OBJInit@4 — 0x00491250: allocates the object pool with _MMAllocPtr_8(param_1, 0x8000). Also initialises _nextObjId = 1 and computes a network ID range based on _thisComputer.
_OBJAdd@8 — 0x004913E0: copies param_2 bytes from param_1 into the bump-allocated pool. Returns a u16 object ID. Hard caps: 799 objects when _curScreen == 3; 899 otherwise. Returns 0 on overflow.
_T_ObjList@8 — 0x004A7DF0: the object iterator used for spatial queries. Iterates IDs 1..nextObjId-1. For each entry where objPtr[+1] & 1 (alive flag) and _InBounds_8 (position within a query rect) pass, invokes a callback function pointer. Returns early if the callback returns '\0'.
_ServiceObjects — 0x00462A50¶
Called from ?FlyingLoop@@YAXXZ when _frameTicks > 0. Each frame it walks the
service chain (_chainStart, ordered by each object's +0x68 service key),
mirrors the head object into the _cg/_cgt scratch buffers via GetCurObj,
dispatches its update through the proc table (_GVProc, the projectile proc, …),
writes the mirror back with PutCurObj, and re-queues it — then drains the remote
hit/effect message queues. The full object system (chain, mirror, allocation, proc
dispatch) is documented in objects.md.
_GVProc — 0x00473DB0 (FA.SMS: _GVProc)¶
The per-frame dispatch function for ground vehicles and naval vessels (.NT objects). It is a proc-table accessor, not a loop itself — it is called once per object per frame by the object iterator.
undefined * _GVProc(char param_1) {
if (param_1 == 0x03) return _GVEventProc; // event proc
if (param_1 == 0x05) return &_NPCWeaponsProc; // weapons proc
return _OBJProc(param_1); // delegate to base
}
Dispatch codes:
| Code | Returned proc |
|---|---|
0x03 |
_GVEventProc — event handler (see below) |
0x04 |
_OBJDamageProc__YAXPAUHIT_OBJ_DATA___Z (via _OBJProc) |
0x05 |
_NPCWeaponsProc — NPC weapon firing |
0x06 |
_OBJSayProc (via _OBJProc) — speech/callout |
| other | NULL (via _OBJProc) |
_GVProc has no direct callers by design: it is installed as an object's proc-table accessor and invoked indirectly through the proc dispatch in _ServiceObjects (the +0x68-keyed service chain), never called by name. See objects.md for the dispatch mechanism.
_GVEventProc — 0x00473F50 (FA.SMS: _GVEventProc)¶
Event handler for ground vehicle objects. Reads the entity state byte at +0x100 (the primary per-frame state byte) and the entity class. Delegates waypoint navigation via _GVDoCurrentWaypoint__YADXZ and dispatches hit/damage events. References _Dist_8, _COMaxSpeed_0, _CreateMove_52, _CreateMoveGoal_20.
?GVDoCurrentWaypoint@@YADXZ — 0x00473DE0¶
Called from _GVEventProc. Advances the waypoint list via _WPMaybeAdvance_0, reads the next waypoint position with _WPPos_8, optionally blends toward the prior waypoint position (when waypoint flag bit 2 is set), then calls _CreateMove_52 and _CreateMoveGoal_20 to issue a movement command. Sets DAT_0050CED8 = 0xC700.
_OBJProc — 0x00473BE0 (FA.SMS: _OBJProc)¶
Base proc-table accessor for static objects (.OT). Returns function pointers for event (0x03), damage (0x04), and speech (0x06) dispatch codes; returns NULL for all others.
Projectile Dispatch — 0x004C1F50 (_PROJProc)¶
_PROJProc (0x004C1F50, FA.SMS-confirmed) is the projectile proc-table accessor. Like _GVProc it has no direct callers — it is reached indirectly through the object proc dispatch in _ServiceObjects. See objects.md for the dispatch and weapons.md for the projectile system.
_Kill@0 — 0x00473C10 (FA.SMS: _Kill@0)¶
Called when an object is destroyed. Key actions:
- If the killed object belongs to the local player's computer (
DAT_0050CE90 & 0x7F == _thisComputer) and is not already marked dead: calls_MISSIONAddScore_12(-2 penalty), resolves the kill class fromDAT_0050CEF6, and calls_KillStats_12. - If the killed object is the player (
_playerId == _curId): in single-player, calls_SetScenarioEndTime_4(5); in multiplayer, broadcasts"You have died"/"<name> has died"via_MPLifeNotify. - Sets the dead flag in
_DAT_0050CE81(bit0x2000; also bit0x102000unless object type byte at+0x85is0x05). - If
_cgis 2 or 4: zeroes the cargo hold (_HARDFindStore_12walk over store type 7).
6. Frame Timing¶
Variables¶
| Symbol | VA | Type | Role |
|---|---|---|---|
_timerTicks |
0x005528EC |
s32 |
Raw timer tick counter — incremented by the timer thread |
_frameCounter |
0x004EB738 |
u32 | Frame counter (FA.SMS confirmed) |
_frameTicks |
0x0055292C |
s32 |
Ticks elapsed since last frame; gating value for object simulation |
_currentTime |
0x005528E0 |
u16/u32 | Mission simulation time (in mission ticks); capped at 0x7F9A |
_timeCompression |
0x005528F8 |
s16 |
Time compression factor: 0=1×, 1=2×, 2=4×, 3=8×, -1=½×, 0x7FFF=paused (FA.SMS name is _timeCompression) |
?constConv@?1??cobraTime@@YAJJ@Z@4JA |
0x004F7618 |
s32 |
Cobra-time conversion constant (multiplied with _timerTicks delta to produce cobra-time units) |
Cobra-Time Computation (movie loop)¶
In ?MainLoop@@YAGPAUGlobalData@@@Z, elapsed cobra-time is computed as a 64-bit multiply then shifted:
lVar6 = (longlong)(_timerTicks - iVar8) * (longlong)_constConv__1__cobraTime__YAJJ_Z_4JA;
cobra_time = (int)((ulonglong)lVar6 >> 0x20) << 0x10 | (uint)lVar6 >> 0x10;
This is a fixed-point 16.16 scale applied to the raw tick delta.
Frame-Tick Gating (simulation loop)¶
?FlyingLoop@@YAXXZ gates the entire object simulation block behind if (0 < _frameTicks). When time compression is set to 0x7FFF (paused), _frameTicks evaluates to zero and the simulation is frozen while rendering continues. _TIMEUpdate_0() is called unconditionally each iteration to keep the clock current.
GetTickCount Reference¶
__imp__GetTickCount@0 is confirmed at import table address 0x00593550. _GetTickCount@0 at 0x004D70BE is an internal wrapper. The Cobra movie player uses timeGetTime() (in ?CreateGameThread@@YAHXZ) for its 30-second thread-startup timeout.
7. Shutdown Sequence¶
?EndGame@@YAXXZ — 0x00476700 (FA.SMS: ?EndGame@@YAXXZ)¶
Top-level shutdown called after the flight loop exits:
FUN_004767D0— restores screensaver setting viaSystemParametersInfoA(0x11, 1, ...)if it was disabled at startup.TerminateThread(_hGameThread, 0)+CloseHandle— if_dwGameThreadID != 0.TerminateThread(_hTimeThread, 0)+CloseHandle— if_dwTimeThreadID != 0._GameCleanup_0()— full game state teardown (body not transcribed here).
?CleanupCobra@@YAGPAUGlobalData@@@Z — 0x0046B530¶
Symmetric inverse of SetupCobra:
CleanupCobra:
CleanAudio (stub — returns immediately)
CleanVideo (stub — returns immediately)
CleanCobra (closes LIB file handle, frees movie frame table buffer)
?CleanCobra@@YAGPAUGlobalData@@@Z — 0x0046B0F0¶
Calls _LibClose(param_1+0xF2) (close movie LIB file handle) and _MMFreePtr_4(param_1+0xEA) (free the frame index table allocated in InitCobra).
?CleanVideo@@YAGPAUGlobalData@@@Z — 0x0046B4B0¶
Returns 0. No-op in the analyzed build.
?CleanAudio@@YAGPAUGlobalData@@@Z — 0x0046B4D0¶
Returns 0. No-op in the analyzed build.
?MPMissionShutdown@@YGXXZ — 0x0046C500¶
Called at mission end. If _thisComputer > 0 (not the host), restores _gamePrefs from the value sent by the host at mission init (DAT_00546E00).
8. Campaign / Mission Initialization¶
Mission initialization is split into two numbered phases (Init1, Init2) with optional multiplayer wrappers. There are two sets: ?_MISSIONInit1/2@@YGXXZ (C++ mangled, cdecl) and _MISSIONInit1/2@0 (C-calling-convention versions at adjacent addresses). The decompile shows them as functionally identical — the two sets are likely inlined duplicates or near-identical callers of the same logic.
Phase 1: ?_MISSIONInit1@@YGXXZ / _MISSIONInit1@0 — 0x00480750 / 0x00480B40¶
Resets all per-mission state and initializes subsystems. Actions in order:
| Step | Call / Action |
|---|---|
| RNG seed | _randomSeed = _Rand16_0() ^ (u16)_TIMESystemTime_0() ^ (u16)_waitCounter |
| Multiplayer | If _numComputers > 1: call _MPMissionInit1__YGXXZ |
| RNG init | _InitRand_4(_randomSeed) |
| Mission flags | Zero _freeFlightMission, _playerCheating, _thisMissionGunsOnly, _killedMonster__3DA, _missionSucceeded, _fortMission, _fortWin |
| Scenario limits | _endScenarioTime = 0x7FFF, _endScenarioKills = 0x7FFFFFFF, _reviveAllowed = 0x7FFFFFFF, _reviveDelay = 0, _reviveDist = 10 |
| Score arrays | Zero _playerKills, _playerDeaths, _playerDamage, _playerRevives, _playerKillRatio, _playerScores__3PAJA, _playerKillsHuman, _playerKillsAI, _playerDamageHuman, _playerDamageAI (8 entries each) |
| Score state | Zero _enemyScore__3JA, _friendlyScore__3JA, _scoreBy, _scoreGoal |
| Entity IDs | Zero _playerId, _doArmPlane, _playerWMId, _doSelectPlane, _slewId |
| UI flags | Zero _doBriefMap, _doBriefPaper, _cloudAlt, _missionDLLName__3PADA |
| Nation sides | memcpy(&_nationSides, &_defaultNationSides, 0x40) |
| Wind | _windH = _Rand_4(0xFFF0), __windSpeed = _Rand_4(0x16) + 7 |
MISSIONLoadOrdIcons (0x004809D0) |
Loads the ordnance HUD icon PICs (ord_air3.PIC, …) — recovered by the reconstruction (campaign subsystem) |
| MM alloc ID | _mmAllocId__3EA = 1 |
| Subsystem inits (ordered) | _T_Init_0, _T_InitDatabase_0, _FPSInit, _TIMEInit_12(0,0,0), _OBJInit_4(300000), _InitChain_0, _COLInit_0, _CTInit_0, _MSGInit_0, _SAYInit_0, _WNGInit_0, _GRPInit_0, _APInit_0, _PLANEInit_0, _ROInit_0, _PROJInit_0, _ZONEInit_0, _GRAPHICInit_0, MAPClearHover (0x00422840, campaign — clears map hover/redraw state) |
| Player damage init | If _playerId != 0: _GetCurObj_4(_playerId) → _DAMAGEInit_0() → _PutCurObj_0() |
| Stats array | Zero _stats (0x978 dwords = ~9,696 bytes) |
_OBJInit_4(300000) allocates the 300,000-byte object pool, establishing the hard cap on total live-entity data.
Phase 2: ?_MISSIONInit2@@YGXXZ / _MISSIONInit2@0 — 0x00480A30 / 0x00480B50¶
Post-spawn finalization. Runs after all mission objects have been loaded and placed:
| Step | Call / Action |
|---|---|
| Side assignment | Iterate all objects (1..nextObjId-1); for each where objPtr[+1] & 0x10 == 0: call _MAPSetSide_4(objPtr) |
| Human scan | _OBJFindHumans_0() — identifies human-controlled slots |
| Alias setup | _OBJAliasAll_12(0, 0, 0), _OBJAliasForMulti_0() |
| Time init | _TIMEInit_12(0, _missionHours__3JA, _missionMinutes__3JA) — sets mission start time |
| Scale max | _G_SetScaleMax_8(0x140, 200) |
| MC DLL load | If _missionDLLName__3PADA != 0 and _thisComputer == 0: _eventFilterProc = _RMAccess_8(&_missionDLLName__3PADA, 0x8000) — loads the .MC mission condition evaluator DLL |
| Say init | _SAYInit2_0() |
| Score init | _ChooseScoreInit() |
| Airport | If _playerId != 0: _APHomeAirport_0() to identify home airport, call _NextString_8(...) for airport name |
MISSIONLoadOrdIcons (0x004809D0) |
Ordnance-icon PIC load again, before the success check (campaign subsystem) |
| Success check | _MISSIONCheckSuccess_0() |
?MPMissionInit1@@YGXXZ — 0x0046C280¶
Multiplayer Phase 1 wrapper. Only runs if _numComputers > 1. On host (_thisComputer == 0): loads the mission file via _LoadFile_16(&_missionName, ...) and broadcasts it to all peers via MPEnqueue (0x0046C0A0, network — the core outbound enqueue primitive). On all machines: calls _MPWaitEveryoneStatus__YGDJDDD_Z(0x17, ...) to synchronize before proceeding.
?MPMissionInit2@@YGXXZ — 0x0046C470¶
Multiplayer Phase 2 wrapper. Only runs if _numComputers > 1. Host calls _MPSendPrefs__YGXXZ to broadcast _gamePrefs; peers copy the received prefs. Resets _packetTicks__3PAJA and _packetTicksAdjust__3PAJA arrays. Iterates all objects and calls _MPPrepareForInterp__YGXPAUANGLE__J_Z(0, 0) on each (interpolation state reset).
Phase 3 and Medal Info¶
Two additional init phases follow:
_MISSIONInit3@0 — 0x00480B60: evaluates _MISSIONSucceededForThisPlayer_0(); sets _home and DAT_00552FE0 on success, sets DAT_00552FC8 on failure.
_MISSIONInitMedalInfo@0 — 0x00480B80: calls _WNGPart_12 to check if the player has wingmen; sets _playerHasWingmen accordingly. Calls _MISSIONSetCheating_0() in both branches.
Function Reference¶
All addresses are virtual addresses from the game executable (base 0x00400000).
| VA | FA.SMS name | Role |
|---|---|---|
0x004D9D00 |
_WinMainCRTStartup |
PE entry point / CRT startup |
0x00476120 |
_WinMain@16 |
Win32 WinMain; message loop |
0x004764B0 |
?InitApplication@@YAHPAX@Z |
Window class registration and creation |
0x00403700 |
?usnfmain@@YAXXZ |
Game's internal main — outer shell/mission state machine (switch (_curScreen)); launches FlyingLoop at state 0x12 |
0x00476660 |
?CreateGameThread@@YAHXZ |
Spawns game simulation thread |
0x00476700 |
?EndGame@@YAXXZ |
Tears down game and timer threads |
0x00436320 |
?StartGameThread@@YAKPAK@Z |
Game thread entry point |
0x0046B4E0 |
?SetupCobra@@YAGPAUGlobalData@@@Z |
Top-level subsystem init (Cobra → Video → Audio) |
0x0046AE10 |
?InitCobra@@YAGPAUGlobalData@@@Z |
Movie subsystem / GlobalData init |
0x0046B120 |
?InitVideo@@YAGPAUGlobalData@@@Z |
VESA/DirectDraw display init |
0x0046B4C0 |
?InitAudio@@YAGPAUGlobalData@@@Z |
Audio init stub (no-op) |
0x0046B530 |
?CleanupCobra@@YAGPAUGlobalData@@@Z |
Top-level subsystem shutdown |
0x0046B0F0 |
?CleanCobra@@YAGPAUGlobalData@@@Z |
Cobra / movie cleanup |
0x0046B4B0 |
?CleanVideo@@YAGPAUGlobalData@@@Z |
Video cleanup stub (no-op) |
0x0046B4D0 |
?CleanAudio@@YAGPAUGlobalData@@@Z |
Audio cleanup stub (no-op) |
0x0046B560 |
?MainLoop@@YAGPAUGlobalData@@@Z |
Cobra FMV playback loop |
0x0046BD90 |
(unnamed) | Per-frame key/mouse abort check (cobra loop) |
0x00404C70 |
?FlyingLoop@@YAXXZ |
In-flight simulation loop |
0x0046A370 |
_SMInit@0 |
Symbol/script manager init |
0x0046A4C0 |
_SMShutdown@0 |
Symbol/script manager shutdown |
0x00473BE0 |
_OBJProc |
Static object proc-table accessor |
0x00491250 |
_OBJInit@4 |
Object pool allocation |
0x004913E0 |
_OBJAdd@8 |
Add object to live list |
0x004A7DF0 |
_T_ObjList@8 |
Spatial object iterator |
0x00473DB0 |
_GVProc |
Ground vehicle proc-table accessor |
0x00473DE0 |
?GVDoCurrentWaypoint@@YADXZ |
Ground vehicle waypoint navigation |
0x00473F50 |
_GVEventProc |
Ground vehicle event handler |
0x004C1F50 |
_PROJProc |
Projectile proc-table accessor |
0x00473C10 |
_Kill@0 |
Object death handler |
0x00476880 |
_LimitFromLowSpeed@12 |
Low-speed flight limit utility |
0x00476AE0 |
_MovePlane@0 |
Player aircraft movement update |
0x00480750 |
?_MISSIONInit1@@YGXXZ |
Mission Phase 1 init (C++ form) |
0x00480A30 |
?_MISSIONInit2@@YGXXZ |
Mission Phase 2 init (C++ form) |
0x00480B40 |
_MISSIONInit1@0 |
Mission Phase 1 init (C form) |
0x00480B50 |
_MISSIONInit2@0 |
Mission Phase 2 init (C form) |
0x00480B60 |
_MISSIONInit3@0 |
Mission Phase 3 — home/fail flag set |
0x00480B80 |
_MISSIONInitMedalInfo@0 |
Wingman/cheating flag init |
0x0046C280 |
?MPMissionInit1@@YGXXZ |
Multiplayer mission Phase 1 wrapper |
0x0046C470 |
?MPMissionInit2@@YGXXZ |
Multiplayer mission Phase 2 wrapper |
0x0046C500 |
?MPMissionShutdown@@YGXXZ |
Multiplayer mission shutdown |
0x004EB738 |
_frameCounter |
Frame counter global |
0x004D70BE |
_GetTickCount@0 |
Internal GetTickCount wrapper |
0x00593550 |
__imp__GetTickCount@0 |
GetTickCount IAT slot |
Startup & Shell
Startup & C Runtime¶
The program's boot path and the statically-linked MSVC C runtime it pulls in. Notably,
The game executable is a native Win32 PE — there is no Phar Lap DOS extender (that name refers to
the PL\0\0 overlay-DLL format handled elsewhere). The one genuine startup element in this
range is the PE entry _WinMainCRTStartup (0x4D9D00); the rest is CRT.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
What's here¶
_WinMainCRTStartup(0x4D9D00): the PE entry point — CRT init (heap, locale, argv, atexit) then the jump to the game's_WinMain.- CRT public functions: ~180 FA.SMS-named MSVC runtime routines (memory, string, math, stdio, locale) plus 41 IAT import thunks to DDRAW / the serial-comms driver / matchmaking.
- CRT internals: ~116 compiler-runtime helpers (EH funclets, locale init/teardown,
__amsg_exit, FID-identified library internals) — waived as "CRT runtime helper"; they are boilerplate, named where FA.SMS provides a name and waived otherwise. Per the reconstruction DoD, startup carries the CRT with these waivers rather than pretending the runtime is game code.
Functions¶
Full record: db/symbols/startup.csv.
| VA | Symbol | Role |
|---|---|---|
0x4D9D00 |
_WinMainCRTStartup |
PE entry: CRT init → game _WinMain |
0x4D715A |
_DirectDrawCreate@12 |
DDRAW import thunk |
0x4D7220 |
_ser_rs232_getpacket@12 |
serial-comms driver import thunk |
Open Questions¶
1. Import-thunk attribution¶
The 41 IAT import thunks are carried under startup; a later pass could reassign them to the subsystems that call them (DDRAW → renderer, serial → network, …) for purer attribution.
Decision: keep them under startup. Each thunk is a single shared IAT entry (one per imported symbol, called from many subsystems), and the owning module is already legible from the import DLL name, so per-caller reattribution would duplicate entries across files and churn the DB for no analytic gain. The IAT/CRT-bootstrap grouping stays.
Status: resolved — re-static (kept in startup by decision).
Related¶
- game-loop.md — the
_WinMain→FlyingLoopsequence startup hands off to. - architecture.md — the runtime environment and overlay-DLL system.
- network.md — the serial-comms driver the thunks reach.
Shell / Menu / Dialog UI¶
The out-of-cockpit interface: the menu-bar state machine and the dialog/widget core
that render and drive every menu screen, dialog box, and button. Re-carved from a nominal
range that was ~85% foreign into its two true clusters (menu core 0x40B8A0–0x40D79C,
dialog core 0x487A3A–0x48D200) plus scattered screen entries.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
Menu bar and dialogs¶
- Menu core (
0x40B8A0–0x40D79C): the menu-bar state machine —MenuStartUp,MenuLoadFont(MENUFONT.PIC / MFONT320.PIC),MenuMouseSelect(hit-test), andMenuDrawDropdown(draw an opened submenu with background save/restore), over a_firstMenulinked list with palette-remap chrome. - Dialog/widget core (
0x487A3A–0x48D200): the DIALOG lifecycle — record hit-test and dispatch, and every_Draw*widget renderer (check/toggle/list-box/scrollbar) with edit/rocker/slider input. Many_Draw*handlers are label-only in FA.SMS and materialise on apply.
Functions¶
Full record: db/symbols/shell-ui.csv.
| VA | Symbol | Role |
|---|---|---|
0x40BD30 |
MenuStartUp |
initialise the menu bar |
0x40C160 |
MenuLoadFont |
load the menu font PIC |
0x40C670 |
MenuMouseSelect |
hit-test the mouse over the bar/items |
0x40C990 |
MenuDrawDropdown |
draw an opened submenu |
0x40BF60 |
MenuMeasureItemWidth |
measure the widest menu label |
Open Questions¶
1. DLG record types — mapped¶
The .DLG record types 1/3/4/5/7/8 are now field-mapped in DLG.md § Per-type
record fields (recovered under #258 from
DialogUpdate, _DrawListBox, and the scrollbar helpers). Only a few engine-scratch interior
bytes of the largest records remain unnamed (tracked as the residual .DLG gap under #54).
Status: resolved — re-static (#258; residual scratch fields under #54).
Related¶
- formats/DLG.md / formats/MNU.md — the dialog and menu formats.
- renderer.md — the
G_*rasterizer the UI draws through. - campaign.md — the campaign/mission screens the shell hosts.
.SEQ Cutscene Player¶
The .SEQ sequence player runs Jane's scripted intro/outro cutscenes from a text
script file (e.g. g_intro). It loads and compiles the script, then ticks a timeline of
commands that draw bitmaps, blocks and wrapped text into a display list, fade the palette,
and play music, sound and video. 0x444F70–0x4471E0 (plus the public entry PlaySeq at
0x412C10).
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown. Found by the reproducibility audit as the 19th subsystem the original map missed (#240).
Dispatch is by name, not by opcode¶
The defining feature of the .SEQ player is that script commands are dispatched by
name through the shipped symbol table, not through a switch or an opcode jump table.
_SeqStart reads the .SEQ file, performs %N argument substitution and include
expansion (SeqLoadScript → SeqExpandLine / SeqParseInclude), and compiles the result
into a per-slot command buffer. Each tick, _SeqContinue fetches the next line
(SeqFetchLine parses a leading /abs or +rel timecode), then for a command word like
bitmap it builds the string "SEQ" + command into a local buffer, resolves that
symbol at runtime with _SMAddress (a lookup into the same FA.SMS map this project
applies), and indirect-calls it with up to eight parsed operands.
The consequence for reconstruction is concrete: the twelve _SEQ<verb> handlers
(_SEQbitmap, _SEQtext, _SEQmusic, _SEQvideo, _SEQwait, …) are reached only
through _SMAddress, never by a direct CALL, so Ghidra's auto-analysis never created
them as functions. They exist in the binary — FA.SMS gives their addresses — and applying
the symbol database materialises them (createFunction). This is exactly the class of
name-dispatched leaf that a purely xref-driven inventory misses.
Graphics: the SEQGR display list¶
Drawing commands do not paint immediately; they allocate SEQGR nodes from a
free-list (SeqNewGraphic) and link them into a ring (seqGraphics). Each node carries a
rect, a type (1 = bitmap, 2 = filled block, 4 = multiline colour text), an owning-sequence
index and an expiry tick. SeqRender walks the ring once per frame, expires aged nodes
(SeqExpireGraphics), and redraws the dirty ones under the current clip box
(SeqDrawGraphic), marking overlapped neighbours dirty so partial refreshes stay correct.
Palette fades run in parallel: _SEQfadein / _SEQfadeout latch a direction into
seqFading, and SeqFadeIn / SeqFadeOut blacken or restore curPalette by an
elapsed/length ratio each tick.
Functions¶
Full record: db/symbols/seq.csv.
| VA | Symbol | Role |
|---|---|---|
0x412C10 |
PlaySeq |
public entry — blocking SeqStart → tick loop → SeqEnd |
0x445060 |
SeqStart |
load .SEQ, alloc a SEQUENCE slot, compile the script |
0x445330 |
SeqLoadScript |
read the file, expand includes and %N substitutions |
0x445700 |
SeqContinue |
per-tick interpreter — build "SEQ"+cmd, _SMAddress-dispatch |
0x446500 |
SeqNewGraphic |
allocate a SEQGR node and link it into seqGraphics |
0x445ED0 |
SeqRender |
walk the display-list ring, expire + redraw dirty nodes |
0x446660 |
SEQbitmap |
script op — display a bitmap as a SEQGR node |
0x446F10 |
SEQtext |
script op — wrapped-text node via FormatText |
0x446B70 |
SEQmusic |
script op — start a music track (MusicOn) |
0x445E30 |
SeqEnd |
tear down all sequences — SoundAllOff / MusicOff / free |
Open Questions¶
1. Dispatch prefix and keyword strings — resolved¶
A direct read of the .data bytes settles them:
| VA | Bytes (C string) | Role |
|---|---|---|
0x004F4F5C |
"_SEQ" |
the dispatch prefix — SeqContinue builds "_SEQ" + cmd and resolves it through _SMAddress, matching the _SEQ<verb> handler names |
0x004F4F64 |
"sync" |
the sync script keyword |
0x004F4F6C |
".XMI" |
the music-file extension, not a SEQmusic suffix — the _SEQmusic op loads an .XMI sequence ("all" follows at 0x004F4F74) |
So the prefix is "_SEQ" (with the leading underscore, hence the _SEQ<verb> labels), and the
third string is the .XMI extension the music op appends — the earlier SEQmusic guess was
wrong. (Read via the PE .data section; these sit at file offset 0x… in the 0x4EB000-based
.data block.)
Status: resolved — re-static ("_SEQ" / "sync" / ".XMI").
2. SEQUENCE / SEQGR struct maps — resolved¶
The program-wide struct-typing pass (#230)
addressed this: SEQUENCE (stride 0x38), SEQGR, SEQLBL, SEQFNT and SEQTXT are declared
as the recovered type vocabulary in db/types/fa_types.h,
and the FA.SMS array-base globals are typed against them (seqGrArray/seqGrList/seqGraphics
→ SEQGR *, seqFontArray/seqFonts → SEQFNT *, seqTextArray → SEQTXT *,
seqLabelList → SEQLBL *). Per that pass's conservative policy the struct interiors stay
reserved padding rather than guessed byte-exact layouts.
Status: resolved — struct-typing pass (#230).
Related¶
- sound.md —
MusicOn/SoundAllOff, driven by theSEQmusic/SEQsoundops. - video-decode.md — the Cobra
.VDOplayer theSEQvideoop invokes. - renderer.md —
GG_Flushand the bitmap blit theSEQGRrenderer uses. - formats/SMS.md — the FA.SMS symbol map that
_SMAddress(0x46A4E0) resolves the"SEQ"+cmdnames against.
Input — Joystick & Mouse¶
Player input device handling: the Win32 multimedia joystick API layer and the mouse event ring. (Serial-cable and modem link transport, historically "input" territory, is documented with the network transport layer per the reconstruction program's ownership split.) Re-carved from a very broad nominal range into the two true device clusters.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
Devices¶
- Joystick (
0x494270–0x494B50): a thin layer over the Win32 MM joystick API (joyGetNumDevs/joyGetPos(Ex)/joyGetDevCapsA) — not DirectInput.ReadSticksRawpolls axes,InitJoysticksenumerates,GetJoystickTypemaps to a configured stick type. - Mouse (
0x499CF0–0x499E5B): a WndProc hook feeding a 16-entry event ring the shell and cockpit poll.
Functions¶
Full record: db/symbols/input.csv.
| VA | Symbol | Role |
|---|---|---|
0x494270 |
ReadSticksRaw |
poll raw joystick axes (Win32 MM) |
0x4942D0 |
InitJoysticks |
enumerate/initialise joystick devices |
0x494430 |
GetJoystickType |
map a device to the configured stick type |
0x4944A0 |
ReadDevice |
read one device's current state |
Open Questions¶
1. Calibration / mapping table — resolved (layout)¶
NormalizeStick (0x4946B0) indexes two parallel per-device arrays by the axis's device
number (_joystickXDevice/YDevice/ThrottleDevice/RudderDevice):
_joystickInfo— Win32JOYINFOsnapshots, stride0x10(&_joystickInfo + dev*0x10).- Calibration table at base
0x554670— stride0x34per device; the raw axis words sit at record+0x00(X,0x554670),+0x04(Y,0x554674),+0x0C(rudder,0x55467C). The first poll auto-captures the resting value as centre (_gotCenterX/Y/Rguard →DAT_00554EBC/EC4/EC8), andScaleToRange(0x494580) maps raw→min/centre/max into the normalized int the sim consumes.
So each joystick device has a 0x34-byte calibration record (raw + captured-centre per axis)
plus a 0x10-byte JOYINFO slot; that is the layout the .CFG axis bindings persist.
Status: resolved — re-static (calibration record: 0x34-byte stride/device; JOYINFO 0x10 stride).
Related¶
- network.md — the serial/modem link transport (SER_/MOD_).
- physics.md — control inputs feed the flight model.
- hud.md — slew/padlock controls drive HUD symbology.
Simulation
Object / Entity System¶
How the game executable stores, services, and dispatches behavior for every live game object — aircraft, ground vehicles, projectiles, and static props alike. This is the runtime spine the flight model, AI, weapons, and renderer all hang off: one object chain walked every frame, a current-object mirror each handler operates on, and a proc-dispatch indirection that routes events, damage, and updates to the right per-class code.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recovered from
DumpAllFunctions.txtandAnalyzeOTNT.txt(scripts/ghidra/). Every symbol here is recorded in the symbol database and applied to the Ghidra project; progress is tracked in the reconstruction matrix. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
Objects, ids, and the pointer table¶
Every object is a variable-length record addressed by a small integer id. The
global pointer table _objPtrs (0x553848) maps id → record base; almost all
engine code reaches an object as (&_objPtrs)[id]. Id 0 is the null object. Ids
are handed out by the allocator (below); a separate high band of alias ids
(negative, per-computer) is reserved for multiplayer references.
The first bytes of every record are shared across all classes — the fields the object system itself reads:
| Offset | Size | Field | Meaning | Confidence |
|---|---|---|---|---|
+0x00 |
1 | class |
object class tag (& 0x1f); 4 = aircraft |
confirmed |
+0x01 |
4 | flags |
status bits; & 1 = alive, & 0x100000 = draw destroyed model |
confirmed |
+0x05 |
4 | type |
pointer to the shared type record (OT/NT/PT/JT) | confirmed |
+0x0E |
2 | health |
0 = destroyed |
confirmed |
+0x11 |
12 | pos |
world position (X,Y,Z, 24.8 fixed) | confirmed |
+0x64 |
2 | next_id |
next object in its service chain | confirmed |
+0x68 |
2 | service_key |
sort key that orders the service chain | inferred |
+0x6C |
4 | event_override |
optional per-instance proc override | inferred |
The per-instance record is documented in full in structs.md; the shared type record (and its shape fields) in shape-selection.md.
The current-object mirror (_cg / _cgt)¶
Handlers do not operate on the object record in place. GetCurObj (0x4628B0)
copies the whole record into a fixed scratch buffer _cg (0x50CE80) and its type
record into _cgt (0x50D268), records the id in _curId (0x4F6FBC), and (for
aircraft) unpacks the flight-model fields. Every subsystem then reads and writes
_cg/_cgt — which is why so much of the engine references fixed addresses like
_cg+0x11 (position) rather than an object pointer. PutCurObj (0x462980) copies
the mirror back and clears _curId. _curObjSize/_curTypeSize hold the byte
counts to copy; PushCurObj/PopCurObj nest the mirror (_curObjStackTop) so a
handler can service a second object re-entrantly.
This mirror is the reason the object record layout and the _cg global layout are
the same map — a fact this subsystem's waivers record explicitly, pointing every
_cg+N interior back to entity +N in structs.md.
The service chain and per-frame loop¶
Active objects live on a singly linked service chain (_chainStart, 0x546B90),
ordered by service_key (+0x68). ServiceObjects (0x462A50) walks it once per
frame:
GetCurObjmirrors the head object.- If its ready time is in the future, stop (the chain is time-ordered).
- Detach the head, run its update via
Service→ the proc dispatch below. - If the handler destroyed the object and the type opts into auto-removal
(
type +0x09 & 0x400),RemoveCurObjunlinks it. PutCurObjwrites the mirror back, then the object is re-queued.
Servicing pulls objects off _chainStart and collects the ones to run again into a
re-queue chain (_requeueChain, 0x546BA8); ChainMergeSorted (0x462B70)
folds that back into _chainStart in service_key order at end of frame.
ChainInsertCurObj (0x4626D0) does the ordered insert; ChainRemoveCurObj
(0x462640) the unlink. After the walk, ServiceObjects drains two remote-event
queues (below).
Proc dispatch — one object, many behaviors¶
An object's behavior is not called directly; it is resolved through a proc
selector. CallUtilProc (0x463F60) is the hub: given a selector index it calls
GetObjProc (0x463F30), which returns either the object's per-instance override
(+0x6C) or the class proc looked up on the type record (+0x7D), then invokes it.
Higher-level entry points wrap it:
CallEventProc(0x4639C0) — routes a game event, first offering it to a global_eventFilterProchook (used by the mission/AI layer to intercept).CallDamageProc(0x463EC0) — routes a hit; may triggerRemoveCurObjwhen the object dies and the type auto-removes.
Each object class publishes its procs through a small selector function.
OBJProc (0x473BE0) is the static-object one: selector 3 → OBJEventProc
(0x473A40), 4 → OBJDamageProc (0x473B40), 6 → OBJSayProc. Aircraft,
projectiles, and ground vehicles (GV) publish their own proc sets the same way, so
the same CallUtilProc call reaches class-appropriate code.
Allocation and aliases¶
Objects are bump-allocated from a single arena. OBJInit (0x491250) reserves the
arena (_objArena, capacity _objArenaSize) and seeds the id counter and the
per-computer alias band (_tempAliasBase/_tempAliasMax). OBJAdd (0x4913E0)
copies a prepared record to the arena bump cursor (_objArenaNext), records its byte
size in _objSizes, and publishes the id → base entry in _objPtrs; OBJSubtract
(0x491490) pops the most recent one. OBJAlias (0x4914C0) and its variants map a
transient reference (waypoint, multiplayer peer, preferred target) onto a real id so
that AI and networking can name objects that may not be locally resident.
Remote effect and hit queues¶
In multiplayer, other computers' hits and effects arrive as messages, drained after the service walk:
ProcessHitMsgs(0x462C91) — reads hit events (MSG 0x800B/0x800C), raises the0x4000event on the target, and spawns the explosion viaGRAPHICAddExp.ProcessEffectMsgs(0x462D40) — reads per-computer effect spawns (MSG 0x8003+n): explosions, smoke trails, andMANAddman/parachute spawns.
Local destruction takes the same visual path from the flight model:
PLANEBreakUp sets the 0x300000 destroyed/awaiting-swap flags and writes the
damage-set selector — see shape-selection.md.
GRAPHIC effect spawning¶
Transient visual effects — explosions, smoke, fire, debris, craters, chaff/flare, dust
puffs — are not full game objects. They live in a fixed GRAPHIC pool: a 100-entry
array _graphics of 0x66 (102)-byte entries, initialised by _GRAPHICInit@0
(0x442c00), stepped every frame by _GRAPHICUpdate@0 (0x442de0), and drawn by
_GRAPHICAddYourObjs@4 (0x4431b0) when the object system's 0x200 "add graphics" flag
is set. A free entry is marked by 0xffff in the owner field (+0x04); the allocator
FUN_00443b70 linear-scans for a free slot, or when full evicts the effect with the
lowest priority-weighted age (older + lower-class effects go first).
Spawn API¶
Every spawner takes a spawning-computer id as its first argument (network origin —
the local machine only mirrors the spawn to peers when it equals _thisComputer, via the
MP* twin) and an F24_POINT3* world position. The family:
| VA | Function | Parameters after (computerId, pos) |
|---|---|---|
0x4432d0 |
_GRAPHICAddExp@28 |
ownerObjIdx, useOwnerVelocity, spawnSecondaryDebris, collisionScatter — the canonical explosion; applies random type-variation (see below) and chains debris / cluster-release / smoke children |
0x443e80 |
_GRAPHICAddSmoke@28 |
type, lifetime, riseHeight, intensity2, ownerObjIdx — a smoke puff that drifts with _windH/_windSpeed |
0x444020 |
_GRAPHICAddFire@32 |
type, ownerObjIdx, WORD_POINT3* mountOri, groundZ, intensity, intensity2 — attaches a looping fire + sound |
0x4441d0 |
_GRAPHICAddDebris@24 |
count, WORD_POINT3* baseVel, spread, upwardBias — scatters count tumbling fragments |
0x443d00 / 0x443dc0 |
@GRAPHICAddCrater@12 / @GRAPHICAddHulk@12 |
id — ground scar / burnt-out hulk marker |
0x443f90 / 0x4443d0 / 0x444560 |
_GRAPHICAddSmokeAdder@40 / _GRAPHICAddClusterRelease@24 / _GRAPHICAddSpecialDebris@16 |
secondary-spawn helpers used by AddExp |
0x4447a0 |
@GRAPHICAddDevice@12 |
type, WORD_POINT3* ejectDir — countermeasure dispense (type 0xC = CHAFF, 0xD = FLARE) |
0x444150 |
_GRAPHICMakeAdder@24 |
entry*, adderType, interval, p1, p2, p3 — converts an existing entry into a continuous emitter (below) |
The MP twins carry the fully-typed signatures that pin the parameter kinds, e.g.
?MPGraphicAddExp@@YGXJPAUF24_POINT3@@GDDD@Z (id J, F24_POINT3*, owner G, three
flag bytes D) and ?MPGraphicAddFire@@…GPAUWORD_POINT3@@JJJ@Z (a WORD_POINT3*
orientation plus three longs).
GRAPHIC entry layout (0x66 bytes)¶
| Offset | Size | Field | Meaning |
|---|---|---|---|
0x00 |
u8 | type |
effect type → shape and parameter tables (below) |
0x01 |
u8 | frame |
shape frame / colour variant |
0x02 |
u16 | flags |
bit0 attached-to-owner · bit1 terrain-follow · bit5/6 fuse-timing mode · bit7 expired · bit8 emitter (has adder) |
0x04 |
u16 | owner |
owner object index into _objPtrs; 0xffff = free slot |
0x06,0x0A,0x0E |
F24×3 | pos |
world position (F24.8) |
0x12,0x14,0x16 |
s16×3 | orient |
orientation, spun each frame by the rates at 0x3A |
0x18 |
F24 | ground_z |
terrain floor from _T_Info — the Z the effect settles onto |
0x1C,0x1E,0x20 |
s16×3 | mount |
attach offset applied via _RotatedOffset when flags bit0 is set |
0x22,0x26,0x2A |
F24×3 | vel |
velocity, integrated by _frameTicks each update |
0x2E,0x30,0x32 |
s16×3 | accel |
drift/acceleration (smoke seeds this from wind) |
0x34,0x36,0x38 |
s16×3 | damp |
velocity damping targets (_MatchF24) |
0x3A,0x3C,0x3E |
s16×3 | spin |
per-axis angular rate |
0x40 |
i32 | spawn_tick |
_currentTicks at spawn |
0x44 |
i32 | expiry_tick |
death tick (lifetime*0x100 + spawn; 0x7FFFFFFF = permanent, e.g. craters) |
0x48 |
u8 | intensity |
brightness / scale |
0x49 |
u8 | intensity2 |
secondary brightness / alpha |
0x4A |
u8 | adder_type |
continuous-emitter type (0 = none; 7–11 = smoke trail) |
0x4B |
u16 | adder_interval |
ticks between child spawns |
0x4D–0x4F |
u8×3 | adder_p1..3 |
child-spawn parameters |
0x50 |
i32 | adder_next |
next emit tick |
0x54 |
char[] | loop_sound |
looping sound-effect name (fire) |
0x61 |
u16 | sound_param |
loop-sound parameter |
0x65 |
u8 | spawn_computer |
network origin computer id |
Effect types and shapes¶
_GRAPHICInit@0 resolves one .SH handle per effect type into a table at _DAT_0053da38
(indexed type*4). The type ranges:
| Type(s) | Shape | Effect |
|---|---|---|
0 |
— | none / mixed |
1–3 |
crater.SH |
ground craters |
4–6 |
debris.SH |
tumbling debris |
7–11 |
smoke.SH |
smoke puffs / trails |
12 |
chaff.SH |
chaff bundle |
13 |
flare.SH |
flare |
14 |
fire.SH |
fire |
15–0x26 |
exp.SH |
explosion variants (airbursts, ground bursts, water, etc.) |
0x28–0x2A |
spd/mpd/lpd.SH |
small / medium / large dust puffs |
For a local AddExp, the requested type is randomly varied into a nearby variant
(_Percent_4/_Rand_4) so repeated explosions differ — e.g. type 0x12 promotes to one
of 0x13/0x14/0x1C/0x1D a fraction of the time. The random remap runs only on the
originating machine; the chosen concrete type is what ships in the network mirror, so all
peers show the same variant.
Effect-parameter table¶
The static per-type tuning lives in a 0x30 (48)-byte record indexed by effect type at
0x4f46c4 (AddExp reads it as base + type*0x30). Recovered fields:
| Offset | Size | Meaning |
|---|---|---|
0x04 |
s16 | intensity base (scaled by Rand(0x42)+0x42 → entry 0x48/0x49) |
0x06 |
s16 | shape frame count / start frame |
0x08 |
u16 | sub-type / shape selector (low byte also carries a ground-burst flag, bit 2) |
0x0A |
s16 | secondary-debris count |
0x0C |
s16 | secondary-debris spread |
0x0E.. |
u32[≤8] | sound-effect name pointers — one picked at random per spawn (list ends at first null) |
0x2E |
s16 | sound pitch/parameter |
This record is the effect-data block that the fx_lib effect interpreter
(#315) turns into semantic form; the
remaining bytes of the 0x30 record are not yet individually resolved.
Adders (continuous emitters) and lifecycle¶
_GRAPHICMakeAdder@24 sets flags bit8 and fills 0x4A–0x50, turning an entry into a
periodic emitter. Each _GRAPHICUpdate@0 tick, FUN_00442e10 computes the entry's life
fraction (now − spawn_tick)·100 / (expiry_tick − spawn_tick); when it crosses the
fuse threshold (100% for bit6, 50% for bit5) it spawns its follow-on (e.g. a burning wreck
grows a fire + smoke adder), and while alive an adder of type 7–11 emits a smoke child
every adder_interval ticks. Non-attached entries integrate velocity and spin; attached
entries (bit0) track their owner via _RotatedOffset from the mount offset. When
now ≥ expiry_tick the entry is freed (owner = 0xffff).
Effect spawns propagate to other machines as MSG 0x8003 records (17 bytes:
type u8, pos F24×3, owner u16, two flag bytes), drained remotely by
ProcessEffectMsgs (above).
Globals¶
Recovered object-system state (full list, with per-symbol confidence, in the symbol database):
| Global | Address | Role | Confidence |
|---|---|---|---|
_objPtrs |
0x553848 |
id → record base pointer table |
confirmed |
_chainStart |
0x546B90 |
head of the service chain | confirmed |
_requeueChain |
0x546BA8 |
objects to re-queue this frame | confirmed |
_curId |
0x4F6FBC |
id of the mirrored current object | confirmed |
_cg |
0x50CE80 |
current-object record mirror | confirmed |
_cgt |
0x50D268 |
current-object type-record mirror | confirmed |
_curObjSize |
0x546B94 |
bytes to copy for the object mirror | confirmed |
_curTypeSize |
0x546B9C |
bytes to copy for the type mirror | confirmed |
_objArena |
0x4FFE34 |
base of the entity arena | confirmed |
_objArenaNext |
0x553828 |
arena bump cursor | confirmed |
_objArenaSize |
0x553840 |
arena capacity, bounds OBJAdd |
confirmed |
_objSizes |
0x553120 |
per-id record byte sizes | confirmed |
_nextObjId |
0x553838 |
next id to allocate | confirmed |
_tempAliasNext |
0x55383C |
next transient alias id | confirmed |
Functions¶
| VA | Symbol | Role |
|---|---|---|
0x00462600 |
InitChain |
reset the service chain and event hook |
0x00462A50 |
ServiceObjects |
per-frame walk of the object chain |
0x004626D0 |
ChainInsertCurObj |
ordered insert by service_key |
0x00462640 |
ChainRemoveCurObj |
unlink the current object from a chain |
0x00462B70 |
ChainMergeSorted |
fold the re-queue chain back into _chainStart |
0x004628B0 |
GetCurObj |
copy an object + type into the _cg/_cgt mirror |
0x00462980 |
PutCurObj |
write the mirror back to the record |
0x00463F60 |
CallUtilProc |
resolve and call a proc by selector |
0x00463F30 |
GetObjProc |
pick instance override (+0x6C) or class proc (+0x7D) |
0x004639C0 |
CallEventProc |
route a game event (through _eventFilterProc) |
0x00463EC0 |
CallDamageProc |
route a hit; may auto-remove on death |
0x00473BE0 |
OBJProc |
static-object proc selector |
0x00473A40 |
OBJEventProc |
static-object event handler |
0x00473B40 |
OBJDamageProc |
static-object damage handler |
0x00491250 |
OBJInit |
reserve the entity arena and id/alias bands |
0x004913E0 |
OBJAdd |
append a record; publish id → base |
0x00491490 |
OBJSubtract |
pop the most recently added record |
0x004914C0 |
OBJAlias |
map a transient reference onto a real id |
0x00462C91 |
ProcessHitMsgs |
drain remote hit events → explosions |
0x00462D40 |
ProcessEffectMsgs |
drain remote effect spawns |
0x00436B30 |
MoveObj |
advance an object toward its move goals |
0x004A6EB0 |
SetupOT |
type-load: generate damage-model variants (see shape-selection) |
0x00442C00 |
GRAPHICInit |
init the 100-entry effect pool + per-type .SH handles |
0x00442DE0 |
GRAPHICUpdate |
step every live effect (motion, fuse, adders) |
0x004431B0 |
GRAPHICAddYourObjs |
draw live effects when the object 0x200 flag is set |
0x004432D0 |
GRAPHICAddExp |
spawn an explosion (+ chained debris / smoke) |
Open questions¶
1. service_key (+0x68) time base — resolved¶
+0x68 (a u16) is an absolute schedule tick on the mission-sim clock (_currentT),
not a relative offset. The service walk gates on it directly against the clock — objects are
processed while key <= deadline and skipped while _currentT + 1 < key ((&_objPtrs)[id] +
0x68) — so the chain is a priority queue keyed by absolute due-time. The re-queue writes the
next key through TimeAddSat (0x463B90), which saturates near 0x7FFF instead of
wrapping (the same guard applied to DAT_0050CED2/CED4), matching _currentT's 0x7F9A
cap. So an object due "now + N ticks" stores TimeAddSat(_currentT + N), and near end-of-scale
the key sticks at 0x7FFF rather than wrapping past the clock.
Status: resolved — re-static.
Related¶
- shape-selection.md — the whole-model damage swap and the type-record shape fields this system's objects carry.
- structs.md — the full per-instance entity record mirrored into
_cg. - game-loop.md — where
ServiceObjectssits in the per-frame update. - physics.md — the flight model that runs inside an aircraft's service
slot and drives
PLANEBreakUp. - network.md — the multiplayer layer that produces the remote hit and effect messages, and consumes object aliases.
Flight Model & Stores¶
The aircraft flight model (FM_*) and hardpoint/stores management (HARD_*) —
how the engine turns pilot/AI control inputs and a loadout into per-frame aircraft state:
gear, flaps, wing sweep, thrust vectoring, weapon-bay doors, throttle, fuel burn, weight,
and the rearm/repair bookkeeping behind it. All virtual addresses are from the shipping
binary. Fixed-point values use the engine's standard s8.8 format (divide by 256 for real
units) unless noted.
Scope: this is the reconstruction-program
flight-modelsubsystem (#212) —FM_*+HARD_*,0x451480–0x454800. The broader physics story it used to bundle lives in adjacent subsystems: collision (_Collision,COL_*) is222, weapons/projectiles (
PROJ_*, the seeker/ballistics model) is #215, and¶terrain height/queries (
T_*) is #221. The sections below that describe those (§ Terrain Collision, § PROJ dispatch, § Collision Detection) are provisional and migrate to those docs as they land.Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; every flight-model symbol here is recorded in the symbol database and applied to the Ghidra project. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
Globals — Key Physics State¶
These globals are polled by nearly every function in the flight model. All are
per-player (the flight model operates on the current object selected by _curId).
| Global | Address | Role |
|---|---|---|
DAT_0050ceb4 |
0x50CEB4 |
Current airspeed (s16.8 knots) |
DAT_0050ce9f |
0x50CE9F |
Pitch angle (s16, engine units) |
DAT_0050ce95 |
0x50CE95 |
Altitude (absolute, engine units — 1 unit = 1 ft) |
DAT_0050ceaf |
0x50CEAF |
Ground elevation below aircraft |
DAT_0050ce9d |
0x50CE9D |
Heading angle |
DAT_0050cea1 |
0x50CEA1 |
Bank angle |
DAT_0050ce91 |
0x50CE91 |
X world position |
DAT_0050ce99 |
0x50CE99 |
Z world position |
DAT_0050d06e |
0x50D06E |
Current throttle position (s16.8, smoothed) |
DAT_0050d072 |
0x50D072 |
Target throttle % (0–100) |
DAT_0050d07c |
0x50D07C |
Internal fuel quantity (s16.8) |
DAT_0050d01b |
0x50D01B |
G-load (s16.8, 0x100 = 1 G) |
DAT_0050d08c |
0x50D08C |
Stall state byte (0=normal, 1=warning, 2=stall, 3=deep stall, 4=snap) |
DAT_0050cfef |
0x50CFEF |
HUD/state flags word — bit 0x40 = gear down, bit 0x20 = afterburner, bit 0x80 = low fuel, bit 0x200 = bay open |
_onTheGround |
(SMS) | Boolean: aircraft on ground |
1. Flight Model Overview¶
The player flight model runs inside _FMFlight@0. For NPC aircraft the same PT data
is used but fuel burn goes through @FMBurnNPCFuel@4.
_FMFlight@0 — 0x47B020¶
FA.SMS name: _FMFlight@0
Main per-frame flight update for the player aircraft. Called from the flying loop. Entry sequence:
_FMAircraftSetup_0()— copies PT type fields to the working globals (see §7)._F24ToPA_4(DAT_0050d013)→DAT_0050ce9f— converts stick pitch to pitch angle._SetThrottle_4(_throttle)— applies throttle input._BurnFuel_0()— deducts fuel for this tick.- Gear, gear pitch, weapon bay, thrust vector, and wing sweep update calls.
- Stall detection: compares
DAT_0050ceb4 >> 8against_oneGStallSpeed__3JA. State machine transitions throughDAT_0050d08cvalues 0→1→2→4; state 2 applies control authority reduction proportional to(stallSpeed - currentSpeed) / stallSpeed. - Stick inputs fed through
_StickInput_28for pitch (DAT_0050d01b), roll (DAT_0050cfff), yaw (DAT_0050d007), and thrust-vector nozzle (DAT_0050d12f/DAT_0050d133).
@FMFuelConsumption@4 — 0x451E50¶
FA.SMS name: @FMFuelConsumption@4
int _FMFuelConsumption_4(int throttle_pct)
{
if (100 < throttle_pct)
return (int)DAT_0050d3cb << 8; // afterburner fuel flow (PT field)
return (DAT_0050d3c9 * throttle_pct * 0x100) / 100; // mil power flow × pct
}
Parameters: throttle_pct — integer 0–100 (or >100 for afterburner).
Returns: fuel flow rate in s16.8 units per tick.
Reads: DAT_0050d3c9 (mil power fuel flow, PT field), DAT_0050d3cb (AB fuel flow, PT field).
Callers: _PLANECheckFuel@0 (0x49FB70), FUN_00451e8b (0x451E8B — the player fuel tick).
_BurnFuel@0 — 0x451E80¶
FA.SMS name: _BurnFuel@0
Deducts fuel from the internal tank (DAT_0050d07c) and from external hardpoint
stores (via _HARDFindStore_12). Called directly by _FMFlight@0 (0x47B020) and
@FMBurnNPCFuel@4 (0x452050). The player tick wrapper FUN_00451e8b calls
_FMFuelConsumption_4 to get the current flow rate and deducts 5 units per 5-tick
service interval; when an external tank empties it is automatically jettisoned and
the HUD message "Empty external tanks automatically jettisoned" is played with the
_BOMB_11K sound.
@FMBurnNPCFuel@4 — 0x452050¶
FA.SMS name: @FMBurnNPCFuel@4
NPC fuel burn. param_1 is the NPC's current fuel quantity. Computes a throttle
percentage from the fuel level relative to PT thresholds (DAT_0050d0cb / DAT_0050d0cf),
smooths towards the target via _MatchF24_12, then calls _BurnFuel_0(). Also triggers
the afterburner HUD flag (bit 0x20 of DAT_0050cfef) when fuel is critically low and
the PT has an afterburner (DAT_0050d3bb != 0).
2. Aerodynamic Parameters¶
Stall¶
| Symbol | Address | Role |
|---|---|---|
?oneGStallSpeed@@3JA |
0x54B72C |
1-G stall speed (knots, integer) — threshold for stall warning |
?stallVol@@3GA |
0x5718E4 |
Stall warning audio volume |
?stallString@@3PADA |
0x4EC9E8 |
Stall text string pointer |
?stallWarningString@@3PADA |
0x4EC9F0 |
Stall-warning text string pointer |
?liftFailString@@3PADA |
0x4ECA08 |
Lift-fail text string pointer |
Stall onset: _FMFlight@0 checks (DAT_0050ceb4 >> 8) <= _oneGStallSpeed__3JA + 25.
The margin of 25 knots above 1-G stall speed is the warning threshold.
Stall sounds: _STALL_5K (looped) at full _stallVol during stall state 2;
_STALLWR_5K (looped) at 55% volume during state 1 (warning).
Flight envelope check uses _CheckFlightEnvelope_8 (reads DAT_0050d32a–DAT_0050d32c
for the altitude-band range), _GetFlightEnvelope_4, and _EnvelopeSpeedLimits_16.
Return codes from _CheckFlightEnvelope_8: 0 = normal, 1 = stall warning,
2 = stall, 3 = deep stall / snap.
Turn Rate and G¶
| Function | Address | Role |
|---|---|---|
_COTurnRate@0 |
0x4780D0 |
Returns current sustained turn rate. For aircraft (_cg == 4) calls _GToTurn_8(PT.brv[5], airspeed); for ground vehicles returns DAT_0050d2c5. |
_COBankRate@0 |
0x478090 |
Returns current bank rate. For aircraft with _cg == 4 returns PT.brv[1] * 0xB6; caps at 0x7FFF. |
_GToTurn_8 |
(SMS) | Converts G-load + airspeed to turn-rate. Used in _FMFlight@0 for DAT_0050d003 (current turn demand). |
_MaxGAtAlt_0 |
(SMS) | Returns the flight-envelope index for max-G altitude band; used to look up corner speed via _GetFlightEnvelope_4. |
_COGPullDrag@0 |
0x4784A0 |
Returns G-induced drag increment. Computes (DAT_0050d3d3 * (gPullLoad * DAT_0050d3e3 / 100 + 100)) / 100. |
_COGPullDrag@0 reads two PT drag fields: DAT_0050d3d3 (base induced drag coefficient)
and DAT_0050d3e3 (G-drag scaling factor). DAT_0050d0dd and DAT_0050d0df hold the
accumulated G load across pitch and yaw axes.
Thrust¶
@COThrust@4 (0x478190) returns DAT_0050d3b7 (mil thrust) or DAT_0050d3bb
(AB thrust) depending on the param_1 AB flag. In multiplayer with _gameMultiPrefs
bit 0x10 set, thrust is halved for AI opponents.
Mach¶
@SpeedOfSound@4 (0x412780) is present in FA.SMS. Mach number is not stored as an
explicit global; the engine scales indicated airspeed from knots using the BRF
fixed-point convention (6,076 units = 1 nm; 1 unit = 1 ft).
Wing Sweep¶
_FMUpdateWingSweep@0 (0x4515E0) — for variable-sweep aircraft. Reads
_COMinSpeed_0() and _COMaxSpeed_0(), linearly interpolates sweep position
_DAT_0050d021 from 0 to 0x7FFF across the speed range
[minSpeed, (minSpeed + maxSpeed)/2].
3. _PROJProc Virtual Dispatch¶
Wrapper — FUN_004C1F10 (0x4C1F10)¶
No FA.SMS name. Confirmed to read entity field +0x4 from the PROJ entity. Callers
list in the Ghidra output is empty — the wrapper is invoked indirectly through the
object-update loop's utilProc pointer, not via a direct call chain that Ghidra could
trace statically. The architecture.md table maps _PROJProc as the utilProc
symbol for .JT projectile files.
Dispatch — 0x4C1F50 and PROJMoveProc — 0x4C11B0¶
Both addresses returned NOT FOUND in the Ghidra analysis; neither contains a decompilable function at those exact VAs. They are likely thunks or mid-function entry points in the PROJ update path. The closest named functions in the vicinity are:
| VA | Name | Notes |
|---|---|---|
0x4C1120 |
_PROJSpeed@8 |
Computes clamped projectile speed |
0x4C1170 |
_PROJEngineState@0 |
Returns 0/1/2 for motor phase (boost / coast / burnout) |
0x4C20C0 |
_PROJHit@8 |
Hit detection; reads entity +0x55 |
0x4C2170 |
_PROJFire@16 |
Fire initialisation; reads entity +0x50 |
0x4C26F0 |
_PROJFireSound@4 |
Fire sound; reads entity +0x7F |
0x4C2860 |
_PROJInFOV@40 |
FOV check; reads entity +0x50 |
_PROJSpeed@8 — 0x4C1120¶
int _PROJSpeed_8(int proj_type_ptr, int throttle_pct)
{
int spd = (*(byte*)(proj_type_ptr + 0x115) * (throttle_pct >> 8)) / 100;
if (spd < *(short*)(proj_type_ptr + 0xFB)) spd = *(short*)(proj_type_ptr + 0xFB);
if (spd < *(short*)(proj_type_ptr + 0x67)) spd = *(short*)(proj_type_ptr + 0x67);
if (*(short*)(proj_type_ptr + 0x6B) < spd) spd = *(short*)(proj_type_ptr + 0x6B);
return spd;
}
Reads three PROJ_TYPE speed fields at +0x67 (min speed floor), +0x6B (max speed
cap), +0xFB (boost minimum), and +0x115 (throttle scaling byte).
_PROJEngineState@0 — 0x4C1170¶
Returns the motor phase for the current projectile:
- 0 — boost phase (time since fire < ram0x0050d367)
- 1 — sustain phase
- 2 — coast / burnout (time since fire >= DAT_0050d369)
Reads DAT_0050cf68 (fire timestamp) and ram0x0050d367 / DAT_0050d369 (phase
durations from the JT type). Turn rates in _COTurnRate@0 for _cg == 6 (missile)
dispatch through this state machine: state 0 returns 0, state 1 returns DAT_0050d36d,
state 2 returns _DAT_0050d36f.
4. Terrain Collision¶
_GetGround@0 — 0x47AF20¶
FA.SMS name: _GetGround@0. Reads entity field +0x1 (confirmed from offset scan).
Returns the ground elevation at the current object's position. Called by terrain
avoidance and landing logic throughout the flight model.
The Ghidra script queried 0x47AF70 under the label _GetGround@0 but found
_FMSetTV@8 at that address — the correct VA from the offset scan is 0x47AF20.
_FMSetTV@8 — 0x47AF70¶
FA.SMS name: (none found; internal FM helper). Configures thrust-vector nozzle
direction flags _dirTVY / _dirTVZ and the corresponding _onTVY / _onTVZ
booleans. Refuses to activate thrust vectoring while _OnTheGround_0() is true.
Callers: ?TVKey@@YIGG@Z (0x413C70), @FlightKey@4 (0x414690), _FMFlight@0
(0x47B020), _FlightMenu (0x474800), _FMResetTV@0 (0x47B000).
_T_Info@24 — 0x4ABAB0¶
FA.SMS name: _T_Info@24. Returns terrain elevation at a world-space point. Called by
@COLPitchToAvoidTerrain@0 to find the ground level 250 ft ahead of the aircraft:
iVar1 = _T_Info_24(0, NULL, look_ahead_pos, NULL, 0, 0);
clearance = altitude - iVar1 - DAT_0050d2dd;
DAT_0050d2dd is the terrain avoidance margin.
_COLPitchToAvoidTerrain@0 — 0x42DF80¶
Computes the pitch command needed to avoid terrain. Projects a point 250 ft ahead
using _Rotate2_8 and the current heading DAT_0050ce9d, then queries _T_Info_24.
The required pitch-up is iterated from −90° to +90° (−0x3FFC to +0x3FFC in engine
units) in 0x38E increments until vertical clearance is positive. Returns the cached
pitch value DAT_0050ceda; the cache expires based on whether the aircraft is in a
critical regime (DAT_0050ce9f < -0x71C or terrain clearance < 0xBB800 = ~3 nm),
refreshed every 1 vs. 4 ticks accordingly.
do_use_terrain_detail — 0x4D2344 and expandTerrain — 0x50E145¶
Neither address resolved to a function in the Ghidra output. _expandTerrain (the
global, not a function) is used as a flag at 0x50E145-vicinity code: it is set to 1,
_coarse is set to 1, a terrain dispatch via vector_table fires, then both are
cleared. This confirms _expandTerrain is a render-quality hint that forces the
terrain renderer to emit extra-detail geometry for the current tick.
5. PROJ_TYPE Physics Fields — Offset Scan 0x50–0x7F¶
Offsets confirmed used by projectile functions. All relative to the PROJ_TYPE base
pointer (the .JT BRF data pointer passed as param_1).
| Offset | Confirmed by | Likely field |
|---|---|---|
+0x50 |
_PROJFire@16, _PROJInFOV@40, FUN_004c2b5a |
Projectile class / type word |
+0x55 |
_PROJHit@8 |
Hit-detection radius or damage flags |
+0x57 |
FUN_004c0a9d |
Proximity fuze or arming distance |
+0x60 |
FUN_004c24b0 |
Seeker or guidance parameter |
+0x64 |
_PROJSpeed@8 |
Speed parameter block (also confirmed as @FMFuelConsumption@4 offset in throttle context) |
+0x67 |
_PROJSpeed@8 |
Minimum speed (s16) |
+0x6B |
_PROJSpeed@8 |
Maximum speed (s16) |
+0x7F |
FUN_004c1c10, _PROJFireSound@4 |
Fire sound index or audio flags |
The offset-0x64 collision between @FMFuelConsumption@4 and _PROJSpeed@8 is not a
conflict: @FMFuelConsumption@4 uses global DAT_0050d3c9 (mapped from the PT type
area at 0x50D3xx) while _PROJSpeed@8 works on a passed proj_type_ptr. They
happen to share the same local offset within their respective type structs.
6. Collision Detection¶
_Collision@56 — 0x42B800¶
FA.SMS name: _Collision@56. The main broad-phase + narrow-phase collision query.
Signature:
void _Collision_56(
ushort obj_id, // param_1 — object to test against
char *last_hit_cache, // param_2 — optional per-object cache (NULL = no caching)
byte flags, // param_3 — bit 0x80 = use cache; bits 1/4/8/10 = test modes
int *pos_a, // param_4 — start position [x,y,z]
int *pos_b, // param_5 — end position [x,y,z]
int radius, // param_6 — swept-sphere radius
short sweep_len, // param_7 — sweep length hint
ushort col_flags, // param_8 — bit 0x1 = terrain test; bit 0x4 = mesh test; bit 0x200 = no-radius
ushort *result_type, // param_9 — OUT: 0=miss, 0xFFFF=terrain, else mesh-hit index
int *hit_pos, // param_10 — OUT: hit world position [x,y,z]
ushort *surface_id, // param_11 — OUT: mesh surface ID (optional)
ushort *poly_id, // param_12 — OUT: polygon ID (optional)
short *hit_normal, // param_13 — OUT: surface normal (via _COLSetAngle_8)
int cache_write // param_14 — if non-zero, writes back to last_hit_cache
);
When col_flags & 1 is set (terrain test), calls _COLFlatGround__YIDJPAUF24_POINT3__00_Z
for the coarse flat-terrain check, then optionally _T_GetLeaf_12 for the detailed
terrain leaf hit. When col_flags & 4 is set, calls FUN_0042c9b0 for the mesh sweep.
When col_flags & 0xA is set, calls FUN_0042c840 for the broad-phase AABB sweep.
The cache (last_hit_cache) skips the full geometry query if the object position
matches the previous tick's stored values; cache validity is bounded by
_currentTicks + 1 or _currentTicks + 0x100 depending on the hit object's
altitude and armor state.
?IntersectT@@YAJPAUF24_POINT@@JJ@Z — 0x447970¶
FA.SMS name (demangled): IntersectT(F24_POINT*, long, long).
Ray-sphere intersection test. Takes a point record, a minimum distance, and a maximum distance; returns the intersection parameter along the ray or 0 on miss.
int _IntersectT(int *point, int t_min, int t_max)
{
if (t_min <= point[1])
{
int t = _MultDiv32_12(*point >> 8, t_min >> 8, point[1] >> 8);
if (t < t_max) return t;
}
return 0;
}
Terrain flat-ground test — _COLFlatGround__YIDJPAUF24_POINT3__00_Z¶
Called by _Collision@56 with the object ID, start/end positions, and an output
buffer for the hit point. Returns non-zero on terrain hit; if detailed terrain
(_th != 0) is active, calls _T_GetLeaf_12 to get the surface classification byte
from the T2 tile, which is then passed to FUN_0042de60 for the landing/damage event.
7. PT_TYPE (Aircraft Performance Type) — Field Mapping¶
_FMAircraftSetup@0 (0x47A690) copies PT type data into the working globals at
0x50D3xx and 0x50D38x. This copy is the _cgt type-record mirror: it is
byte-for-byte at base 0x50D268, so a global DAT_[0x50D268 + X] reads
exactly PT offset X. The source offsets below are therefore DAT − 0x50D268;
the 65-word aero block (0xCA–0x14B) is fully mapped in
PT.md § The 65-word aerodynamic block.
| Global (after setup) | PT offset | Confirmed by | Role |
|---|---|---|---|
DAT_0050d3b5 |
0x14D |
FUN_00451e8b |
Afterburner spool-up time |
DAT_0050d3b7 |
0x14F |
@COThrust@4 |
Mil-power thrust (= military_thrust) |
DAT_0050d3bb |
0x153 |
@COThrust@4, @FMBurnNPCFuel@4 |
AB thrust (= afterburner_thrust; 0 = no AB) |
DAT_0050d3bf |
0x157 |
FUN_00451e8b |
Throttle ramp-down (= throttle_accel) |
DAT_0050d3c1 |
0x159 |
FUN_00451e8b |
Throttle ramp-up (= throttle_decel) |
DAT_0050d3c3 |
0x15B |
_FMInitPlane@8 |
Min nozzle angle (= tv_min_angle) |
DAT_0050d3c5 |
0x15D |
_FMInitPlane@8, _ThrustSupport@0 |
Max nozzle angle (= tv_max_angle; 0 = no TVC) |
DAT_0050d3c7 |
0x15F |
_FMUpdateThrustVector@0 |
Nozzle slew rate (= tv_speed) |
DAT_0050d3c9 |
0x161 |
@FMFuelConsumption@4 |
Mil-power fuel flow (= fuel_consumption_mil) |
DAT_0050d3cb |
0x163 |
@FMFuelConsumption@4 |
AB fuel flow (= fuel_consumption_ab) |
DAT_0050d3cd |
0x165 |
_FMInitPlane@8 |
Initial/full fuel load (= fuel_capacity) |
DAT_0050d3d3 |
0x16B |
_COGPullDrag@0 |
G-induced drag base coefficient (= g_drag) |
DAT_0050d3db |
0x173 |
_FMInitPlane@8 |
Gear-up spawn flag (PT.md: gear_drag) |
DAT_0050d3e3 |
0x17B |
_COGPullDrag@0 |
G-drag scaling factor (= g_drag_loaded) |
DAT_0050d3e5 |
0x17D |
_FMUpdatePlaneFields@0 |
Damage drag penalty (PT.md: gear_pitch) |
DAT_0050d32a |
0xC2 |
_FMUpdatePlaneFields@0 |
Neg-G envelope count (= neg_g_count; low altitude band) |
DAT_0050d32c |
0xC4 |
_FMUpdatePlaneFields@0 |
Pos-G envelope count (= pos_g_count; high altitude band) |
DAT_0050d322 |
0xBA |
_FMInitPlane@8, _FMUpdatePlaneFields@0 |
PT capability flags (= carrier_flags; bit 3 = carrier, bit 0x1C = TVC, bit 0x400 = snap stall) |
DAT_0050d3a8 |
0x140 |
_FMUpdateGearPitch@0 |
Gear-down pitch-trim authority (aero word 59, × 0xB6) |
The flight-envelope data (_GetFlightEnvelope_4, _EnvelopeSpeedLimits_16,
_StallSpeed@4) is indexed by altitude band (integer 0–DAT_0050d32c). Each
envelope entry holds a min speed, a max (corner) speed, and a stall speed; the
interpolation in _FMUpdatePlaneFields@0 produces DAT_0050d0d7 (min safe speed)
and DAT_0050d0d9 (best turn speed) every game tick.
_StallSpeed@4 (0x49D1D0) is a thin wrapper:
uint _StallSpeed_4(short *envelope_entry)
{
uint spd;
_EnvelopeSpeedLimits_16(envelope_entry, &spd, NULL, NULL);
return max(spd, 1);
}
8. Dark Zone 0x4D0000–0x4EFFFF¶
This range is the 3D renderer and rasteriser — not a physics dark zone in the traditional sense. It has no FA.SMS symbols but contains dense, hand-optimised x86. Notable functions found:
| VA | Internal name | Role |
|---|---|---|
0x4D028C |
FUN_004d028c |
Polygon clip / projection kernel. Takes a packed shift word; applies the 3×3 rotation matrix (m1–m9, _scaled_matrix) to a scaled vertex and tests all six clip planes. Returns a signed distance for the determining clip edge. |
0x4D0494 |
get_sort_dist |
Painter's-algorithm sort key. Computes |xv32| + |yv32| + |zv32| using abs-and-add approximation plus a per-object size bias from *(ushort*)(EDI - 0xC) * 0x100. |
0x4D057C |
_GRAddBrentObj@40 |
Adds a BRF object to the render list. Transforms object-relative position to viewer-relative, scales, calls FUN_004d028c for the clip test, calls get_sort_dist, then writes a 0x30-byte sort entry to obj_ptr / cur_sort_ptr. |
0x4D0798 |
FUN_004d0798 |
BRF shape renderer. Saves the rotation matrix and viewer-relative components (_xv, _yv, _zv), calls _WRSetRemaps_8 for palette remapping, calls FUN_004ce784 for the perspective divide, dispatches to the shape-type draw routine via vector_table, then restores state. |
0x4D1694 |
FUN_004d1694 |
Terrain object render entry. Computes absolute viewer-relative delta (*ESI - __viewer_x/y/z), shift-normalises, applies the rotation matrix, and dispatches to the terrain polygon pipeline. |
0x4D715A |
_DirectDrawCreate@12 |
Thin thunk to DirectDrawCreate Win32 API — marks the start of the DirectDraw IAT stub block. |
Key renderer globals in this range:
| Global | Role |
|---|---|
_scaled_matrix, m2–m9 |
Current 3×3 rotation matrix (s16 fixed-point) |
_xv, _yv, _zv |
Current vertex in viewer space (s16) |
_xv32, _yv32, _zv32 |
Same in s32 for high-precision paths |
__viewer_x/y/z |
Camera world position |
obj_ptr / cur_sort_ptr |
Write cursors into the render / sort list |
_expandTerrain |
Set to 1 during the terrain detail render pass |
_coarse |
Set to 1 alongside _expandTerrain for the detail pass |
_overflow |
Set to 0xFFFF when a vertex overflows the clip range |
axis_check_type |
Selects the axial clip variant via PTR_LAB_004d04dc |
_expandTerrain and _coarse are not function VAs — they are boolean globals
toggled around the terrain dispatch call. The query for function expandTerrain @
0x50E145 found them being set at that address as part of a larger terrain-update
function that builds a terrain command buffer before dispatching through vector_table.
Summary — Function Quick Reference¶
| VA | FA.SMS name | Section |
|---|---|---|
0x42DF80 |
@COLPitchToAvoidTerrain@0 |
§4 |
0x42B800 |
_Collision@56 |
§6 |
0x447970 |
?IntersectT@@YAJPAUF24_POINT@@JJ@Z |
§6 |
0x451E50 |
@FMFuelConsumption@4 |
§1 |
0x451E80 |
_BurnFuel@0 |
§1 |
0x452050 |
@FMBurnNPCFuel@4 |
§1 |
0x47AF20 |
_GetGround@0 |
§4 |
0x47AF70 |
_FMSetTV@8 |
§4 |
0x47B020 |
_FMFlight@0 |
§1 |
0x478090 |
_COBankRate@0 |
§2 |
0x4780D0 |
_COTurnRate@0 |
§2 |
0x478190 |
@COThrust@4 |
§2 |
0x4784A0 |
_COGPullDrag@0 |
§2 |
0x4515E0 |
_FMUpdateWingSweep@0 |
§2 |
0x4ABAB0 |
_T_Info@24 |
§4 |
0x49D1D0 |
_StallSpeed@4 |
§7 |
0x49FB70 |
_PLANECheckFuel@0 |
§1 |
0x4C1120 |
_PROJSpeed@8 |
§3 |
0x4C1170 |
_PROJEngineState@0 |
§3 |
0x4C1F10 |
FUN_004c1f10 (PROJ wrapper) |
§3 |
0x4D028C |
FUN_004d028c (clip kernel) |
§8 |
0x4D0494 |
get_sort_dist |
§8 |
0x4D057C |
_GRAddBrentObj@40 |
§8 |
0x4D0798 |
FUN_004d0798 (shape renderer) |
§8 |
Update flow¶
Each frame, the object system services the aircraft entity; for a player/AI plane the
control inputs (throttle, stick, gear/flap/brake/hook/bay commands) are folded into the
mirrored entity state, FMUpdatePlaneFields recomputes the derived aircraft state
(weight from the current loadout via HARDPtrs, thrust from FMVector/throttle, drag
and fuel burn), and the result is written back. Stores management (HARD_*) sits beside
it: the loadout on each hardpoint sets the weight FMGetWeight sums, and rearm/repair
rebuild the loadout between sorties.
Functions¶
Representative subset; the full record is in
db/symbols/flight-model.csv.
| VA | Symbol | Role |
|---|---|---|
0x4518A0 |
FMInitPlane |
initialise the flight model for a spawned aircraft |
0x452140 |
FMUpdatePlaneFields |
per-frame recompute of derived aircraft state |
0x4516B0 |
FMGetWeight |
sum airframe + fuel + loadout weight |
0x451A60 |
LimitThrottle |
clamp throttle to the flight-envelope / damage limits |
0x451B00 |
SetThrottle |
apply a throttle command |
0x451B60 |
FMFlaps |
flap deployment state and effect |
0x451C90 |
FMGear |
landing-gear deployment |
0x451C30 |
FMHook |
arrestor-hook state (carrier ops) |
0x451D70 |
FMBrakes |
wheel/speed-brake state |
0x451E00 |
FMVector |
thrust-vectoring nozzle angle |
0x4515E0 |
FMUpdateWingSweep |
variable-geometry wing sweep update |
0x452630 |
FMBay |
weapon-bay door state |
0x451E50 |
FMFuelConsumption |
fuel-flow rate from throttle/engine state |
0x451E80 |
BurnFuel |
drain fuel from the tanks/stores each tick |
0x452770 |
HARDPtrs |
resolve a hardpoint's loaded store record |
0x452980 |
HARDCanLoad |
test whether a store may load on a hardpoint |
0x452C20 |
HARDLoad |
load a store onto a hardpoint |
0x452D90 |
HARDBestSeekers |
rank a target's seeker-capable weapons |
0x453220 |
HARDUnrotatedHardPos |
hardpoint position in the airframe frame |
0x453890 |
HARDStoreName |
resolve a loaded store's resource-name string |
0x453A70 |
HARDTotalFuel |
total fuel including external tanks |
0x453B90 |
HARDRearmTest |
can this loadout be rearmed at the current base |
0x454140 |
ChangePlaneType |
swap the aircraft to a different type record |
0x4543C0 |
SelectRepairPlane |
pick/repair an aircraft in the campaign rearm flow |
Open Questions¶
1. Fuel-flow scaling constant — resolved¶
FMFuelConsumption (0x451E50) is a two-branch scale of the throttle term:
int FMFuelConsumption(int throttle) {
if (100 < throttle) return DAT_0050D3CB << 8; // afterburner: fixed rate
return (DAT_0050D3C9 * throttle * 0x100) / 100; // mil power: linear per-percent rate
}
Both DAT_0050D3C9 and DAT_0050D3CB fall inside the _cgt type-record mirror
(≈0x50D268–0x50D420; _cgt+0x161 and +0x163), which GetCurObj refreshes from the active
object's type record each frame. So the fuel rate is derived from the engine/type record
(_cgt), per-aircraft-type — not a single global tuning constant: _cgt+0x161 is the
mil-power fuel rate (scaled linearly by throttle 0–100%) and _cgt+0x163 the fixed afterburner
rate (throttle > 100%). BurnFuel (0x451E80) then drains the result.
Status: resolved — re-static.
Related¶
- reconstruction.md — the program this subsystem belongs to.
- objects.md — the entity service loop that drives the per-frame FM update
and owns the
_cg/_cgtmirror the FM state lives in. - formats/PT.md — the aircraft performance-type record whose fields feed the flight model.
- shape-selection.md —
PLANEBreakUpand the destroyed-model swap that the damage path triggers.
AI Interpreter — "Chuck Talk" (CT)¶
The bytecode virtual machine that runs the game's .AI behaviour scripts. CT is not
"control/tactics" — the interpreter's own error path prints "Chuck Talk error: %s,
line %u", so CT = Chuck Talk, the internal name of the .AI scripting language.
The VM lives at 0x464C60–0x467110; it runs the compiled .BI bytecode
(BI.md) that .AI source (AI.md) compiles to.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; every symbol here is recorded in the symbol database and applied to the Ghidra project (the ~95
CTEval_*/CTDo_*handlers are label-only in FA.SMS and are materialised as functions on apply). Progress is tracked in the reconstruction matrix. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
Two-tier dispatch with a JIT inline cache¶
CTExecProgram (0x466970) is the entry and loop. It is called with an event
priority; a running lower-priority script is preempted when a higher-priority event
arrives (_PLANEEventProc/_GVEventProc call it with 5 for a hit/threat down to 0
for the default-waypoint behaviour). Per call it restores the saved state, reloads the
program if the incoming priority beats the saved one, then runs
while (*IP != '%' && !_ctHalt) stepping one opcode at a time (capped at 5000/tick).
CTStep (0x466A80) decodes one opcode and dispatches through a C switch (a jump
table) over 0x00–0x28 — arithmetic, compare, stack, and control-flow are inline
(matching BI.md's opcode table). The condition and action handlers are
reached through two call opcodes that form an inline cache:
CALL_BY_NAME(0x27) resolves the inline handler name via_SMAddress(the Phar Lap symbol manager), calls it, then self-patches the bytecode: the opcode is rewritten toCALL_DIRECT(0x26) with the resolvedcode*stored inline. First hit resolves by name; every later hit is a direct call.
This is the game-executable-resident-handler / BI-supplies-bytecode split — the same architecture as the SH interpreter (shape-selection.md / SH.md), differing only in that CT resolves handlers by name-and-self-patch rather than a fixed vector table.
Conditions, actions, and state¶
The call targets are two families: CTEval_* stateless condition/attribute readers
(CTEval_tgt, CTEval_disttotgt, CTEval_ir, …) that read the actor's live entity
fields and push a value, and CTDo_* actions (CTDo_turn, CTDo_move,
CTDo_wm_formation, …) that pop typed arguments through the readers (CTReadHeading,
CTReadSpeed, … — all normalising to binary degrees ×182 and clamping to the aircraft's
limits) and drive the maneuver engine. The *diff conditions are all built on
CTVarDiff, which evaluates the same attribute twice — for the actor and, via
Push/PopCurObj, for the compared object — and subtracts.
There is one shared interpreter context: the 0x80-byte _ctState block (script
variables, a ~20-deep eval stack, the instruction pointer, the loaded program base and
name, the current line and priority). Per-object continuity comes from checkpointing:
CTSaveState copies the live block to a heap checkpoint and zeroes it; CTRestoreState
copies it back. So per-object AI is objects' entity fields feeding stateless evaluators,
over a single preemptible, checkpointed VM.
Functions¶
Representative subset; the full record (incl. all CTEval_*/CTDo_* handlers) is in
db/symbols/ai.csv.
| VA | Symbol | Role |
|---|---|---|
0x464C60 |
CTInit |
zero the interpreter state |
0x466970 |
CTExecProgram |
interpreter entry/loop; priority-preemptible |
0x466A80 |
CTStep |
decode + dispatch one opcode; the CALL_BY_NAME→CALL_DIRECT cache |
0x464CD0 |
CTLoadProgram |
load/switch the .BI CODE resource by name |
0x464DB0 |
CTResetPC |
reset the instruction pointer to the program base |
0x466920 |
CTSaveState |
checkpoint _ctState to the heap |
0x4668F0 |
CTRestoreState |
restore _ctState from the checkpoint |
0x466290 |
CTPush |
eval-stack push (overflow → CTError(5)) |
0x465AD0 |
CTPop |
eval-stack pop (underflow → CTError(4)) |
0x4670E0 |
CTVarPtr |
resolve a script variable slot (0–4) |
0x466820 |
CTError |
raise a "Chuck Talk error" and exit |
0x465C90 |
CTReadAngle |
pop + clamp to ±90° in binary degrees |
0x465E00 |
CTReadSpeed |
pop + clamp to the aircraft's speed envelope |
0x464DE0 |
CTVarDiff |
evaluate an attribute for actor vs compared object and subtract |
0x464F10 |
CTEval_tgt |
condition: does the actor have a target |
0x465220 |
CTEval_disttotgt |
condition: distance to target |
0x465EA0 |
CTDo_turn |
action: turn to a heading |
0x465CC0 |
CTDo_move |
action: move toward a point |
0x464C90 |
CTRespondToCancelCmdBuf |
re-enter the script after a command-buffer cancel |
Open Questions¶
1. Who writes _ctCheckPass (0x546C8C)? — resolved¶
Nobody, in the game executable. A whole-program scan of the decompile finds all 11 references to
_ctCheckPass are reads (== '\0' / != '\0' gates); there is no write, address-of, or
bulk initialiser anywhere in the image. So at runtime it is a constant 0, and every branch it
guards (the validate/dry-run path unlocking the syntax-error / expecting-var / stack-imbalance
CTError diagnostics) is dormant in the shipped game. This confirms the read: the CT core
doubles as the .AI→.BI compiler's validator, and the flag's setter lives in that offline
compiler tool, which is not linked into the game executable — the game executable ships only the interpreter, so it never
runs the load-time verification pass.
Status: resolved — re-static (no writer in the game executable; validator dormant).
2. _ctState + 0x7c/0x7e (FRAME) semantics — characterized¶
The two s16 at the tail of _ctState are touched only by the bulk CTSaveState
(0x466920) / CTRestoreState (0x4668F0) snapshot copy — there is no scalar read or write
anywhere in the game executable. Statically that is all that can be established: they are opaque
saved-and-restored interpreter state with no in-binary accessor, so their runtime meaning (the
suspected maneuver-frame / animation-phase stamp) cannot be pinned by static RE alone — it would
need the running game or the .AI compiler. Documented as characterized rather than left open,
since a further static pass cannot resolve it.
Status: resolved (characterized) — no scalar accessor in the game executable; runtime meaning needs #56.
Related¶
- formats/AI.md / formats/BI.md — the Chuck Talk source and compiled-bytecode formats this VM runs.
- objects.md — the entity mirror whose fields the
CTEval_*readers sample. - The WNG/GRP formation engine the
CTDo_wm_*actions drive (#217, forthcoming). - shape-selection.md — the sibling SH bytecode interpreter.
Wingman / Group AI (WNG / GRP)¶
Formation and flight/group management: WNG_* manages aircraft wings (a leader plus up
to 9 wingmen), GRP_* the byte-identical twin for ground/naval groups. This is the
executor the AI interpreter's wm_* commands drive. 0x45E460–0x45FEC0.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. FA.SMS exported the full
GRPAPI but only part ofWNG; every unnamed WNG function is the exact GRP twin (withGRP→WNG), which is how all 15 recovered names are pinned. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
Data model¶
A wing is _wingIds[wing*10 + slot] (ushort ids; slot 0 = leader, 1–9 = wingmen) with
_wingSizes[wing] the count — up to 10 wings, 10 groups. GRP is the same on
_groupIds/_groupSizes. There are 8 formation types (_fTypeOffsets, 8×10 slot offsets;
_wmFormType selects). The formation-command block (_wmFormControl/_wmFormType/
_wmFormSpacingH/_wmFormSpacingV, plus the shared target) is the control surface both the
AI interpreter and multiplayer messages write.
Commands: AI interpreter → WNG/GRP¶
The Chuck Talk AI interpreter's wm_ opcodes map 1:1 onto the
formation surface: CTDo_wm_hspacing/vspacing/formation/control set it, and
CTEval_wm_*_is read it back. Execution reaches WNG/GRP two ways — direct calls on the
current object (_GVEventProc → GRPSetType/SetControl/…), or via _MSGSend wingman
messages (0x8001/0x8002) whose subcode dispatch in _PLANEEventProc calls the WNG
setters (7=SpacingH, 8=SpacingV, 9=Type, 10=Control, 0x0B=attack, 0x0C/0x0D=engage,
0x11=cover-me). WNGSendWM(' ', target) fans an attack order out to all qualifying wingmen.
Formation-keeping¶
WNGFormationMove indexes slot + formType*10 into _fTypeOffsets for a (dx,dy,dz) unit
vector; the target point = (dx·spacingH + jitterX, dy·spacingV + jitterY, dz·spacingH +
jitterZ) relative to the live leader (WNGLeader returns the first living member, so
formation survives leader loss), then CreateMove issues the move order. Jitter is
re-rolled on an interval for natural drift.
Functions¶
Full record: db/symbols/wingman.csv.
| VA | Symbol | Role |
|---|---|---|
0x45E460 |
WNGInit |
zero all wing arrays and formation scalars |
0x45E490 |
WNGAdd |
add the current object to a wing slot |
0x45E710 |
WNGPart |
find id → (wing, slot); leader/wingman test |
0x45E630 |
WNGLeader |
first live member of a wing |
0x45E6E0 |
WNGWingmen |
the wingmen list (slots 1..n) |
0x45E970 |
WNGFormationMove |
position a wingman in formation vs the leader |
0x45ED90 |
WNGSendWM |
dispatch a wingman-message opcode (fan out orders) |
0x45EB30 |
WNGSetType |
set the formation type |
0x45EAC0 |
WNGSetControl |
set the formation control level |
0x45EBF0 |
WNGSetStateTarget |
enter a state + set the shared target |
0x45F190 |
GRPInit |
ground/naval-group twin of WNGInit |
0x45F1C0 |
GRPAdd |
group twin of WNGAdd |
0x45F440 |
GRPPart |
group twin of WNGPart |
0x45FC50 |
GRPAttackingObj |
count group members attacking a target |
Open Questions¶
1. _wmFormControl semantics¶
Set to 1 on Add and clamped in SetControl with a two-sided comparison; WNGSendWM
early-outs when it is > 1. Whether it is a boolean "hold formation", a member count, or a
tightness level needs the CT wm_control argument range — a bench (re-gameplay) check.
Status: open — re-gameplay (#56).
Related¶
- ai-interpreter.md — the Chuck Talk
wm_*opcodes that drive this. - objects.md — the entity/message system carrying wingman commands.
- physics.md —
CreateMove/ the flight model that executes formation moves.
Weapons — Projectiles, Seekers & ECM¶
The PROJ_* subsystem: everything a fired weapon does after it leaves the rail — guidance,
seeker lock, hit probability, detonation, and the countermeasure (ECM) interactions that
defeat it. 0x4C0690–0x4C5D30.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; every symbol is recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
A projectile is an entity with a seeker overlay¶
PROJFire (0x4C2170) launches: it resolves the hardpoint store, computes boresight
angles (PROJAimAngles), acquires a lock (PROJLock), and spawns one or more projectile
entities (PROJAdd, class 6) — cluster/ripple weapons loop. A projectile is a normal
entity whose tail carries the seeker/guidance state (target ids, launch/guidance timers,
weave and drag scratch); the weapon type record (_cgt) supplies the capability flags
(guided / ballistic / loft / powered / radar-IR-ARH seeker class / cluster / special
warhead) plus motor timing and the Pk-curve breakpoints.
Per-frame guidance and detonation¶
PROJMoveProc (0x4C11B0, owner-computer only) runs each frame:
Engine state (PROJEngineState) advances boost→burn→coast; lifetime or altitude cutoff
detonates via PROJSendCollateralDamages + RemoveCurObj. With a target it re-validates
the lock and guides (PROJGuideToTarget / PROJGuideLoft), and PROJProximityFuze fires
at closest approach; without one it searches (PROJSelectTarget → PROJScoreTarget).
PROJHitChance is the probability-of-hit model — signal × range envelope (PROJRangePk) ×
ECM × flare/chaff × aspect/closure.
ECM / countermeasures¶
Four defeat mechanisms: jammers (PROJHitChance calls HARDFindECMForObj, reducing Pk
in the seeker's band), flares/chaff (PROJLaunchDevice dispenses; PROJRetargetMissilesOnDevice
rolls seduction on each locked missile and steers seduced ones to the decoy via
PROJGuideToDevice), notch/beaming (PROJInNotch defeats pulse-doppler), and sun
decoy (PROJSunInSeeker / PROJGuideToSun for IR seekers).
Functions¶
Full record: db/symbols/weapons.csv.
| VA | Symbol | Role |
|---|---|---|
0x4C2170 |
PROJFire |
master launch: aim → lock → spawn projectile(s) → fire sound |
0x4C0A90 |
PROJAdd |
spawn a projectile entity (class 6) |
0x4C11B0 |
PROJMoveProc |
per-frame guidance / detonation proc |
0x4C2F20 |
PROJLock |
seeker lock acquisition |
0x4C1630 |
PROJGuideToTarget |
proportional guidance to the lock target |
0x4C1660 |
PROJGuideLoft |
loft / high-trajectory guidance |
0x4C3250 |
PROJProximityFuze |
closest-approach detonation decision |
0x4C3380 |
PROJHitChance |
probability-of-hit model |
0x4C3890 |
PROJRangePk |
range→Pk envelope lookup |
0x4C39A0 |
PROJLaunchDevice |
dispense chaff / flare |
0x4C3AF0 |
PROJRetargetMissilesOnDevice |
countermeasure seduction roll + retarget |
0x4C2E40 |
PROJInNotch |
Doppler-notch / beaming detection |
0x4C17F0 |
PROJSunInSeeker |
IR sun-decoy check |
0x4C4100 |
PROJSelectTarget |
autonomous seeker target search |
0x4C1870 |
PROJDamageProc |
apply impact damage + kill scoring |
0x4C1F50 |
PROJProc |
class-proc selector (move/event/damage) |
Open Questions¶
1. Lock-tone / RWR bookkeeping globals — resolved¶
They are a multi-slot lock-timing table, keyed on the mission clock _currentT. Two kinds
of field, all zeroed by PROJInit and read in the PROJ lock-state aggregation:
- Expiry timestamps tested
<= _currentT— the track-lock group around_trackLockEndT(0x58F100):0x58F102/104/106/108; and the search-lock group around_searchLockEndT(0x58F1C0):0x58F1C2/1C4/1C8. Each marks when that lock slot's window elapses. - Active counters/flags tested
< 1—0x58F1DC/1DE/1E0, the "lock still up" gates.
A single compound predicate (near 0x4C…) ANDs all of these together with
_projLocksOnPlayer to decide "no lock currently on the player" — i.e. this table is the state
behind RWR silence vs. the search/track lock-tone. So they are confirmed lock-timing slots,
not opaque: expiry stamps + active flags for the per-slot track/search locks.
Status: resolved — re-static (lock-timing slots: <=_currentT expiry stamps + <1 active flags).
Related¶
- physics.md — the
HARD_*stores management that arms the hardpoints and supplies seekers/ECM lookups. - objects.md — projectiles are entities; the mirror carries their state.
- hud.md — the target box, gun reticle, and CCIP pipper that display lock state.
- formats/JT.md — the projectile type record.
Collision (COL)¶
The swept collision query engine — one entry point, _Collision (0x42B800), that answers
"what does this segment hit first?" against terrain and against registered objects. Used
for weapon impacts, line-of-sight/lock gating, ground-avoidance AI, and landing.
0x42B800–0x42E680.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
One query, two phases, two result slots¶
_Collision stores the ray, computes a swept world AABB, and dispatches on a flag word:
- Terrain path (
flags & 1): a precise swept walkCOLSweepTerrainover ≤20 grid cells (COLClipSegToCell→COLTestTerrainCell→COLTestTerrainTri), with a coarseCOLFlatGroundfallback. - Object path: broad-phase
COLTestObjectsiterates the frame's registration list (COLAddObj/_colObjList, structures in_colStructList) with a swept-AABB reject, then narrow-phaseCOLTestObjtransforms the ray into each object's local frame and clips against its oriented box hierarchy (COLClipSegToBox, fromCOLGetInfo).
Both paths feed COLRecordHit, which keeps the nearest hit in two slots — a blocking
slot (terrain + special structures, surfaced as 0xffff) and a nearest-object slot
(surfaced as an object id) — with a terrain-priority bias.
Data model¶
A per-shape COLInfo block (X86 region tag 0xf2, resolved by COLGetInfo) holds a list
of 0xe-byte oriented boxes (6 bounds ×0x100 + id/flags); the terrain _th grid supplies
leaf heights/normals (T_GetLeaf/T_Normal); and the collision scratch block
(_colRay*/_colBound*/_colBlock*/_colObj*, 0x536728–0x537254) carries a query's
inputs, swept bounds, and two nearest-hit slots as file-scope globals — one query at a time.
Functions¶
Full record: db/symbols/collision.csv.
| VA | Symbol | Role |
|---|---|---|
0x42B800 |
Collision |
master swept collision query (the single entry) |
0x42BDC0 |
COLSweepTerrain |
swept segment-vs-terrain grid walk |
0x42BFC0 |
COLTestTerrainCell |
test one terrain cell's two triangles |
0x42C1A0 |
COLTestTerrainTri |
segment vs one terrain triangle plane |
0x42C840 |
COLTestObjects |
object broad-phase (AABB over registered ids) |
0x42C9B0 |
COLTestObj |
object narrow-phase (ray in local frame, box hierarchy) |
0x42D050 |
COLClipSegToBox |
segment vs oriented box (6-plane clip) |
0x42DE60 |
COLRecordHit |
keep-nearest hit accumulator (two slots) |
0x42DDA0 |
COLFlatGround |
coarse ground-plane crossing test |
0x42DF80 |
COLPitchToAvoidTerrain |
AI pull-up pitch to clear terrain ahead |
0x42E0C0 |
COLGetInfo |
resolve a shape's collision-info block |
0x42E4E0 |
COLTerrainBlocking |
terrain LOS gate to a target box |
0x42E540 |
COLAddObj |
register the current object as collidable this frame |
Open Questions¶
1. _Collision flag-word bit map — resolved¶
The flag word is _Collision's 8th argument, stored in _colFlags (0x00536B08). The low
bits were already confirmed; a read of COLTestObj's bit tests pins the object class/side
filters:
| Bit | Meaning (from COLTestObj) |
|---|---|
0x1 |
test terrain |
0x2 |
structure broad-phase (also gated on the object's +9 flags & 0x408000) |
0x4 |
single-target — only test _colTargetId |
0x8 |
all-objects sweep |
0x10 |
ignore side — bypass the same-side filter |
0x20 |
same-side filter — require ((target[+9] ^ _colSelfSide) & 0x80) == 0 (the 0x80 side bit) |
0x40 |
class-4 filter — gate objects whose class byte == 0x04 |
0x80 |
class-filter override — bypass the per-object [+?] & 0x40 exclusion in the sub-object loop |
0x200 |
skip-self-predict |
Call sites confirm the low bits as literals (0x201 = terrain + skip-self from the airframe
move path; 0x4 = single-target from the projectile path); the 0x10–0x80 class/side bits
are computed per caller (MoveObj, PROJ) and consumed by COLTestObj as above.
Status: resolved — re-static (0x10/0x20 = side filters, 0x40/0x80 = object-class filters).
Related¶
- physics.md —
COLPitchToAvoidTerrainfeeds the flight model's ground avoidance. - weapons.md —
PROJuses_Collisionfor impact and LOS/lock gating. - objects.md —
COLAddObjregisters entities; hits reference object ids. - The terrain grid (
_th,T_GetLeaf/T_Normal) — terrain subsystem (#221, forthcoming).
Campaign / Mission / Pilot¶
The single-player meta-game: the theater mission map screen, the scripted ZONE
threats, pilot save/logbook, and the .CAM campaign state machine that strings
missions together with scoring. Re-carved from a grab-bag nominal range into its true
clusters (the mission-map editor 0x421C70–0x42B800 plus pilot/campaign/mission cores in
0x467110–0x490000).
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
The pieces¶
- Mission map (
MAP*,0x421C70–0x42B800): the theater-map screen — world↔screen coordinate transforms (MAPScreenToWorld), background/grid/icon/path drawing, waypoint editing (MAPWPListBounds), and the object placement the mission builder uses. - ZONE threats: scripted threat zones that fire projectiles/explosions on a time
schedule —
ZONEActive(active-window test),ZONEServiceRange,ZONEPickTarget. - Pilot (
PLT*,0x467180–0x468FD0): the pilot save (PILOT.BKP), sorted rosters, and the logbook / "paper" record builder. - Campaign (
.CAMstate machine,0x480750–0x4869A0):_CallCampaignProcdrives the_campaignState(active → dead/fail → planning); mission load runs through_CallMissionProc+_MISSIONInit2, and scoring accumulates via_MISSIONAddScore.
Functions¶
Full record: db/symbols/campaign.csv.
| VA | Symbol | Role |
|---|---|---|
0x4224B3 |
MAPScreenToWorld |
inverse map projection (screen → world) |
0x42256A |
MAPLoadBG |
load the theater-map background |
0x421D40 |
ZONEActive |
scripted-threat active-window test |
0x422120 |
ZONEPickTarget |
pick a target plane for a zone |
0x422190 |
MAPWPListBounds |
walk a waypoint list |
Open Questions¶
1. TIME/FPS utility block — resolved¶
0x4869A0–0x486E60 is a self-contained game-clock + FPS + timer-interrupt cluster, not
campaign code. Its functions are almost all FA.SMS-named: TIMESystemTime (0x4869A0),
TIMEInit/TIMERestart/TIMEUpdate, TIMESetCompression (0x486C60), FPSInit/FPSUpdate/
FPSPrint/FPSPrint2/FPSReturn, and InstallTimerInt (0x486E20), plus two helpers —
FUN_00486BF0 (a QueryPerformanceCounter-based high-resolution frame-time via __alldiv) and
FUN_00486DC0 (a strlen used by the FPS string formatting). It drives the very globals in
game-loop.md § Frame Timing (_timerTicks, _currentTime, _timeCompression).
Disposition: it is its own small timing subsystem — recommend a standalone timing row in
db/subsystems.csv (≈13 functions, 2 still FUN_) rather than annexing it to campaign; tracked
alongside the game-loop discovery #257.
Status: resolved — re-static (identified as the TIME/FPS timing cluster; homing tracked in #257).
Related¶
- formats/CAM.md / formats/P.md — the campaign and pilot save formats.
- objects.md — mission objects are entities; scoring reads the mirror.
- network.md — multiplayer missions share the mission-load path.
Rendering
Renderer & Rasterizer¶
The 2D graphics device and software rasterizer — the primitive-drawing library the whole
game renders through. Two layers: GG_*, the DirectDraw surface/mode/palette/present device
(0x45DBD0–0x45E460), and G_*, the software rasterizer — points, lines, rectangles,
polygons, text, bitmap blit/scale, and the polygon/triangle span fillers (G_* clusters at
0x497330, 0x4B7900, and the span fillers at 0x4C6000+). Functions are identified by
virtual address (VA) and SMS name where available.
Scope: this is the reconstruction-program
renderersubsystem (#211) — the device + rasterizer. The generic 3D scene pipeline that drives it (GR_*: transform, project, scene dispatch, camera, culling) is the separate render-core subsystem, documented in render-core.md (#228, landed). The horizon/sky path (§1 Scene Dispatch, §10 Horizon) is kept here because it is the concrete consumer of the device — its entry pointT_DrawHorizonis filed under the terrain subsystem in the symbol database and is cross-referenced from LAY.md, which owns the atmosphere lookup-table side. §5 Camera and §6 Visibility remain provisional pending consolidation into render-core.md. The SH shape format is its own subsystem.Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; every symbol here is recorded in the symbol database and applied to the Ghidra project. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
1. Scene Dispatch¶
The per-frame scene is built inside T_DrawHorizon (0x4aacfe), the sole caller of @G_Tile@32
(0x447aa5). It is reached through the adjacent _T_DefaultHorizon horizon descriptor
(0x4aacf0); the LAY DLL's dispatch table resolves the horizon slot to this render path at load
time. The dispatch chain is:
| VA | SMS name | Role |
|---|---|---|
0x4aacf0 |
_T_DefaultHorizon |
Default horizon descriptor record (14 B, symbol DB · terrain) — the data the horizon/scene render is reached through |
0x4aacfe |
T_DrawHorizon |
Core per-frame scene builder — clips the viewport, selects colour tables, calls _SolidHorizon, _GouraudHorizon, @G_Tile@32, and @GRExec@4 |
T_DrawHorizon takes 13 parameters encoding clip rect, fog colour entries, and atmosphere flags. Its
first act is to test DAT_00583a58 and DAT_00573396 to choose between a sky-tile path and a
straight solid-horizon path.
Key globals read at scene start:
| Global | Role |
|---|---|
_clipLeft, _clipRight, _clipTop, _clipBottom |
Active viewport clip rectangle |
_currentLayer |
Bitmask of active LAY layer flags (bit 3 = sun, bit 4 = cloud layer, bit 14 = tile mode) |
_currentTimeOfDay |
Used to gate sun rendering against DAT_00583a82–DAT_00583a86 window |
_hackSky / _hackHorizonUp / _hackHorizonDown / _hackGround |
Debug overrides for sky palette — skips _currentTintTable lookup when non-zero |
DAT_00573394 |
Cloud/tile quality level (0 = no tile, 1 = reduced, 2+ = full) |
_currentTintTable |
Per-layer atmosphere colour table (the active LAYER's tint LUT, set at load — see LAY.md). T_DrawHorizon reads individual band colours as single bytes at +0xd4/+0xdf (sky-top / horizon-down, clear) and +0xed/+0xfc (overcast), +0xe5/+0xec (upper sky / horizon-up), +0xf0 (ground), and +0xf1/+0xf3/+0xf4 (Gouraud gradient endpoints), then passes them as the colour arguments to _SolidHorizon/_GouraudHorizon. _hackSky/_hackHorizon*/_hackGround override this whole lookup when non-zero |
The scene dispatch builds a GRExec command list on the stack (a 0xf8-terminated short-int
stream), then calls @GRExec@4 (0x4d6498) to execute it, which renders the sky gradient and
cloud tiles.
2. Shape System (.SH Files)¶
The .SH string appears at over 60 data addresses in the 0x4F4E00–0x50C441 range, used as
file-extension literals in load/lookup paths. The primary load infrastructure sits in two
confirmed SMS functions:
| VA | SMS name | Role |
|---|---|---|
0x47a130 |
@LibFileExists@4 |
Tests whether a named asset (with .SH extension check at 0x47a3cc) exists in any mounted LIB |
0x4ad3c0 |
_LoadFile@16 |
Generic asset loader; resolves .SH extension at 0x4ad7bd and hands off to the resource manager |
The resource manager exposes:
| SMS name | Role |
|---|---|
_RMAccess_8 |
Lock an already-loaded asset handle for read |
_RMAccessHandle_8 |
Load by name + flags, returning a locked handle |
_RMChangeType_12 |
Swap the file extension on a name buffer (used to resolve terrain-variant ~ names) |
_MMAllocHandle_8 |
Allocate a tracked memory handle |
_MMAccessW_4 |
Get a writable pointer from a handle |
_MMAccessR_4 |
Get a read-only pointer from a handle |
_MMFreeHandle_4 |
Release a handle |
Shape handles are stored in the runtime entity struct — the OBJ_TYPE record in a .PT/.OT/.NT
file names shapes via ~ asset references, which the engine resolves at mission load into resource
handles. No dedicated "shape cache" table was found in the analysed range; caching is implicit via
the resource manager's handle pool.
3. Polygon and Vertex Pipeline¶
The rasteriser is split into a fixed-integer path (G_UPolygon family) and a floating-point
near-plane-mapped path (NPM, prefixed NPM_).
3.1 Integer polygon path¶
| VA | SMS name | Role |
|---|---|---|
0x4984b0 |
@G_PolygonFlip@8 |
Submit filled polygon; integer fixed-16.16 vertices |
0x4984f0 |
@G_UPolygonFlip@8 |
Unclipped variant — fast path when all vertices are inside the viewport; falls through to _G_FloatFlatFlip_8 when _cFillType is set and the 0x10000 effects bit is active |
0x498530 |
@G_SPolygonFlip@8 |
Shaded polygon (Gouraud) flip |
0x498550 |
@G_SUPolygonFlip@8 |
Shaded + unclipped flip |
0x4c6ecc |
@G_UPolygon@8 |
Core integer rasteriser — walks vertices in fixed-16.16 screen space, computes left/right edge slopes, fills spans via _G_DrawYLR_4 |
0x4c77d0 |
@G_SUPolygon@8 |
Shaded (Gouraud colour-interpolated) variant of G_UPolygon |
0x4c7350 |
UPolygonToYLR |
Converts polygon vertices to a YLR (Y, Left, Right) scanline list |
Vertex format (5 words per vertex, 16.16 fixed point): [x_fp, y_fp, u_fp, v_fp, c_packed].
The _G_DrawYLR_4 fill loop writes _cColor (flat) or a per-pixel Gouraud-interpolated colour into
the current bitmap row pointer obtained from _cb (the current render-target bitmap handle).
Clip globals used by G_UPolygon:
| Global | Role |
|---|---|
_eclipLeft, _eclipRight, _eclipTop, _eclipBottom |
Extended clip rect for polygon clipping (wider than viewport) |
_no_overlap |
When non-zero, right edge is exclusive — avoids overdraw at tile seams |
_overflow_ptr |
Exception-handler slot — set to _divide_by_ebp_handler during raster inner loops |
The fx_render::fa span core reproduces this path — PolygonToYlr (UPolygonToYLR) feeding
flat YLR span fills with the _no_overlap exclusive-right-edge rule — with the stepping
conventions recorded as inferred: edge x evaluated per integer scanline from the 16.16 slope,
span endpoints truncated (x >> 16, inclusive right by default), and vertical coverage
half-open (⌈y_min⌉ … ⌈y_max⌉ − 1, so vertically abutting polygons never overdraw). Pinned by
tests/render/test_fa.cpp (#329). The shaded variant (G_SUPolygon) rides c_packed — a
palette/shade index, not an RGB blend — through the same edge/span stepping, evaluated at
each pixel's integer x and clamped to the palette range (both inferred); flat-vs-Gouraud
selection is the _cFillType dispatch the SH interpreter stages via sh_op_80/SetFlatColor
(#330).
3.2 Near-plane mapped (NPM) floating-point path¶
Used for perspective-correct texture-mapped polygons that may cross the near plane.
| VA | SMS name | Role |
|---|---|---|
0x4b8e10 |
?NPM_clipTop@@YIJPAUFVERTEX@@0@Z |
Clips a triangle against the top frustum plane; populates DAT_005843d8 with surviving vertex count |
0x4b8f70 |
?NPM_clipTri@@YAJPAUFVERTEX@@@Z |
Clips one triangle; initialises float vertex buffer at DAT_005843e0–DAT_005845a0; writes guard bits DAT_00584834 (AND) and DAT_00584838 (OR) for trivial-accept/reject |
0x4b90c0 |
?NPM_clipAndScan@@YIJPAUFVERTEX@@J@Z |
Clips + scans a triangle to DAT_0058b7d4/DAT_0058b7e4 (Y-top/Y-bottom) |
0x4b9430 |
?NPM_FlatTri@@YIXPAUFVERTEX@@J@Z |
Flat-shaded triangle inner loop; called by _G_FloatFlatFlip_8 |
0x4b9630 |
?NPM_TextureLinearTri@@YIXPAUT_BITMAP@@PAUFVERTEX@@J@Z |
Linearly texture-mapped triangle; called by _G_FloatTextureLinearFlip_12 (0x4ba500) |
0x4b9b90 |
?NPM_TexturePerspectiveTri@@YIXPAUT_BITMAP@@PAUFVERTEX@@J@Z |
Perspective-correct texture triangle; called by _G_FloatPerspectiveFlip_12 (0x4ba660) |
FVERTEX layout (7 floats): [x_screen, y_screen, u, v, w_reciprocal, clip_flags, pad]. The clip
flag word uses bit 2 (0x4) for the top-plane guard.
Texture coordinate interpolation sets up six _DAT_0058b7?? doubles as gradient coefficients
(du/dx, dv/dx, dw/dx, du/dy, dv/dy, dw/dy), then calls (*(code *)_gbuffer)() which is a
function pointer to the actual scanline fill kernel selected at startup.
fx_render::fa reproduces the clip stages: the outcode near-plane scheme (CodePnt,
AND-reject / OR-accept guard words, straddlers cut with attributes interpolated —
NearClipPolygon), the screen-edge Sutherland–Hodgman polygon clip in the render-core
clip_edge_{left,right,top,bottom} order (G_Polygon's clipped entry), and the
Cohen–Sutherland G_ClipLine for G_Line. The clipped and span-clamped paths are
cross-validated pixel-identical by tests/render/test_fa.cpp (#331).
3.3 Z-buffer¶
No dedicated Z-buffer write was observed in the rasteriser output — the game executable predates z-buffer
hardware and relies entirely on painter's-order submission (objects sorted back-to-front by the
scene graph before draw calls). The _lineStats array (base 0x5568a8) is a per-scanline byte
flag used to mark which scanlines are occupied by a polygon, preventing re-scan of empty rows.
fx_render::fa reproduces this property structurally: the fa surface carries no depth buffer,
and occlusion comes only from the painter's-order submission list (PaintersList, the
GRAddBrentObj → sort_objs_wrapper stage) sorting back-to-front on the centroid+size key —
pinned, including a case where a z-buffer would disagree, by tests/render/test_fa.cpp (#332).
4. Sprite and Billboard Rendering¶
The SPRITE section in the analysis output has no decompiled function bodies — Ghidra either did not recover functions in this range or they fall inside the dark zone (see section 10). The following SMS symbol is confirmed present:
| VA | SMS name | Role |
|---|---|---|
0x4440f0 |
_GRAPHICAddInvisible@20 |
Adds an entity to the invisible (non-rendering) sprite list; allocates a 0x2B-type node via FUN_00443b70 and populates a 4-byte position + 2-byte type field |
The _explode function (0x401000) manages explosion particles: it reads a decompress callback
pointer at entity+0x28, decodes up to 0x800 bytes of particle data from entity+0x2234, extracts
counts at +0x2234/+0x2235/+0x2236, and populates lookup tables at entity+0x30f4, +0x3104,
+0x3114, and +0x30b4 from ROM tables at 0x4eb0c0–0x4eb110. The particle colour/size tables
are 0x40 entries, consistent with 8-bit indexed palettes.
Additional billboard symbols seen in surrounding code:
| SMS name | Role |
|---|---|
_G_Blit_36 |
2D blit (used for HUD elements and cockpit overlays) |
_G_Circle_16 |
Filled-circle draw (lens flare, disruption ring) |
_G_AcTexture_12 |
Binds a texture handle via _G__AC_Texture assembly kernel |
5. Camera and Viewport¶
The 3D-to-2D projection is handled by:
| VA | SMS name | Role |
|---|---|---|
| — | _GRTo2d_8 |
Projects a world-space F24_POINT3 to 2D screen coords; returns negative if behind the near plane |
| — | _Move3d_16 |
Translates and rotates a world point to camera-relative coords; takes position, heading, pitch angles |
| — | _GRSinCos_12 |
Look-up sin/cos from a packed angle (360 × 0xb6 units) |
These appear in _HUDDraw_4 (0x406a50) and related HUD functions as the primary world-to-screen
pathway. The viewport centre is maintained in DAT_00521d94 / DAT_00521d96 (s16 x/y). The HUD
draw code reads _mainV (DAT_00521084) for the main viewpoint object index, and
_xscale / _yscale for screen-resolution scale factors (0 = 640×480 reference, non-zero for
higher resolutions).
Viewport clip bounds set by T_DrawHorizon (0x4aacfe):
_clipLeft,_clipRight,_clipTop,_clipBottom— integer pixel bounds of the active clip rect_clipWidth/_clipHeight— derived dimensions; compared against 200/300 thresholds to select LOD fog distance (param_8=0xFFFFFFECfor narrow viewports,0xFFFFFFC4for full-width)
The texture coordinate scaling constants at DAT_004e9528 and DAT_004e9530 convert fixed-24.8
world units to float screen-space texture coordinates inside the NPM vertex preparation loops.
6. Visibility Culling¶
| VA | SMS name | Role |
|---|---|---|
0x498a50 |
_G_Visible |
Per-entity visibility test against _visibleLineStats; result drives whether a shape is submitted for raster |
0x4b4b30 |
@WRCanSee@8 |
Fog/weather-gated visibility check — calls _WRWeatherEffects to get visibility range, then _Dist_8 for actual distance; returns bool |
_visibleLineStats (0x5568a8) is a byte array indexed by scanline. _visibleTargetIds
(0x57cc70) and _numVisibleTargets (0x580bb4) track the target entities visible on screen for
HUD target-box drawing.
@WRCanSee@8 reads entity+0x15 for each object's altitude (used to index a LAYER struct at
stride 0x160) and entity+5 for the object-type pointer (to get the .SH bounding radius at
+0x3b). Visibility is gated by weather — _WRWeatherEffects walks the LAYER stack between two
altitudes and returns the minimum visibility percentage.
The NPM triangle clipper (NPM_clipTri) performs near-plane culling by writing bit 2 of the FVERTEX
clip-flags word — if all three bits are set (DAT_00584834 != 0), the triangle is entirely behind
the near plane and discarded.
No LOD system was found in the analysed range. Terrain tile LOD is implicit in @G_Tile@32 via
the tileExpand__3JA flag (set by _tileExpand__3JA = (DAT_00573394 < 2) in T_DrawHorizon).
7. DirectDraw Surface Management¶
| VA | SMS name | Role |
|---|---|---|
0x4b7a80 |
_G_AllocSurfaceBitmap@8 |
Allocates a W×H bitmap with a DirectDraw secondary surface; calls CDirDraw::CreateSecondarySurface, locks it via CDirDrawSurface::Lock, clears to zero, builds a row-pointer table, and returns an MM handle |
0x4b7bf0 |
@G_FreeSurfaceBitmap@4 |
Releases the DirectDraw surface via CDirDrawSurface::Destroy and clears _DDsurfaceBitmap__3PAVCDirDrawSurface__A |
Key DirectDraw globals:
| Global | Role |
|---|---|
_m_singleton_CDirDraw__1PAV1_A |
Singleton CDirDraw object — checked for null before any surface allocation |
_DDsurfaceBitmap__3PAVCDirDrawSurface__A |
Active secondary surface used for 3D rendering |
_surfaceBitmap__3PAEA |
Raw pixel pointer from the locked surface |
_cb |
Current render-target bitmap MM handle — read extensively by the rasteriser (_cb + 6 = height, _cb + 0x22 = row-pointer array) |
G_AllocSurfaceBitmap stores the DirectDraw surface's locked pixel pointer (piVar3[9]) and row
stride (piVar3[4]) directly into the bitmap header, then builds a param_2-entry array of row
pointers at the handle's data area starting at offset +0x32. The the game executable bitmap struct (used as
_cb) has this layout at known offsets:
| Offset | Field |
|---|---|
+2 |
Width (pixels) |
+6 |
Height (scanlines) |
+10 |
Row stride (bytes) |
+0x22 |
Row-pointer array pointer |
The fx_render::fa surface reproduces this record's semantics — runtime width/height/stride
with row-pointer access — as the reconstruction's software render target; the layout and the
192-entry 6-bit palette presentation are pinned by tests/render/test_fa.cpp (#328).
@G_DoubleBitmapX@4 (0x4b8bf0) doubles a bitmap's width by duplicating each pixel horizontally,
used when upscaling to higher resolutions (@G_DoubleBitmapY@4 at 0x4b8960 is the height twin).
8. WR Raster Subsystem¶
The WR (Weather/Raster) subsystem owns the sky palette, atmosphere state, and fog. All WR functions
were found in the dark zone 0x4B4200–0x4BEDFF (see section 10).
| VA | SMS name | Role |
|---|---|---|
0x4b4320 |
WRFogLayerUpdate |
Per-frame fog jitter — adds Rand(51) - 25 to each LAYER's fog_density field at +0xfe, clamped to [217, 235] (0xD9–0xEB) |
0x4b4370 |
_WRInit@4 |
Full WR initialisation — calls _WRShutdown_0, loads the .LAY DLL via _RMAccess_8, copies 30 dwords to _hdr, initialises _currentShadeTable and _currentTintTable, sets _fillTypes, copies the real palette, calls _WRForcePaletteUpdate_0 and _InitTmapRemaps |
0x4b46d0 |
_WRShutdown@0 |
Frees _hdrPtr__3PAULAYER_FILE_HEADER__A via _MMFreePtr_4 and clears DAT_0050c8b8 |
0x4b46f0 |
@WRInt@4 |
Writes DAT_0050c8b8 — single-byte WR-enabled flag |
0x4b4700 |
_WRForcePaletteUpdate@0 |
Clears _lastPalette (0xC0 dwords) to force a full palette upload on the next frame |
0x4b4720 |
_WRWeatherEffects |
Queries visibility percentage for an altitude range — walks LAYER structs between two altitudes, returns the minimum +0x14e visibility byte |
0x4b4790 |
?InitTmapRemaps@@YIXXZ |
Clears the texture-remap cache (DAT_00581140, 0x843 entries) and resets DAT_00583aa0 |
0x4b47b0 |
@SetTmapRemaps@0 |
Checks the texture-remap cache for the current _currentShadeTable/_currentTintTable/DAT_005843c4/DAT_005843c8/_globalColorAdd combination; if not found, evicts LRU entry and calls _DoSetTmapRemaps_0 to regenerate a 64-entry remap table, which is then copied into _tmapRemapTable |
0x4b48c0 |
@WRMakeHazeList@12 |
Builds a haze-distance list for sky rendering — walks the active LAYER's +0x3a colour-entry list and interpolates fog density across its visibility ramp (+0x12 fog_alt_low, +0x16 vis_lo, +0x1a fog_alt_high, +0x1e vis_hi — see LAY.md § LAYER struct), emitting (distance, colour) pairs terminated by 0x7fffffff into the 0x583940 buffer |
0x4b4990 |
@WRLensFlare@0 |
Draws lens-flare halos when _gamePrefs bit 7 is set, (*DAT_00580d90 & 8) != 0, and DAT_0050c8a2 > 0xb5 (sun above horizon); uses _sunPoint and DAT_00583dbe for projected sun position; calls _G_Circle_16 for each flare disc from DAT_0050c8d8 table |
0x4b4b30 |
@WRCanSee@8 |
See section 6 |
0x4b3190 |
_WRGetLayer@8 |
Returns the LAYER struct pointer for a given altitude (right-shifts by 8, clamps to LAYER array bounds) |
0x4b3d90 |
_WRUpdatePalette@0 / _WRUpdatePalette__YSKYSx |
Per-frame palette animation — steps _palSunWhiten, _palCockpitWhiten, _palBlacken, _gForceBlacken, and _palColor toward their destination values, then applies to DAT_00583b20/DAT_00583aa8 ranges via _WRBlackenPalette_12, _WRWhitenPalette_12 |
0x4c8e20 |
_WRBlackenPalette@12 |
Scales N×3-byte RGB entries by (256 - param_3) / 256 toward black |
0x4c8e6c |
_WRWhitenPalette@12 |
Scales N×3-byte RGB entries toward 0x3F (VGA maximum) |
0x4c8ec8 |
_WRReddenPalette@12 |
Shifts R channel toward 0x3F while darkening G and B |
_WRInit@4 additionally sets up the _fillTypes dispatch table (14 entries at 0x60e99–0x60ea?),
mapping fill-type indices to scanline fill kernel addresses, and loads cloud/sky PIC wildcards via
FUN_004b4680 (a strchr(name, '*') + _Rand_4 + _Sprintf pattern for wildcard PIC selection).
9. PIC Texture Loading¶
PIC textures are loaded into DirectDraw surfaces via the DirectDraw path in G_AllocSurfaceBitmap
(section 7). The binding to shapes uses the texture remap system:
| VA | SMS name | Role |
|---|---|---|
0x4b87f0 |
@G_AcTexture@12 |
Calls _MMAccessW_4 to get a writable pointer to the texture handle, then calls _G__AC_Texture() — an assembly kernel that writes the texture pointer into the global raster state |
0x4b7c30 |
?RemapAdd@@YAXPAUT_HANDLE@@H@Z |
Adds a colour offset to all bytes in a PIC's pixel buffer (palette shift) |
0x4b7c60 |
?RemapRelocate@@YAXPAUT_HANDLE@@F@Z |
Applies a palette base-address relocation to a PIC handle |
0x4b47b0 |
@SetTmapRemaps@0 |
See section 8 — builds the 64-entry _tmapRemapTable used by NPM texture kernels |
The texture-remap cache at DAT_00581140 has 8 entries at stride 0x11A each. Each entry holds
a generation counter, the effects mask (_effects & 0x14), five table pointers
(_currentShadeTable, DAT_005843c8, DAT_005843c4, _currentTintTable, _globalColorAdd),
and a 64-dword copy of _tmapRemapTable. On a cache miss, the LRU entry (lowest generation) is
evicted and _DoSetTmapRemaps_0 regenerates the table.
For double-resolution modes, @G_DoubleBitmapX@4 (0x4b8bf0) doubles a PIC's width by pixel
duplication, creating a 2× stretched copy for the higher-resolution renderer path.
fx_render::fa reproduces the textured fills over indexed texels: the affine u/v span
stepping on the five-word vertex (G__Texture), the NPM linear and perspective triangle
kernels (u·w′/v·w′/w′ interpolation with a per-pixel carefulDiv-guarded divide), and the
256-entry _tmapRemapTable shade/tint remap (SetTmapRemaps) applied per sampled texel —
pinned, including a linear-vs-perspective divergence golden, by tests/render/test_fa.cpp
(#333).
10. Horizon / Sky Integration¶
This section traces the sky/horizon pipeline end-to-end: from the LAY atmosphere lookup
tables through colour selection to the raster fills that put pixels on the surface. The
lookup-table side — how a .LAY DLL is loaded, how the active LAYER's tint/shade tables and
colour-entry list are resolved, and the fog/brightness/angle mechanics that populate them — is
documented in LAY.md (§ Engine Notes); this
section is that data's consumer.
T_DrawHorizon (0x4aacfe) is the per-frame scene builder; it is reached through the
_T_DefaultHorizon descriptor (0x4aacf0) whose slot the LAY DLL dispatch table resolves at
load time. Its sequence:
_T_Info_24— query atmosphere parameters into a local buffer._WRMakeHazeList_12(0x4b48c0) — build the fog-density list into a stack buffer at0x583940from the active LAYER's visibility ramp (§8).- colour selection — unless a
_hack*override is set, read the sky/horizon/ground band colours as single bytes from_currentTintTable(offsets listed in §1) — the LAY tint LUT. _SolidHorizon— draw a solid-colour sky band (clear sky or overcast).@G_Tile@32(0x447aa5) — ifDAT_00583a42is non-zero (cloud tiles enabled), draw cloud tile layer from the tile bitmap atDAT_00583a50×DAT_00583a54._GouraudHorizon— draw the horizon gradient band.@GRExec@4(0x4d6498) — execute the GR command list (sky dome elements, sun disc).- Second
_SolidHorizon+ optional@G_Tile@32— draw the ground colour band. - Second
_GouraudHorizon— draw the lower-horizon gradient.
The sun element is appended to the GRExec command list only when _currentLayer & 8, the current
time of day is inside [DAT_00583a82, DAT_00583a86], and DAT_0050c8a2 > -0x71d. The sun entry
is a 4-short record: [0xF8, _sunAngle, DAT_0050c8a2, 0] followed by one dword DAT_0057cd08
(sun colour/brightness). The GRExec list is null-terminated by a 0 short.
The _landFilename global selects the terrain tile bitmap used for the distant ground plane when
_currentLayer & 0x10 is clear. DAT_00583a58 and DAT_00583a66 / DAT_00583a6a control the
terrain tile distance fade thresholds.
Solid horizon band — _SolidHorizon (0x4c924c)¶
The solid path draws a flat sky (or ground) band clipped to the tilted horizon line. It stores the
selected sky/ground colour bytes into _sky_color_data / _ground_color_data, then computes the
four horizon-line endpoints (DAT_0050fd96–0x9c) from the camera up-vector components
(top_up, right_up, forward_up) plus __amtMoveHorizon — the vertical horizon offset staged
by T_DrawHorizon — so the band tilts and slides with pitch/roll. A 4-iteration sign-bit loop
tests those endpoints against the viewport (wleft_data/wright_data/wtop_data/wbot_data) to
decide visibility. When the band is on-screen it clamps the four viewport edges
(FUN_004c93c3/FUN_004c93f6), orders the span endpoints into hhigh/hxlow/hxhigh/hlow,
and calls Horizon2d() — the scanline fill that writes the colour band into the raster
surface; otherwise it calls NoHorizon(). This is the terminal "through raster" step of the
solid path.
Gouraud horizon gradient — _GouraudHorizon (0x4c942c)¶
The gradient path renders the sky/ground colour ramp as shaded polygons through the shared SH
interpreter. It saves and zeroes the viewer position (__viewer_x/y/z, so the gradient is drawn
in view space) and the _effects/_effectsAllowed flags, then stages a fixed set of gradient
quads into the 0x50fda0–0x50fe40 command region: the up/horizon/ground colour bytes (params
from the tint-table lookup) fill the per-vertex colour slots, and the screen-space deltas are
derived from the camera heading vector (_headv_x, _headv_z, right-shifted) so the bands tilt
with the horizon. It then dispatches the staged polygons through vector_table
((*vector_table[DAT_0050fda0*2])() and [DAT_0050fdfe*2]) — i.e. the gradient sky is
rasterized by the same Gouraud draw-opcodes as any SH shape (see
SH.md § Interpreter dispatch and
render-core.md). Finally it restores the effects flags and
viewer position. The polygons land in the same triangle/span fillers documented in §3 and §8, so
both horizon paths converge on the rasterizer's fill kernels.
11. Dark Zone: 0x4B4200–0x4BEDFF¶
This range was explicitly annotated as the "shape manager range" in the Ghidra script but also contains the entirety of the WR subsystem and several airport/carrier management functions. Functions found within the zone:
| VA | SMS name | Notes |
|---|---|---|
0x4b4320 |
WRFogLayerUpdate |
Fog density jitter — see section 8 |
0x4b4370 |
_WRInit@4 |
WR/LAY initialiser — see section 8 |
0x4b4680 |
— | Wildcard PIC selector (strchr *, rand, sprintf) — called from _WRInit |
0x4b46d0 |
_WRShutdown@0 |
WR teardown |
0x4b46f0 |
@WRInt@4 |
WR enable flag setter |
0x4b4700 |
_WRForcePaletteUpdate@0 |
Force palette upload |
0x4b4720 |
_WRWeatherEffects |
Weather/visibility query |
0x4b4790 |
?InitTmapRemaps@@YIXXZ |
Texture remap cache init |
0x4b47b0 |
@SetTmapRemaps@0 |
Texture remap cache lookup/update |
0x4b48c0 |
@WRMakeHazeList@12 |
Haze-distance list builder |
0x4b4990 |
@WRLensFlare@0 |
Lens flare renderer |
0x4b4b30 |
@WRCanSee@8 |
Fog-gated visibility test |
0x4b4bb0 |
— | JPEG decoder init (allocates JPEGMEM-sized pool; sets up 11-slot vtable) |
0x4b4cf0 |
— | JPEG allocator (FUN_004b4cf0) — bump-allocates from a two-segment pool |
0x4b4e30 |
— | JPEG error handler (raises error code via vtable dispatch) |
0x4b4e60 |
— | JPEG high-watermark allocator |
0x4b4f10 |
— | JPEG row-pointer array allocator |
0x4b4fd0 |
— | JPEG DCT row-buffer allocator |
0x4b5460 |
— | JPEG row-decoder trampoline (calls per-row kernel from param_2[10]) |
0x4b5660 |
— | JPEG row-decoder with 0x80-stride variant |
0x4b5700 |
— | JPEG memory free (two-pass: free callback list then pool) |
0x4b5960 |
— | JPEG marker parser — scans for 0xFF start codes, dispatches to segment handlers |
0x4b5a90 |
— | JPEG image-object init (sets up FUN_004b7700 + 4 other vtable slots) |
0x4b5f90 |
— | JPEG SOF parse stub — reads 2-byte segment length |
0x4b6410 |
— | JPEG SOF0 handler — reads image dimensions, validates, allocates component table |
0x4b64c0 |
— | JPEG SOF0 component descriptor reader — reads H/V sampling factors and quantisation table IDs |
0x4b6840 |
— | JPEG SOS handler — reads scan header, validates component count (1–4), populates Huffman selector table |
0x4b6c20 |
— | JPEG DHT segment skip handler |
0x4b6df0 |
— | JPEG DQT (quantisation table) reader — reads up to 256-byte tables |
0x4b7700 |
— | JPEG decoder state reset |
0x4b7890 |
— | JPEG restart handler |
0x4b78c0 |
— | JPEG decoder shutdown |
0x4b7a80 |
_G_AllocSurfaceBitmap@8 |
DirectDraw secondary surface allocator — see section 7 |
0x4b7bf0 |
@G_FreeSurfaceBitmap@4 |
DirectDraw surface release — see section 7 |
0x4b7c30 |
?RemapAdd@@YAXPAUT_HANDLE@@H@Z |
PIC palette shift |
0x4b7c60 |
?RemapRelocate@@YAXPAUT_HANDLE@@F@Z |
PIC palette relocation |
0x4b8bf0 |
@G_DoubleBitmapX@4 |
Double-width bitmap duplication (height twin @G_DoubleBitmapY@4 at 0x4b8960) |
0x4b79b0 |
_G_AllocBitmap@12 |
Software-only fallback bitmap allocation |
0x4b80?? |
Various G_Color*, G_Scale*, G_Texture*, G_Print* wrappers |
2D graphics utility functions |
0x4b8d90 |
?carefulDiv@@YANPAMMM@Z |
Float divide with NaN/zero guard for texture gradient setup |
0x4b8e10 |
?NPM_clipTop@@YIJPAUFVERTEX@@0@Z |
Near-plane top-clip — see section 3.2 |
0x4b8f70 |
?NPM_clipTri@@YAJPAUFVERTEX@@@Z |
Triangle clipper — see section 3.2 |
0x4b90c0 |
?NPM_clipAndScan@@YIJPAUFVERTEX@@J@Z |
Clip + scan — see section 3.2 |
0x4b90d6 |
— | NPM flat-triangle rasteriser inner loop |
0x4b9430 |
?NPM_FlatTri@@YIXPAUFVERTEX@@J@Z |
Flat NPM triangle — see section 3.2 |
0x4b9630 |
?NPM_TextureLinearTri@@... |
Linear texture NPM triangle — see section 3.2 |
0x4b9b90 |
?NPM_TexturePerspectiveTri@@... |
Perspective texture NPM triangle — see section 3.2 |
0x4ba400 |
@G_FloatFlatFlip@8 |
Float flat polygon flip (vertex conversion + NPM dispatch) |
0x4ba500 |
@G_FloatTextureLinearFlip@12 |
Float linear-texture polygon flip |
0x4ba660 |
@G_FloatPerspectiveFlip@12 |
Float perspective polygon flip |
0x4ba770 |
@APInit@0 |
Airport manager init — zeroes DAT_0058e870 (0x21C dwords) |
0x4ba800 |
@APAdd@4 |
Airport/carrier registration (up to 40 airports, stride 0x134) |
0x4ba870 |
@APDelete@4 |
Remove airport by short ID |
0x4baac0 |
@APLandingType@8 |
Determine valid landing approach type for an airport |
0x4baa10 |
@APTakeoffType@8 |
Determine valid takeoff type (catapult, STOL, VTOL) |
0x4bad?0 |
@APNearest@20 |
Find nearest compatible airport to a world position |
0x4baa?0 |
@APLandingType@8 |
Returns landing capability flags |
0x4bab20 |
— | Carrier-deck position check helper |
0x4bb?00 |
Various AP* functions |
Carrier/airport on-board tracking |
0x4bbd?0 |
Various AP* / plane management |
Plane wing/slot assignment |
0x4bbfe0 |
— | Autopilot reset (gear up, flaps neutral, 100% throttle, enter state 0x1f) |
0x4bd950 |
— | Airport taxiway/pad rotated-offset computation (_RotatedOffset_20 × 9 pad slots) |
0x4be6a0 |
_APApproachPath@20 |
Compute ILS approach path vectors |
0x4beb60 |
@APRemoveFromCarrier@0 |
Remove current object from its carrier slot |
0x4bed70 |
_APHomeAirport@0 |
Set player home airport from campaign state or nearest default |
The JPEG decoder cluster (0x4b4bb0–0x4b7700) is a stripped-down libjpeg port used to
decode .PIC files that are JPEG-compressed (as opposed to the raw 8-bit palette format). It reads
the JPEGMEM environment variable to override its memory pool size (default from DAT_004e94d0).
Key Global Reference¶
| Global | Role |
|---|---|
_cb |
Current render-target bitmap (MM handle) — updated by the flip path |
_effects |
Render effects bitmask — bit 0x4 = shadow, bit 0x10 = float fill, bit 0x10000 = NPM |
_cFillType |
Current fill type (0 = flat, 1 = shaded, 2+ = textured) |
_cColor |
Current flat fill colour (palette index) |
_currentShadeTable |
Pointer to the active shade (lighting) lookup table |
_currentTintTable |
Pointer to the active atmosphere tint table |
_globalColorAdd |
Global colour addition bias applied to all shading |
_fillTypes |
14-entry dispatch table mapping fill-type codes to scanline fill kernels |
_gbuffer |
Function pointer to the active texture fill kernel (set by SetTmapRemaps) |
_tmapRemapTable |
64-entry table mapping texture palette indices through the current shade/tint |
_sunAngle |
Packed sun azimuth (in 0xb6 units per degree) |
DAT_0050c8a2 |
Sun elevation above the horizon (negative = below) |
DAT_0057cd08 |
Sun disc colour/brightness for GRExec |
_realPalette / _curPalette |
0xC0-entry (192) VGA 6-bit RGB palette (base + sky range) |
_lastPalette |
Copy of the last-uploaded palette; zeroed by _WRForcePaletteUpdate to trigger re-upload |
The raster-state subset of these globals — the clip box, _cColor, _cFillType, and the
192-entry palette — is reproduced by the fx_render::fa state block and pinned by
tests/render/test_fa.cpp (#328).
Pipeline¶
The renderer is a strict stack: the 3D core (render-core, GR_*) transforms geometry into
2D calls; the G_* rasterizer turns those into spans; the GG_* device presents the
finished frame to DirectDraw. This subsystem owns the lower two layers.
Functions¶
Representative subset of the device + rasterizer; the full record is in
db/symbols/renderer.csv.
| VA | Symbol | Role |
|---|---|---|
0x45DBD0 |
GG_InitMode |
create the DirectDraw surfaces and enter the graphics mode |
0x45DE70 |
GG_SetPalette |
upload the 192-entry palette to the primary surface |
0x45E120 |
GG_Flush |
present the back buffer (dispatches to the two paths below) |
0x45DEDF |
GG_FlushShaken |
present with the current screen-shake offset |
0x45E13F |
GG_FlushDirtyLines |
present only the dirty scanlines tracked in _lineStats |
0x45E3A0 |
GG_RestoreSurfaces |
rebuild surfaces after a DirectDraw device loss |
0x45CDA0 |
DrawAcrossBank |
span helper that draws across a VGA bank boundary |
0x497340 |
G_Init |
initialise the rasterizer (clip box, colour, font, line stats) |
0x4974F0 |
G_SetClipBox |
set the active clip rectangle |
0x4976D0 |
G_Point |
plot a single clipped pixel |
0x498160 |
G_Line |
draw a clipped line |
0x497BF0 |
G_Rect |
draw a clipped filled rectangle |
0x497DE0 |
G_ClipLine |
Cohen–Sutherland line clip against the clip box |
0x4986B0 |
G_Print |
draw a text string in the current font |
0x4B79B0 |
G_AllocBitmap |
allocate a rasterizer bitmap |
0x4B7CD0 |
G_LoadBitmap |
load a .PIC/brush into a bitmap |
0x4B7E10 |
G_RemapBitmapToPalette |
nearest-colour remap of a bitmap to _curPalette |
0x4B7FE0 |
G_Blit |
blit a bitmap to a surface |
0x4B8670 |
G_Scale |
scaled bitmap blit |
0x4B87F0 |
G_AcTexture |
affine-textured span setup |
0x4B9B90 |
NPM_TexturePerspectiveTri |
perspective-correct textured triangle rasterizer |
0x4C6ECC |
G_UPolygon |
unclipped convex-polygon span fill |
0x4C8A38 |
G_Polygon |
clipped convex-polygon span fill |
0x4CAE38 |
G__Texture |
affine texture-mapped scanline span filler |
0x4CBD0B |
G__Perspective |
perspective texture-mapped scanline span filler |
Open Questions¶
1. GG_Flush path selection¶
A disassembly + caller sweep corrects the premise. GG_Flush (0x45E120) is a single
SEH-wrapped flush routine — called by G_Flush (0x498420) and PlaySeq — that itself
chooses full-frame redraw (when _forceRedraw is set, or at 320×200) versus the
_lineStats dirty-line diff-blit default; its SEH handler is at 0x45E356. So the real
predicate is _forceRedraw/resolution inside GG_Flush, not a dispatch between two callees.
GG_FlushDirtyLines (0x45E13F) is a Ghidra mid-function split of GG_Flush's own body
(it falls inside the 0x45E120–0x45E356 extent), not a separate function; GG_FlushShaken
(0x45DEDF) is a distinct 518-byte variant with no direct callers in the image (reached, if
at all, only via a shake path outside the analyzed call graph). Both facts — the
GG_FlushDirtyLines fall-through split and the uncalled GG_FlushShaken — are now recorded in
the symbol-DB notes
(#262).
Status: resolved — re-static (single SEH flush; _forceRedraw/resolution predicate; DirtyLines is a Ghidra split).
Related¶
- reconstruction.md — the program this subsystem belongs to, and the forthcoming render-core (#228) 3D pipeline that drives this rasterizer.
- objects.md — the object system whose draw-enqueue passes feed geometry in.
- shape-selection.md / SH.md — the shape format the 3D core interprets into the polygon calls this rasterizer fills.
- formats/PIC.md — the bitmap format
G_LoadBitmapconsumes.
3D Render Core & SH Interpreter (GR)¶
The 3D scene pipeline that turns transformed geometry into the 2D rasterizer calls — and the
hand-written threaded-code SH interpreter that executes .SH shape bytecode into it.
0x4CD588–0x4D6C00. This is the layer above the 2D rasterizer: render-core
issues the G_* calls, renderer fills them.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project (the ~120
vector_tableSH-opcode handlers, previously created only byAnalyzeSHDispatch.java, are now captured indb/and materialised on apply). Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
The 3D pipeline¶
render_3d (0x4CDCB8) traverses the scene; setup_view_projection builds the per-frame
aspect/head-vector/frustum constants from zoom and screen size; check_flat picks the flat
vs. perspective path from the object matrix. Fixed-point math leaves (sincos, acos,
isqrt16) back the transforms; Sutherland–Hodgman clip_edge_{left,right,top,bottom} clip
polygons to the screen; _Sun computes the lighting dot product; and sort_objs_wrapper
depth-sorts (painter's order, no z-buffer). The public API (GRInit3d, GRRender, GRExec,
GRTo2d, GRSinCos, GRAddBrentObj, GRSetLightSource) is how the rest of the engine
drives it; GRAddBrentObj queues a shape into the render-sort list.
The clip and sort stages are reproduced in fx_render::fa: the screen-edge clip order and
crossing interpolation (#331) and the GRAddBrentObj → sort_objs_wrapper painter's-order
submission list on the centroid+size key (#332), both pinned by tests/render/test_fa.cpp.
The SH interpreter¶
Each queued shape is drawn by the SH bytecode interpreter — a hand-written threaded-code
dispatcher over the vector_table (0x5183A0, ~120 handlers indexed by opcode). Handlers
(do_* / sh_op_*) walk the .SH instruction stream, apply transforms
(do_xformunmask), and synthesize the polygon micro-programs that dispatch into the
G_* 2D rasterizer (do_new_poly for opcode 0xFC faces). Ten unassigned
opcodes share a single sh_op_stub no-op. This is the same interpreter documented, from the
format side, in SH.md (epic #52) — here it is named and mapped as engine
code.
Functions¶
Full record: db/symbols/render-core.csv.
| VA | Symbol | Role |
|---|---|---|
0x4CDCB8 |
render_3d |
scene traversal / per-frame 3D entry |
0x4CDEB4 |
setup_view_projection |
per-frame view/projection/frustum setup |
0x4CE4B4 |
check_flat |
pick the flat vs. perspective render path |
0x4CD588 |
sincos |
table-lookup sine/cosine (transform core) |
0x4CD8B0 |
_Sun |
light dot-product against the world light source |
0x4CD8F0 |
clip_edge_right |
Sutherland–Hodgman screen-edge clip |
0x4CE968 |
sort_objs_wrapper |
painter's-order depth sort |
0x4D3194 |
do_xformunmask |
SH opcode 0xC4: render a sub-stream at a relative transform |
0x4D0C8A |
sh_op_6A |
SH opcode 0x6A handler (render-state/geometry) |
0x4D17E0 |
sh_op_stub |
shared no-op for 10 unassigned SH opcodes |
Open Questions¶
1. Remaining sh_op_* handler semantics¶
The vector_table handlers are named and materialised; the larger handlers' exact
geometry/state effects are the remaining substrate for finishing SH.md's
Unk* opcodes.
Characterized. sh_op_80 (0x4D1FC0) is a shading/colour-setup opcode: it early-outs
on codes_and, and when gouraudOn == 0 calls SetFlatColor(a, b) — i.e. it selects flat vs.
Gouraud shading and stages the primitive colour. sh_op_78 (0x4D3938, the 2085-byte largest
handler) is a bounding-box visibility cull: it reads a center point plus an extent vector,
transforms the extent by the view matrix, and forms the box's 8 corners (center ± (±dx,
±dy, ±dz)); it projects each via code_pnt (a Cohen–Sutherland clip outcode) and range-tests
them, trivially rejecting the guarded geometry when the box falls outside a frustum edge. It
emits no geometry — a cull/LOD gate, not a mesh op. (This matched OpenFA, which likewise treats
0x78 as an opaque fixed-size instruction, and explains why a static codec can skip it safely.)
Also characterized — the fragment-call structure. do_short_eof (0x4D17F4, opcode
0x1E) is a plain ret — the fragment return, not a NOP pad; do_unmask (0x4D2278,
0x12) calls its target sub-stream via the dispatch call-form and resumes after the opcode
when the callee's ShortEOF returns; and sh_op_6C / sh_op_06 / sh_op_0C / sh_op_0E /
sh_op_10 are draw-order selectors: each always renders both of its sub-chains (call one,
tail-continue the other) with an object-field or face-plane dot-product sign only swapping the
order — painter's-algorithm sorting in bytecode. Layouts and the static-walk consequences are in
SH.md → Fragment calls and draw-order selectors.
Status: open — re-static (#262; sh_op_78,
sh_op_80, the ShortEOF/Unmask call structure, and the 0x6C/0x06-family selectors
characterized; the remaining larger handlers' fine state effects continue).
Related¶
- renderer.md — the
G_*2D rasterizer this core drives. - formats/SH.md — the
.SHshape-bytecode format this interpreter runs. - shape-selection.md — whole-model selection feeding
GRAddBrentObj. - terrain.md — terrain geometry enters the same pipeline.
Terrain (T_)¶
The terrain engine — how a .T2 heightfield becomes drawable geometry each frame: a
view-adaptive quadtree cell tessellation with distance LOD, plus the decoration-scatter
system that places trees/objects across the ground. 0x4A7310–0x4ABBE2 (+ a small tail at
0x4C5D30–0x4C60E8).
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown. The nominal manifest range was a grab-bag; this is the true
T_*cluster (the ~40 KB middle held ~15 other subsystems).
Quadtree cells with distance LOD¶
Each frame the visible ground is built as a grid of quad cells in _cellArray:
T_BuildQuadCell samples the .T2 leaf heights at a cell's four corners (T_GetLeaf),
computes a representative altitude (T_QuadAltitude) and a flat-vs-sloped flag
(T_QuadSetFlags). T_ViewBounds rotates the view box by heading to bound the working set;
T_SubdivideCells is the LOD pass — cells nearer the camera subdivide finer;
T_CompactCells drops off-screen cells; and T_EmitCells walks the survivors, looks up each
cell's texture (T_CellTmapLookup), computes normals where sloped (T_Normal), and emits
the geometry via T_LeafOp into the render pipeline.
Decoration scatter¶
Ground decoration (trees, clutter, objects) is data-driven: T_RunAmbientProcs runs up to
17 decoration procs, T_ScatterGrid tiles a 2ⁿ×2ⁿ region calling T_ScatterDecorTile per
tile, which applies per-band distance LOD, gates on the leaf id, samples altitude, and
appends to the decoration queue (T_QueueDecor). T_AddVisibleObjs folds visible world
objects into the same pass.
Functions¶
Full record: db/symbols/terrain.csv.
| VA | Symbol | Role |
|---|---|---|
0x4A9D00 |
T_BuildQuadCell |
build one quad cell (sample corners, altitude, flags) |
0x4A9E20 |
T_QuadAltitude |
representative altitude of a cell |
0x4AA070 |
T_SubdivideCells |
distance-LOD subdivision pass |
0x4AA2B0 |
T_CompactCells |
drop off-screen cells |
0x4AA4A0 |
T_EmitCells |
emit surviving cells' geometry (T_LeafOp) |
0x4A9ED0 |
T_ViewBounds |
rotate the view box by heading |
0x4A8090 |
T_ScatterGrid |
tile the decoration-scatter region |
0x4A8130 |
T_ScatterDecorTile |
place one decoration cluster per tile |
0x4A8C30 |
T_QueueDecor |
append a decoration entry to the queue |
0x4A9C20 |
T_AddVisibleObjs |
fold visible world objects into the pass |
Open Questions¶
1. .T2 sub-header bytes¶
Resolved statically (2026-07-05, #262).
The "sub-header class constants" decode as the .T2 header field map read by the
loader path, which was in the analyzed code all along: T_Load (0x4C5D70)
loads the theater file through RMAccess and relocates two file offsets into
pointers — the tile-summary array (+0x85) and the leaf array (+0x91) — and
T_GetLeaf (0x4C6040) indexes both arrays row-major using the grid fields
(+0x79 leaf step, +0x7D/+0x81 tile grid, +0x89/+0x8D leaf grid). The
payload is two flat arrays, not per-tile records; full field map and the
superseded readings in T2.md.
Status: resolved — see T2.md § Engine Notes.
Related¶
- formats/T2.md — the
.T2terrain heightfield format, and its § Terrain Texturing (per-leaftexture_variant→<theater><N>.PICtile), the model the fxs terrain viewer draws. - render-core.md — the 3D pipeline
T_EmitCellsfeeds geometry into. - collision.md — the terrain grid (
T_GetLeaf/T_Normal) collision uses. - renderer.md — the rasterizer that draws the emitted cells.
VIEW — in-flight camera & replay¶
The VIEW subsystem (0x40D7A0–0x40F6B0) manages the in-flight camera — the external / spot
views, the slew (free-look) camera, view transitions, and the flight replay recorder — for
the main view struct _mainV. It was a gap in the original 19-subsystem map: FlyingLoop (see
game-loop.md) calls into it every frame, but only four functions carried FA.SMS
names (_VIEWSlew, _VIEWImmediateVisibility, _VIEWInTransition, _VIEWChangeObj) and the
15 helpers between them were unnamed. This page names them — the discovery filed as
#257, the same class of gap as the .SEQ
player (#240) and the SPX path (#241).
Provenance: Ghidra static analysis of the game executable with FA.SMS seed symbols; the
VIEW*helper names are recovered by this program from their behaviour. Confidence markers follow spec-authoring.md: confirmed · inferred.
How it works¶
Each frame FlyingLoop calls VIEWApplyMode (0x40D7F0): when the view-mode word (_mainV[0x5A])
is set it delegates to the view builder VIEWBuild (0x40EBC0), which positions the
external / spot camera. The camera is derived from the tracked object — VIEWFromObject
(0x40D810) reads _objPtrs[_mainV[0x0E]], VIEWFitDistance (0x40E2C0) sets the stand-off
from the object radius, and the slew path (VIEWSlew + VIEWSlewIntegrate, frame-rate-scaled by
_systemFrameTicks) applies free-look. VIEWCanSeeTarget (0x40F5D0) is the padlock visibility
test (_WRCanSee), gated on a _gamePrefs bit.
The replay recorder is an in-memory record/playback pair over a single saved-view buffer —
it does not serialize to disk (#284).
VIEWReplayRecordGate (0x40E960) captures the live view into the 0x30-dword _replaySaveBuf
and interpolates the camera toward saved reference values (windowed _GRSinCos/_InterpAngle
blends) while _timerTicks is inside the capture window (_replayWindowStart/End), setting
_replayActive; VIEWReplayPlayback (0x40EBA0) copies _replaySaveBuf back into _mainV
on playback. The buffer (0x522400) is exactly one 0x30-dword (192-byte) view-state block —
it abuts _replayActive at 0x5224C0 — written and read only by this pair, both called only
from FlyingLoop; there is no growing/ring buffer, no file I/O on the path, and the executable
contains no replay/demo file strings. So the "replay" is a momentary in-RAM camera snapshot and
blend-back, not a mission recording, and there is no on-disk replay format to specify.
Functions¶
| VA | Function | Role |
|---|---|---|
0x0040D7A0 |
VIEWSlew |
slew (free-look) camera control |
0x0040D7F0 |
VIEWApplyMode |
dispatch to VIEWBuild when the view mode is set |
0x0040D810 |
VIEWFromObject |
position the camera from the tracked object |
0x0040E2C0 |
VIEWFitDistance |
camera stand-off from object radius |
0x0040E380 |
VIEWImmediateVisibility |
force immediate (no-transition) visibility |
0x0040E3A0 |
VIEWInit |
allocate / initialise a view slot |
0x0040E450 |
VIEWFree |
free the view's buffer |
0x0040E930 |
VIEWInTransition |
non-zero while the view is mid-transition |
0x0040E960 |
VIEWReplayRecordGate |
set _replayActive inside the capture window |
0x0040EBA0 |
VIEWReplayPlayback |
copy _replaySaveBuf back into the view |
0x0040EBC0 |
VIEWBuild |
build the external / spot view |
0x0040F2D0 |
VIEWSlewIntegrate |
frame-rate-scaled slew integration |
0x0040F590 |
VIEWChangeObj |
switch the view to a different tracked object |
0x0040F5D0 |
VIEWCanSeeTarget |
padlock visibility test (_WRCanSee) |
Open Questions¶
1. View-mode enumeration¶
_mainV[0x5A] selects the view mode that VIEWApplyMode dispatches on, and VIEWModeLookup
(0x40F230) scans the _viewModeTable (0x4EC420); the exact mode enumeration (cockpit / spot /
padlock / fly-by / external) is not fully pinned. A short trace of the callers that write
_mainV[0x5A] would settle it.
Status: open — re-static (#262).
Related¶
- game-loop.md —
FlyingLoop, which drives VIEW each frame. - renderer.md / render-core.md —
T_Make/T_Render, which draw_mainV. - objects.md — the tracked-object system the camera follows.
Shape Selection & Damage Models¶
How the game executable chooses which .SH model to draw for a game object — the whole-model
damage swap that replaces an aircraft with a wreck when it is destroyed, and the
per-class variant set (A10_A.SH, A10_C.SH, …) that supplies those wrecks.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recovered from
DumpAllFunctions.txt(scripts/ghidra/). Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
Two axes of shape variation¶
FA varies an object's geometry along two independent axes, handled by two different mechanisms — keeping them straight is the key to reading this system:
| Axis | Granularity | Mechanism | Where |
|---|---|---|---|
| Detail (distance) and articulation/damage within a model | intra-shape | .SH bytecode opcodes — JumpToLOD, JumpToDetail, JumpToDamage, and the x86 _PL* selectors |
SH.md → Engine Notes |
| Whole-model swap when the object is destroyed | inter-shape | separate .SH files (_A…_D) chosen at render time |
this document |
So A10.SH already contains its own LOD ladder and gear/flap articulation
(intra-shape); the sibling files A10_A.SH…A10_D.SH are destroyed-state
models that the engine substitutes when the aircraft dies. The two never overlap.
The variant set is generated, not authored¶
The .PT type record names only the primary shape and the shadow (for the A-10:
shape → a10.SH, shadow_shape → a10_s.SH). The lettered variants appear in no
file — the engine derives their names at type-load time.
_SetupOT (0x4A6EB0) — reached for every object type via
_SetupPT/_SetupNT/_SetupJT → _SetupOT — copies the base filename and
overwrites the last character of the stem with a/b/c/d, producing the
sibling names, then resolves each through the resource manager
(FUN_004a71e0 → _RMAccess) into a loaded shape pointer stored on the type
record:
a10.SH (template) ──▶ a10_a.SH → +0x17 a10_b.SH → +0x1B
a10_c.SH → +0x25 a10_d.SH → +0x29
base kept at +0x0F
How many variants an object gets depends on its obj_class (type +0x0D, the
same bitfield documented in PT.md):
obj_class |
Class | Variants generated |
|---|---|---|
& 0xC000 |
Fighter (0x8000) / Bomber (0x4000) |
full _a _b _c _d set — confirmed |
0x2000 |
Ship | per-ship <name>_a.SH proxy — confirmed |
0x400 / 0x800 / 0x1000 / 0x200 |
Tank / AAA / SAM / Vehicle | shared generic TANK_A.SH / AAA_A.SH / SAM_A.SH / VEHI_A.SH — confirmed |
The _b slot (+0x1B) is additionally gated on a type flag (+0x0E & 0xC0); the
_c/_d slots are built only for aircraft (the & 0xC000 branch). This is
why every aircraft carries four siblings while a tank references one shared shape.
Runtime selection — gated on destruction¶
The choice is made per object, per frame, in the two draw-enqueue passes. Both
always draw the base shape (+0x0F); then, only when the object is
destroyed — entity health word +0x0E == 0 (the same word ShapeSetup
(0x4AB450) turns into the global _destroyed) and the entity carries the
0x100000 flag — they add a second, whole-model wreck shape:
// _T_AddYourObjs (world / terrain pass) // _GRAPHICAddYourObjs (graphics pass)
if (entity[0x0E] == 0 && (entity[1] & 0x100000)) {
variant = (type[0x33] == 2) ? type[0x25] // variant = (type[0x33]==2) ? type[0x29]
: type[0x17]; // : type[0x1B];
GRAddBrentObj(variant, x, y, z); // GRAddBrentObj(variant, ...);
}
The per-type field +0x33 selects between two damage-model sets: {_A, _B}
when +0x33 != 2, {_C, _D} when +0x33 == 2. Within a set, one variant is
drawn in the world/terrain pass (_A or _C) and its partner in the graphics
pass (_B or _D). Distance never enters this decision — LOD is entirely
intra-shape.
+0x33 is not authored in the type file — it is written at the moment of
destruction. PLANEBreakUp (0x49D730, the flight model's break-up handler)
sets damage_set = _Rand(2) + 1 (so 1 or 2) as it raises the destroyed
flags, then chooses ground vs. air debris. A value of 2 routes the object to
the {_C, _D} set, 1 to {_A, _B} — i.e. the wreck pair is picked at random
per kill, which is why the same airframe shows either the torn-airframe or the
flat-silhouette wreck across replays.
Worked example — the A-10 family in FA_2.LIB¶
| File | Faces | Bounds (ft) | Role | Evidence |
|---|---|---|---|---|
A10.SH |
81* | X ±81 | in-flight model (articulated, x86 _PL* selectors) |
base slot +0x0F; gear/flap trampolines |
A10_A.SH |
432 | X −87..80 | destroyed set {A,B} — world pass |
slot +0x17 |
A10_B.SH |
98 | X ±13 | destroyed set {A,B} — graphics pass |
slot +0x1B |
A10_C.SH |
398 | X −45..84 (asymmetric) | destroyed set {C,D} — world pass |
slot +0x25; torn airframe |
A10_D.SH |
80 | X −54..27, Z −2..3 (flat) | destroyed set {C,D} — graphics pass |
slot +0x29; ground wreckage |
A10_S.SH |
6 | Z = 0 | shadow | .PT shadow_shape |
*A10.SH reports 81 extracted faces because the rest of its geometry sits
behind x86 selector blocks the fx codec skips — see
SH.md → X86Unknown Region. Its true face count is
comparable to A10_A.
The geometry corroborates the trace: A10_C is laterally asymmetric (one side
torn away) and A10_D is flat (a burnt ground silhouette) — destruction, not
detail reduction.
Object-type record — shape fields¶
Recovered subset of the OT/NT/PT/JT type record (the struct _SetupOT operates
on; distinct from the per-instance entity struct). Offsets are from
the type-record base.
| Offset | Size | Field | Meaning | Confidence |
|---|---|---|---|---|
+0x0D |
2 | obj_class |
class bitfield (drives variant generation) | confirmed |
+0x0E |
1 | type_flags |
& 0xC0 enables the _b slot |
inferred |
+0x0F |
4 | shape |
base shape → loaded pointer | confirmed |
+0x13 |
4 | shape_name |
filename used as the suffixing template | confirmed |
+0x17 |
4 | shape_a |
_a variant (loaded) |
confirmed |
+0x1B |
4 | shape_b |
_b variant (loaded) |
confirmed |
+0x25 |
4 | shape_c |
_c variant (loaded), aircraft only |
confirmed |
+0x29 |
4 | shape_d |
_d variant (loaded), aircraft only |
confirmed |
+0x33 |
4 | damage_set |
== 2 selects the {_C,_D} set; written _Rand(2)+1 by PLANEBreakUp |
confirmed |
Instance-side fields this system reads (see structs.md): entity
+0x05 → type record, +0x0E health word (0 = destroyed), +0x01 & 0x100000
damage-model flag.
Functions¶
| VA | Symbol | Role |
|---|---|---|
0x4A6EB0 |
_SetupOT |
generate the lettered variant names and load every shape slot |
0x4A7200 / 0x4A7220 / 0x4A7230 |
_SetupNT / _SetupPT / _SetupJT |
per-type-kind entry points → _SetupOT |
0x4A71E0 |
LoadShapeSlot |
resolve one slot's name to a loaded shape via _RMAccess |
0x4A6B10 |
ResolveTypeRecord |
return the type-record pointer via the MM handle at +0x0F (_SetupOT's first step) |
0x4A6AE0 |
_RMAccess@8 |
resource-manager load/lock by name |
0x4A7A40 |
_T_AddYourObjs@0 |
world/terrain draw-enqueue; selects _A/_C |
0x4431B0 |
_GRAPHICAddYourObjs@4 |
graphics draw-enqueue; selects _B/_D |
0x4D057C |
_GRAddBrentObj@40 |
queue a shape into the render-sort list (consumes the SH header) |
0x4AB450 |
@ShapeSetup@4 |
per-object shape-state init; derives _destroyed from the health word |
Open questions¶
1. damage_set (+0x33) derivation — resolved¶
+0x33 is written at destruction time by PLANEBreakUp (0x49D730) as
_Rand(2) + 1, so the {_A,_B} vs {_C,_D} wreck pair is chosen at random per
kill rather than fixed per aircraft (see above). No longer open.
Status: resolved — re-static.
Related¶
- objects.md — the entity/object system that owns the type record and runs the destruction path.
- SH.md — the shape format; intra-shape LOD/damage/articulation
opcodes and the x86
_PL*selectors (the other variation axis). - PT.md — the plane-type record whose
obj_classand shape fields feed this system. - structs.md — the per-instance entity struct (health word, type pointer, flags).
- renderer.md — the render-sort pipeline
GRAddBrentObjfeeds.
HUD / Cockpit¶
The head-up display — the symbology layer drawn over the 3D scene each frame: the
flight-path marker, pitch ladder, speed/altitude/heading tapes, warning annunciators,
and the combat symbology (target boxes, gun reticle, CCIP bomb pipper, sensor contacts).
One tidy compilation unit (HUD.C), 0x405E30–0x40AE50.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; every symbol here is recorded in the symbol database and applied to the Ghidra project; progress is tracked in the reconstruction matrix. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
Layout is computed once, symbology is drawn from one anchor¶
HUDInit (0x406040) computes a large layout/state block (base 0x521360, the _hud
region) once from the screen geometry: per-element (dX, dY) screen offsets, text line
height, colour, the warning-text pointers, and blink phases. Everything the per-frame
drawers emit is positioned relative to the flight-path marker _hudFpmX/_hudFpmY
(0x521D94/0x521D96) plus a layout offset — so the whole HUD moves with the velocity
vector. Three fonts (HUDSetFont/HUDSetSymFont/HUDSetWinFont) and a const symbology
region (0x4EBD00–0x4EC200: warning strings, glyph bytes, numeric formats) supply the
marks. The pitch-ladder is data-driven from _hudPitchBarTable (0x4EBD30, 37×3
int16), and a target/padlock silhouette is rendered into an offscreen _hudBitmap
(0x5213D4) via the 3D pipeline.
Per-frame draw flow¶
The flight loop calls HUDDraw (0x406A50) each frame. Its body resolves the player
object, computes the flight-path marker into _hudFpmX/Y, sets colour/font, then invokes
the element drawers in a fixed order — data block, tapes, pitch ladder, then the combat
symbology, and finally the scrolling message lines:
Setters called from the flight-model/damage code between frames stage state the drawers
read: HUDSetWarning (the blinking STALL / LOW FUEL string), HUDSetStability,
HUDSetDisrupt (damage flicker), and HUDBrightness.
Functions¶
Representative subset; the full record is in
db/symbols/hud.csv.
| VA | Symbol | Role |
|---|---|---|
0x406040 |
HUDInit |
compute the layout/state block from screen geometry |
0x406A50 |
HUDDraw |
per-frame master dispatcher (called twice per frame) |
0x405F50 |
HUDMessage |
queue a scrolling HUD text message |
0x4077B0 |
HUDSetWarning |
stage the blinking warning string |
0x407B60 |
HUDDrawHeading |
heading tape |
0x407EE0 |
HUDDrawSpeed |
airspeed tape |
0x408420 |
HUDDrawAlt |
altitude tape |
0x408E20 |
HUDDrawHVel |
horizontal-velocity / drift indicator |
0x4089A0 |
HUDDrawPitchLadder |
climb/dive ladder, rotated by roll about the waterline |
0x4078B0 |
HUDDrawWarning |
blinking warning-string draw |
0x407930 |
HUDDrawConfigFlags |
gear/flap/brake/hook annunciators |
0x407A00 |
HUDDrawGLoadThrottle |
G-load + throttle + thrust-vector readout |
0x409F30 |
HUDDrawTargetBox |
target-designator box over the padlock target |
0x409910 |
HUDDrawGunReticle |
gun aiming circle + range tape |
0x409760 |
HUDDrawBombFall |
CCIP bomb fall line / pipper |
0x409BF0 |
HUDDrawApproach |
ILS / carrier glideslope box (landing mode) |
0x408C80 |
HUDDrawLeadCaret |
lag/lead aim caret at the tracked target |
0x40A7F0 |
HUDDrawContacts |
radar/IR sensor contacts |
0x40A6C0 |
HUDDrawTargetLabels |
name tags over visible targets |
0x4075D0 |
HUDDrawTargetView |
3D target silhouette rendered into the HUD bitmap |
0x40AD40 |
ComputeBombPosition |
ballistic impact point for the CCIP pipper |
0x40A530 |
HUDFindNearest |
pick the nearest target for auto-designation |
Open Questions¶
1. HUDDrawTargetView (0x4075D0) content — resolved¶
It renders a live 3D view, not a static silhouette. The body positions the view struct
_hudShape (0x5213D8) with FUN_0040D810 — the spot/track-view helper that aims a camera at
the tracked object (the same helper the in-flight view/replay cluster [#257] uses) — then
_T_Make(&_hudShape, 0) + _T_Render(&_hudShape) render the world from that viewpoint into the
HUD bitmap, _G_HFlipBitmap(_cb) horizontally flips it, and the result is blitted (element count
_hudTargetViewCount 0x521498). _hudTargetViewEnable (0x4EBD08) is a plain 1/0 gate. So
the content is a target-slaved 3D inset (padlock / combining-glass view), the horizontal
flip being the combining-glass optic — not an IR/FLIR raster or a flat silhouette.
Status: resolved — re-static (target-slaved 3D render via FUN_0040D810 + _T_Make/_T_Render, H-flipped).
2. _hudMasterMode (0x521694) enumeration — resolved¶
The submodes are encoded in _hudMasterMode, not carried on the weapon record. The
master-mode selector (in the HUD update near 0x406E00) picks the value from the currently
selected store's type record (_HARDPtrs_12 → store record; delivery flags at record +0xA6,
bits 0x80/0x10) plus the target-designation flags DAT_00521599/DAT_0052159A:
| Value | Condition | Mode |
|---|---|---|
0 |
initial each frame | none / cleared |
1 |
no deliverable A/G store selected (empty, or store type ≠ 7, or store +0x05 & 0x80) |
A/A guns / navigation default |
2 |
mode 1 and aligned with a runway (heading/pitch within band, lateral < 0x1FFE, alt < 0x1D4C01, range < 0x76AC00) |
landing |
3 |
store +0xA6 & 0x10 set and target designated (DAT_00521599 ≠ 0) |
A/G designated (computed-impact) |
4 |
store +0xA6 & 0x80 set |
A/G special-delivery |
5 |
A/G store, undesignated | A/G unguided default |
6 |
store +0xA6 & 0x10 set, DAT_00521599 = 0, DAT_0052159A ≠ 0 |
A/G designation-pending |
So it is a 0–6 enum driven by the selected store's delivery flags; the reticle name follows the mode. (Exact CCIP/CCRP labelling of 3–6 is inferred from the selecting conditions, not asserted.)
Status: resolved — re-static.
Related¶
- reconstruction.md — the program this subsystem belongs to.
- objects.md — the entity mirror (
_cg) the HUD reads aircraft/target state from. - renderer.md — the
G_*rasterizer the HUD draws its symbology through. - physics.md — the flight-model state (G-load, throttle, config) the HUD displays.
- formats/HUD.md — the
.HUDlayout file that configures cockpit HUD elements.
Audio & Video
Sound / Music¶
The audio engine: positional sound effects, threat-warning tones, ambient loops, and the
two-layer music system — all driven onto the Miles Sound System (WAIL32.DLL / AIL) through
a logical-voice mixer. 0x4328B0–0x435C60.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project (five mixer functions folded into gaps — incl. the
PollModtimer callback — are materialised on apply). Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
Logical voices onto physical channels¶
SoundOn (0x433680) is the master SFX entry — it parses the .11K/.XMI name, allocates
a MODSPEC logical voice, and sets position/pitch/pan/falloff. There are 64 logical
voices across four banks (_MOD, _pendMOD, _noMixLeft, _noMixRight) that multiplex
onto 16 physical AIL sample handles:
SoundPrioritize resolves contention by priority voice-stealing, with a waiting queue
(_pendMOD) drained by StartWaitingSounds. Two specializations: an 8-entry threat-warning
tone table (_warnSnd: IR/radar tracking + lock, both directions) and looping ambients
(_loopSnd: engine/wind/heartbeat, via MaybeLoopSound). The mixer is serviced by the
30 Hz timer callback PollMod. ServiceSounds runs per frame for per-object distance
volume/pan; ViewPan computes stereo pan from a 3D point.
Music: dynamic score + shell¶
Two layers. Dynamic score (.MUS): ScoreOn loads a .MUS bytecode script and
ScoreUpdate interprets it each frame (a byte-switch that weights and selects tracks),
dispatching to MusicOn (XMIDI via AIL_*_sequence) or DMusicOn (digital .11K). See
MUS.md. Shell music: ShellMusicUpdate picks menu / briefing /
debrief tracks. The Miles driver is reached only through the _AIL_* import thunks;
WAIL32.DLL itself is an overlay (out of scope here).
Functions¶
Full record: db/symbols/sound.csv.
| VA | Symbol | Role |
|---|---|---|
0x433180 |
InitSound |
open the DIG_DRIVER; register the PollMod 30 Hz timer |
0x433300 |
InitMixer |
init 64 MODSPEC voices + the warn-tone table |
0x433680 |
SoundOn |
master SFX entry: allocate a voice, set 3D params |
0x4349D0 |
ServiceSounds |
per-frame per-object distance volume/pan |
0x4354B0 |
PollMod |
30 Hz AIL timer mixer-service callback |
0x4357C0 |
SoundPrioritize |
priority voice-stealing across 16 channels |
0x435900 |
StartWaitingSounds |
dequeue waiting voices when channels free |
0x435980 |
StartVoice |
AIL_start_sample on a physical channel |
0x435A00 |
SetVolPitchPan |
AIL 3D volume/pan/rate on a voice |
0x4343B0 |
ViewPan |
stereo pan from a 3D point vs the camera |
0x432C30 |
ScoreOn |
load a .MUS score |
0x432CA0 |
ScoreUpdate |
per-frame MUS bytecode interpreter |
0x4329E0 |
MusicOn |
start XMIDI playback |
0x432F80 |
ShellMusicUpdate |
pick menu/brief/debrief music |
Open Questions¶
1. PollMod per-tick work — resolved¶
A fresh decompile shows PollMod (0x4354B0) is the pause/resume mixer service, not a
voice-aging/fade stepper. Each tick it reconciles the mixer against the pause state
(_timeCompression == 0x7FFF PAUSED, or MPPaused(), or DAT_004F3BC8):
- Music (when
_musicOn): on entering pause it calls MilesAIL_stop_sequenceon_musicSeqHandle(once, guarded byDAT_004F3BF0); on leaving pause,AIL_resume_sequenceand restores volume viaMusicVolume(_flightMusicVol)orMusicVolume(_otherMusicVol)depending on_curScreen. - Sound (when
_soundOn): on entering pause it silences all 16 voices — a loop over the_MODvoice table (stride0x54) callingSetVolPitchPan(&_MOD[i], i)— guarded byDAT_004F3C08; it un-silences on resume.
So the per-tick work is suspend/restore of the Miles music sequence and the 16-voice _MOD
table across pause transitions, plus screen-dependent music-volume selection.
Status: resolved — re-static (pause/resume of the Miles music sequence + 16-voice _MOD table).
Related¶
- formats/MUS.md — the
.MUSscore-bytecode formatScoreUpdateruns. - formats/11K.md — the sampled-audio format.
- objects.md —
ServiceSoundsreads per-object state through the mirror. - The Miles/AIL driver (WAIL32.DLL) — an overlay binary, tracked separately.
WAIL32.DLL — Miles Sound System (AIL) audio driver¶
WAIL32.DLL (135 KB) is the audio library the game executable's sound subsystem (#220) drives.
It is not FA-authored code: it is the Miles Sound System (the Audio Interface Library,
AIL) by Miles Design / RAD Game Tools — WAIL32 = "Windows AIL, 32-bit". Its 130 public
exports are the documented Miles AIL_* API; the ~373 internal functions are Miles' own mixer,
XMIDI player, and statically-linked C runtime.
Provenance: Ghidra static analysis of
WAIL32.DLL(imported into thefa-reproject, auto-analysed; public names from the PE export table). Third-party middleware, documented at the boundary per the license rule — theAIL_*API surface is named; Miles internals are waived, not reverse-engineered (#247 / #253). Confidence markers follow spec-authoring.md.
Boundary treatment¶
Miles Sound System is licensed third-party middleware — the same category as the Microsoft
redistributables (DDraw / dsound / msapi) documented at the boundary rather than reversed. Its
internal mixer/sequencer implementation is not FA's IP and its public API is already documented
by Miles, so this page names the exported AIL_* API (the surface the game executable links against) and
waives the internals. Every one of the 503 functions is accounted for: 130 named exports,
373 waived (Miles internals + statically-linked CRT), and the 577 referenced data globals are
waived as Miles-internal state.
The AIL API surface¶
The API splits into three groups, all reached from the game executable's sound path:
- Digital samples — PCM/
.11Kplayback:AIL_allocate_sample_handle→AIL_init_sample→AIL_start_sample;AIL_set_sample_volume/AIL_set_sample_pan;AIL_end_sample. - XMIDI sequences — music:
AIL_allocate_sequence_handle→AIL_init_sequence→AIL_start_sequence/AIL_stop_sequence;AIL_set_sequence_volume,AIL_register_sequence_callback; mastersAIL_set_digital_master_volume/AIL_set_XMIDI_master_volume. - Driver + quick API —
AIL_startup/AIL_shutdown, and the one-callAIL_quick_startup/AIL_quick_load/AIL_quick_play/AIL_quick_load_and_play.
Functions¶
Representative exports (VAs from the symbol DB; all 130 are named there):
| VA | Export | Role |
|---|---|---|
0x200019C0 |
AIL_startup |
initialise the AIL system |
0x20001CF0 |
AIL_shutdown |
tear down the AIL system |
0x20002D60 |
AIL_allocate_sample_handle |
reserve a digital-sample voice |
0x20003060 |
AIL_init_sample |
bind a sample voice to a format |
0x200033A0 |
AIL_start_sample |
begin playback |
0x200035B0 |
AIL_end_sample |
stop a sample voice |
0x20003710 |
AIL_set_sample_volume |
per-voice volume |
0x200037C0 |
AIL_set_sample_pan |
per-voice pan |
0x20004020 |
AIL_digital_master_volume |
read the digital master volume |
0x20005360 |
AIL_allocate_sequence_handle |
reserve an XMIDI sequence |
0x20005500 |
AIL_init_sequence |
load an XMIDI sequence |
0x20005630 |
AIL_start_sequence |
begin music playback |
0x200056C0 |
AIL_stop_sequence |
halt music |
0x20005910 |
AIL_set_sequence_volume |
music volume |
0x200067A0 |
AIL_register_sequence_callback |
per-sequence event callback |
0x20005E90 |
AIL_set_XMIDI_master_volume |
XMIDI master volume |
0x20008570 |
AIL_quick_startup |
one-call init |
0x200086B0 |
AIL_quick_shutdown |
one-call teardown |
0x20008730 |
AIL_quick_load |
one-call sample load |
0x200088D0 |
AIL_quick_play |
one-call sample play |
0x20008B90 |
AIL_quick_load_and_play |
one-call load + play |
the game executable ↔ AIL boundary¶
The game executable's sound subsystem (#220) links WAIL32.DLL and calls into this API — confirmed call
sites include AIL_startup / AIL_shutdown, AIL_set_preference, AIL_lock / AIL_unlock,
AIL_start_timer / AIL_stop_timer, AIL_end_sample, AIL_sequence_status, and
AIL_release_sequence_handle. The pause/resume path (PollMod, see sound.md)
suspends and resumes the active XMIDI sequence through AIL_stop_sequence /
AIL_resume_sequence. On the far side, AIL drives WINMM (midiOut* / waveOut*) and
DirectSound.
Open Questions¶
1. Internal mixer / XMIDI implementation¶
The 373 waived internals are Miles' private mixer, XMIDI interpreter, and CRT — deliberately not reversed (third-party IP; the public API above is the documented boundary). No FA understanding depends on them.
Status: resolved — boundary-documented (third-party; internals out of scope by license).
Related¶
- sound.md — the game executable's sound subsystem, the client of this API.
- reconstruction.md — the program this binary belongs to.
- formats/11K.md / formats/XMI.md — the sample and music formats AIL plays.
Video Decode (Cobra: .CB8 + .VDO)¶
The Cobra full-motion-video framework — EA's in-house movie player, not licensed
middleware. A per-frame vector-quantization scheme: 2×2 codebooks rendered to 8/15/16/24
bpp with optional 2× pixel doubling. 0x456300–0x45CDA0.
Attribution corrected (#95): Cobra is first and foremost the CB8
player — InitCobra (0x46ae10) validates the DRBC container (rejecting the older
ARBC/BRBC/CRBC generations), streams it through the engine's own LIB layer, and the
8-bit paletted path (DecodeFrame submode 5 → DecodeSVGA8Frame/DecodeDSVGA8Frame,
ExpandDB/EDB, CopySB8/CopyDB8) is exactly the CB8 keyframe codec, with a 768-byte
palette embedded per frame. The 15/16/24-bit submode-6 paths are the hi-color
generalization. Note the shipped .VDO corpus does not run through this
DecodeFrame dispatcher — those 320×200 8bpp movies decode via a separate,
much smaller cluster (GetVDOFrame → UnRLE → DecompressVideo, 0x4C8Axx),
documented in VDO.md (#138). DecodeFrame here is the CB8
player. The compiled-in engine structs, the LIB-layer I/O, and the private generation
lineage mark the whole framework as homegrown (confirmed); the CB8 side is validated by
fx_lib's pixel-exact codec (tests/test_cb8.cpp) and the on-disk layout is specified in
CB8.md.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown. Coordinates with the VDO format spec (epic #55).
Vector-quantized YUV, four output depths¶
Each frame carries a codebook of 256 entries, each 4 luma + 2 chroma decoded to four
RGB pixels (a 2×2 block). DecodeDBook builds the book at 15/16-bit (5:5:5 or 5:6:5
chosen per frame), Decode24Book at 24-bit; the YUV→RGB conversion uses the per-movie
scale/offset constants and saturates each channel through ClampU8 (the one recovered leaf
in the range — the color clamp called from every book decoder). The 8-bit path
(DecodeSVGA8Frame / DecodeDSVGA8Frame) renders paletted output with an interpolated 2×2
dither built by EDB (expand-book), optionally 2× doubled.
Frames are key (intra) or delta (inter) — the dispatch keys on the frame header,
and the inter decoders are gated on the keyframe latch. .VDO audio is a separate .11K
track, not interleaved in this cluster.
Functions¶
Full record: db/symbols/video.csv.
| VA | Symbol | Role |
|---|---|---|
0x457230 |
DecodeDBook |
decode the 15/16-bit YUV codebook |
0x456300 |
DecodeDSVGA8Frame |
key frame → 8bpp paletted, 2× doubled |
0x456EC0 |
DecodeSVGA8Frame |
key frame → 8bpp paletted |
0x456AD0 |
EDB |
expand-book: interpolated 2×2 dither pattern |
0x4575E0 |
ClampU8 |
saturate a channel to [0,255] (YUV→RGB leaf) |
Open Questions¶
1. VDO.md corrections — resolved¶
The three VDO.md inaccuracies are now reconciled (#259):
the DecodeFrame dispatch keys are documented on the FrameHeader (+8 kind, +9 submode),
not the movie context; the frame-kind polarity is corrected (0 = key/intra, 1 = inter/delta);
and the 0xC14E canvas pointer is attributed to GlobalData. The Cobra per-frame codec itself
(the ~45 leaf decoders) remains the long pole, tracked under epic #55.
Status: resolved — re-static (#259; the codec long-pole stays under #55).
Related¶
- formats/VDO.md — the
.VDOcontainer/codec spec. - formats/CB8.md — the related 8-bit codebook image format.
- renderer.md —
DrawAcrossBank, the VGA-bank span helper the 8bpp path uses.
Comms & Multiplayer
FA Multiplayer Networking Internals¶
The game executable's multiplayer networking internals. Companion reference: formats/DAT.md.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; source output
AnalyzeNetwork.txt. Every symbol is recorded in the symbol database and applied to the Ghidra project (this is the reconstruction-programnetworksubsystem,219 — it also owns the serial-cable and modem link transport,
SER_/MOD_, per the¶program's ownership split with input). Progress: reconstruction matrix. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
CN_INFO Struct — EA.CFG / NET.DAT Layout¶
CN_INFO is the central configuration struct passed by pointer to nearly every network and
config function. It is 0xDDC bytes on disk (written verbatim by CN_WriteConfig). The version
field at offset 0x00 distinguishes EA.CFG revisions 1, 2, and 3.
The table below is derived from the offset scan in AnalyzeNetwork.txt, which records every
function that dereferences a CN_INFO * and the field offset used. Field names are inferred
from accessor function names and call context.
CN_INFO Offset Table¶
| Offset | Size | Inferred Field | Accessor / Evidence |
|---|---|---|---|
0x00 |
dword | version (1, 2, or 3) |
CN_ReadConfig — branch on iVar2 == 3 / 2 / 1 |
0x01 |
byte | flags byte 1 | FUN_004024d0, ?IFMSetTime@@YAXD@Z, ?MPMissionInit2@@YGXXZ |
0x02 |
word | flags word | FUN_0041c460 |
0x04 |
dword | transport_type or link count |
_explode, FUN_00401180 (read), ?NET_CancelPlayerList@@YAXXZ |
0x06 |
word | sub-field of transport word | FUN_004075de |
0x07 |
byte | sub-field | FUN_0040b320 |
0x08 |
dword | local_addr / connection slot |
FUN_00401180, FUN_00401430, ?player_list_process_pkt@@YAXPAUNET_PKT@@PAUsocket_state@@@Z |
0x0C |
dword | remote_addr or session name |
FUN_00401280, FUN_004014b0, FUN_00401540, ?ZeroFrame@@YGXPAEJJF@Z |
0x10 |
dword | mouse/display field | ?MouseLoadPtr@@YGXXZ, ?MaybeCampaignMenu@@YGXPADJJD@Z |
0x14 |
dword | player list context pointer | ?NET_RequestPlayerList@@YADPAUNET_ADDRESS@@PAXP6AX1W4PLAYER_ACTION@@PAD0J@Z1P6AX1W4NET_CONNECTED_STATE@@@Z@Z |
0x18 |
dword | HUD/display field | FUN_0040d4e0 |
0x1C |
dword | HUD alt state | ?HUDDrawAlt@@YIXXZ |
0x20 |
dword | HUD range / weapons | ?HUDDrawRangeInfo@@YIXXZ, FUN_00408c80 |
0x24 |
dword | HUD heading | ?HUDDrawHeading@@YIXXZ |
0x28 |
dword | HUD speed / waypoints | ?HUDDrawSpeed@@YIXXZ, FUN_00409f30 |
0x2C |
dword | HUD / view | FUN_00408c8b, FUN_0040eba0 |
0x30 |
dword | net session / map | ?NET_MasterShutdown@@YAXXZ, FUN_00422a71 |
0x34 |
dword | HUD init slot | _HUDInit@0 |
0x38 |
dword | weapon panel | ?HUDDrawWeaponInfo@@YIXXZ, ?ComputeBombPosition@4 |
0x40 |
dword | HUD draw field | ?HUDHasFlaps@@YIDHXZ, FUN_00408c80 |
0x44 |
dword | screen / view slew | _VIEWSlew@12 |
0x4C |
dword | HUD nearest target | @HUDFindNearest@8 |
0x50 |
dword | view / graphics | FUN_0040f270 |
0x52 |
byte | NET_SlaveInit flag | ?NET_SlaveInit@@... (offset 0x52) |
0x54 |
dword | HUD / font | @HUDFindNearest@8, FUN_0040c990 |
0x58 |
dword | — | FUN_00409910, FUN_0040e3a0 |
0x60 |
dword | — | FUN_0040e450 |
0x64 |
dword | slave pkt connection | ?slave_process_pkt@@YAXPAUNET_PKT@@PAUsocket_state@@@Z |
0x68 |
dword | flight loop slot | @FlightKey@4 |
0x6C |
word | player screen handle | FUN_004075de |
0x70 |
dword | pilot object slot | FUN_00416050 |
0x7C |
dword | flying-loop state | ?FlyingLoop@@YAXXZ (offset 0x7C) |
0x80 |
dword | mission / damage | FUN_004282d0 |
0x84 |
dword | camera / view parms | FUN_00424040, FUN_004240d0 |
0x88 |
dword | MP send buffer | ?MPSend@@YGXXZ |
0x8C |
byte | MP slow-comm flag | ?MPSetSlowComm@@YGXXZ (offset 0x8C implied by +0x148) |
0x99 |
byte | chat / text | FUN_00413151 |
0xA0 |
dword | player-list HUD | _HUDDraw@4 |
0xA8 |
dword | MP connect flag | ?MPCheckConnection@@YGXXZ (offset 0xB0) |
0xB4 |
dword | message state | FUN_0040d7f0 |
0xB6 |
word | HUD stability | ?HUDSetStability@@YIJXZ |
0xC8 |
dword | slave connection context | FUN_00401e30 |
0xD0 |
dword | MSG init | _MSGInit@0 |
0xD8 |
dword | slave disconnect context | ?handle_slave_connection_failed@@YAXPAUsocket_state@@@Z |
0x100 |
dword | menu / shell state | ?ShellSetup@@YGXXZ, ?MenuStartUp@@YGXPADJJDJ@Z |
0x104 |
dword | menu item state | FUN_0040cea0, FUN_0040cf40 |
0x10C |
dword | net dialog | ?netDialogAppIO@@YAJJPAD@Z |
0x114 |
dword | init capability field | _HUDInit@0, ?InitVideo@@YAGPAUGlobalData@@@Z (confirmed entity struct offset) |
0x148 |
dword | MP slow-comm period | ?MPSetSlowComm@@YGXXZ |
0x154 |
dword | MP connect field | ?MPConnect@@YGXXZ |
0x194 |
dword | menu / display large | FUN_0040d390 |
0x198 |
dword | name list | @GetNames@4 |
0x1B0 |
dword | — | FUN_0043bba0 |
0x1D4 |
dword | MSGSend buffer | _MSGSend@32 |
0x1E0 |
dword | view transition | FUN_0040d810 |
0x1F0 |
dword | INFO2 draw | ?INFO2Draw@@YIXXZ |
0xDB0 |
dword[16]+ | CN_INFO_TCP block |
_NetSetFactoryTCP call in CN_ReadConfig (v1/v2 upgrade path) |
0x8E4 |
byte | IPX node / address hex string start | CN_ReadConfig v1/v2 migration — atohb(param_1 + 0x8E4, param_1 + 0xDD0, 8) |
0x8EC |
byte | secondary address hex string | atohb(param_1 + 0x8EC, param_1 + 0xDD4, 0xC) |
0xDD0 |
byte[8] | binary network address (node) | decoded from 0x8E4 |
0xDD4 |
byte[12] | binary network address (full) | decoded from 0x8EC |
0xDDA |
byte | address decode valid flag | set to 1 if both atohb calls succeed |
0xDDB–0xDDC |
byte[2] | padding / end | zeroed in v1/v2 migration |
Notes:
- Total struct size: 0xDDC bytes (3,548 bytes). This matches NET.DAT's 3,552-byte file size
(4-byte checksum prefix + 0xDDC struct body).
- CN_ReadConfig writes a 4-byte checksum before the struct; CfigChecksum (0x47F740)
computes it over the raw CN_INFO bytes.
- Version 3 is the current format. Versions 1 and 2 trigger an upgrade path via
_NetSetFactoryTCP and binary address migration.
CN_ReadConfig / CN_WriteConfig¶
CN_ReadConfig — 0x47F7A0¶
FA.SMS: ?CN_ReadConfig@@YAXPAUCN_INFO@@PAE@Z
Parameters: CN_INFO *info, char *path
void CN_ReadConfig(CN_INFO *info, char *path) {
FILE *f = _wfopen(path, "rb");
if (f && fread(&checksum, 4, 1, f) == 1) {
size_t n = fread(info, 1, 0xDDC, f);
fclose(f);
if (CfigChecksum(info) == checksum) {
if (info->version == 3) {
if (n == 0xDDC) {
/* copy _janesOnlineName over info->name field if non-empty */
return;
}
} else if (info->version == 1 || info->version == 2) {
info->version = 3;
NetSetFactoryTCP(info->tcp_block);
if (info[0x8E4] != 0) {
atohb(&info[0x8E4], &info[0xDD0], 8);
if (atohb(&info[0x8EC], &info[0xDD4], 0xC))
info[0xDDA] = 1;
} else {
memset(&info[0xDD0], 0, 11);
}
/* copy _janesOnlineName if non-empty */
return;
}
}
}
CN_SetFactoryDefaults(info);
}
Key points:
- Opens the file with _wfopen (wide-path wrapper). The path argument is typically "EA.CFG".
- Reads a 4-byte checksum header, then 0xDDC bytes of struct body.
- Validates checksum via CfigChecksum (0x47F740) before trusting any fields.
- On version mismatch or corrupt data, falls back to CN_SetFactoryDefaults (0x47F6D0).
- v1/v2 migration: patches the TCP block at +0xDB0 via _NetSetFactoryTCP, then decodes the
hex IPX addresses stored at +0x8E4 / +0x8EC into binary form at +0xDD0 / +0xDD4.
- _janesOnlineName (a global) overwrites the player-name field at the tail of the struct when
set — this is the Janes.com online service name override.
CN_WriteConfig — 0x47F930¶
FA.SMS: ?CN_WriteConfig@@YAXPAUCN_INFO@@PAE@Z
Parameters: CN_INFO *info, char *path
Writes checksum then the full 0xDDC-byte struct. No transformation — the struct is written verbatim.
MPReceive — 0x46C980¶
FA.SMS: ?MPReceive@@YGDXZ
Signature: char MPReceive(void) — returns non-zero if any packet was processed.
MPReceive is the per-frame inbound packet dispatcher. It is called from the main game loop and
all modal screens that need to stay synchronized during multiplayer.
What it does¶
- Timeout scan — iterates all peer slots (
0to_numComputers - 1). For each peer that is connected (bit set inMP_Info->connected_maskat+0x158): - If the last packet timestamp (
DAT_00546e30[peer]) is more than0x500ticks old, sends a keepalive byte0x4EviaFUN_0046c0a0and flushes. -
If no packet received for
0x5A00ticks, calls_MP_Disconnector clears the connected bit. -
Receive loop — for each peer, calls
FUN_0046ee00(peer)to pull a pending packet type byte. Dispatches on type byte (0x10–0x51 confirmed, plus default fall-through). Updates the last-receive timestamp atDAT_00546e78[peer]on success. -
Returns —
uStack_54c(1 if any packet was handled, 0 if none). -
Tail — calls
(*_MP_Often)()(the transport layer's per-frame poll).
Packet type dispatch table (MPReceive switch)¶
| Type | Payload bytes | Action |
|---|---|---|
0x10 |
4 | Sync packet tick timestamp — updates _packetTicks[peer] |
0x11 |
2 | Frame rate update — writes DAT_00547328[peer] |
0x12 |
1 | Scenario end time trigger — calls _SetScenarioEndTime(2) |
0x13 |
3 | Object removal — resolves alias, calls _RemoveCurObj |
0x14 |
9 | Position update (cases 0x14 and 0x15) — reads obj+0xA8/AC/B0 (XYZ), +0xCC/D0 (angles); computes interpolation velocities into +0xB4/B8/BC/D2/D4/D6 |
0x15 |
6 | Partial position update (XYZ only) |
0x16 |
0x15 | Full entity state — position + angles + velocity packed |
0x17 |
0x2B | Projectile add — calls _PROJAdd, writes +0x74 (owner ID), +0xE6 (target), fires sound |
0x18 |
5 | Ejection — resolves pilot alias, calls _EJECTAdd |
0x19 |
0x2B | Manual entity add — calls _MANAdd |
0x1A |
0x0E + variable | Multi-player message — calls _MSGMultiGet or _SAYMsg |
0x1B |
0x11 | Crater graphic — calls _GRAPHICAddCrater |
0x1C |
0x13 | Explosion graphic — calls _GRAPHICAddExp |
0x1D |
0x1C | Smoke graphic — calls _GRAPHICAddSmoke |
0x1E |
0x1C | Debris graphic — calls _GRAPHICAddDebris |
0x1F |
0x1C | Cluster release debris |
0x20 |
0x10 | Special debris — calls _GRAPHICAddSpecialDebris |
0x21 |
0x11 | Hulk graphic — calls _GRAPHICAddHulk |
0x22 |
0x0F | Device launch — calls _GRAPHICAddDevice, _PROJRetargetMissilesOnDevice |
0x23 |
0x22 | Fire graphic — calls _GRAPHICAddFire |
0x24 |
0x2D | Smoke adder graphic — calls _GRAPHICAddSmokeAdder |
0x25 |
3 | Graphic remove — calls _GRAPHICRemove |
0x26 |
0x0B | Weapon state update — writes entity flags at DAT_0050CE81, heading DAT_0050CEB4, speed DAT_0050CE8E |
0x27 |
9 | Projectile target update — writes DAT_0050CF5E flags, calls _PROJSetTarget |
0x28 |
0x10 | NPC state sync — writes DAT_0050CF5E, calls _NPCSetStateTarget, updates preferred target and nav fields |
0x29 |
5 | Fuel quantity — writes DAT_0050CF9A |
0x2A |
0x1E | Full aircraft state — updates HUD state flags DAT_0050CFEF, aircraft type name at DAT_0050D095, airport pointer |
0x2B |
7 | Fuel + throttle fast path — updates DAT_0050CEB4 (throttle), DAT_0050D10A, fires damage timers from DAT_0050CFEF bits |
0x2C |
0x1A | Weapon stats — calls _WpnStats |
0x2D |
5 | Kill stats — calls _KillStats |
0x2E |
7 | Landing stats — calls _LandingStats |
0x2F |
0x79 | HUD message string — calls _HUDMessage |
0x30 |
0x1B | Player revive — calls FUN_00472670, increments _playerRevives[peer] |
0x31 |
10 | Mission score — calls _MISSIONAddScore |
0x32 |
0x0D | Packet tick write to DAT_00546D68 buffer |
0x33 |
0x12 | Mission file sync — saves to file or calls _MISSIONTextProc, sets DAT_00546D64 = 1 |
0x34 |
0x12 | Campaign save file sync — saves or deletes file, sets DAT_00546E98 = 1 |
0x35 |
5 | Random seed sync — writes _randomSeed, sets DAT_00546DD8 = 1 |
0x36 |
9 | Game prefs sync — updates _gamePrefs, _gameMultiPrefs, calls _MISSIONPrefsChanged |
0x37 |
5 | Screen transition — sets _masterNextScreen if not already in-mission |
0x38 |
2 | Human chose side (pre-mission) |
0x39 |
9 | Quick mission button press |
0x3A |
0x55 | Quick mission button text |
0x3B |
2 | Human chose side (variant) |
0x3C |
9 | Fort mission button press |
0x3D |
0x55 | Fort mission button text |
0x3E |
9 | Fort mission 2 button press |
0x3F |
0x55 | Fort mission 2 button text |
0x40 |
0x12 | Anti-cheat data — copies into _globalAntiCheat table |
0x41 |
2 | Cheats on flag — writes _globalCheatsOn[peer] |
0x42 |
5 | Frame rate report — writes _globalFrameRate[peer] |
0x43 |
2 | Game mode update (_gameMode) |
0x44 |
0x0E | Single mission filename sync |
0x45 |
1 | Ready signal — sets DAT_00546D58[peer] = 1 |
0x46 |
5 | MP status update — writes _mpStatus[peer] |
0x47 |
5 | MP status-to-draw update — writes _mpStatusToDraw[peer] |
0x48 |
8 | Object control assignment — calls _OBJSetControl |
0x49 |
3 | Map control assignment — calls _MAPMaybeSetControl |
0x4A |
0x10 | Plane type change — calls _ChangePlaneType |
0x4B |
7 | Fuel quantity fast path — writes DAT_0050D07C |
0x4C |
0x1C5 | Hardpoints sync — walks hardpoint slots via _HARDPtrs, resolves resource names |
0x4D |
0x334 | Waypoints sync — resolves aliases via _OBJAliasWaypoint, calls _MAPSetNewWP / _MAPMaybeClearSelWP |
0x4E |
1 | Keepalive / noop |
0x4F |
2 | Game mode byte only |
0x50 |
5 | Scenario end time — writes _endScenarioSetTime |
0x51 |
5 | Mission succeeded — writes _missionSucceeded |
Position interpolation (cases 0x14–0x16): After updating the entity's canonical position
fields (+0xA8/AC/B0) and angles (+0xCC/D0), MPReceive computes per-axis velocity
interpolants: vel = (new - old) * 0x100 / tick_delta, written to +0xB4/B8/BC. Angular
rates go to +0xD2/D4/D6. The tick delta comes from _packetTicks[peer] - obj[+0xA0].
The last two snapshot positions are preserved at +0xC0/C4/C8 (copied from +0x11/15/19/1D).
Callers of MPReceive¶
MPReceive is polled from 19 call sites. The principal ones are:
| Caller VA | FA.SMS Name | Context |
|---|---|---|
0x46BE50 |
?MPCheckConnection@@YGXXZ |
Connection health check loop |
0x46C28C |
FUN_0046c28c |
MPMissionInit1 helper |
0x471A90 |
FUN_00471a90 |
Wait-for-everyone-status loop |
0x404C70 |
?FlyingLoop@@YAXXZ |
Main flight loop — called every frame in-mission |
0x422A71 |
FUN_00422a71 |
Mission map screen (pre-flight lobby) |
0x488490 |
@DialogUpdate@4 |
Dialog modal pump |
0x46BDE0 |
?MPFlushAll@@YIXD@Z |
Flush-all helper |
0x4A08A0 |
_ChooseActivity@0 |
Activity selection screen |
0x4A1DD0 |
@BriefScreen@16 |
Briefing screen |
0x4A5620 |
@ChooseSidesDialog@4 |
Choose sides dialog |
0x41FB60 |
_FortMission@4 |
Fort mission setup |
0x474800 |
_FlightMenu |
In-flight menu |
Transport Layers¶
IPX¶
The IPX transport is referenced by protocol name strings at several addresses
(0x4FC9C8, 0x4FFF2B, 0x500107, 0x501400, 0x5036B6).
Selection via ?NetSetProtocol@@YADPADJ@Z (0x4B0610), which matches the string "IPX".
The master/slave session model uses:
| Function | VA | FA.SMS Name | Role |
|---|---|---|---|
NET_MasterInit |
0x40AE50 |
?NET_MasterInit@@YADPAUCN_INFO@@... |
Host: initialises session, registers player callbacks |
NET_MasterStartGame |
0x40AFA0 |
?NET_MasterStartGame@@YADXZ |
Host: signals all players to start |
NET_MasterRejectPlayer |
0x40AFF0 |
?NET_MasterRejectPlayer@@YAXPAUNET_ADDRESS@@PAD@Z |
Host: kick a player |
NET_MasterShutdown |
0x40B080 |
?NET_MasterShutdown@@YAXXZ |
Host: tear down session |
NET_SlaveInit |
0x4016C0 |
?NET_SlaveInit@@YADPAUCN_INFO@@PAUNET_ADDRESS@@... |
Client: join session at address |
NET_SlaveShutdown |
0x401780 |
?NET_SlaveShutdown@@YAXXZ |
Client: leave session |
NET_RequestPlayerList |
0x4017B0 |
?NET_RequestPlayerList@@YADPAUNET_ADDRESS@@... |
Query lobby for player list |
NET_CancelPlayerList |
0x401850 |
?NET_CancelPlayerList@@YAXXZ |
Cancel pending player list query |
NET_Initialize |
0x4B0830 |
?NET_Initialize@@YAJPAUCN_INFO@@J@Z |
General network init (all transports) |
NET_Shutdown |
0x4B0A10 |
?NET_Shutdown@@YAXXZ |
Tear down active transport |
NET_StartQuery |
0x4B0940 |
?NET_StartQuery@@YADPAUCN_INFO@@PAXP6AX... |
Begin async session query |
NET_ShutdownQuery |
0x4B0A90 |
?NET_ShutdownQuery@@YAXXZ |
Cancel session query |
NET_Often |
0x4B0AC0 |
?NET_Often@@YAXXZ |
Per-frame transport pump (called from MPReceive tail) |
Low-level send helpers used by all transports:
| Function | VA | Role |
|---|---|---|
pkt_send |
0x45D970 |
Send packet to one peer by index |
pkt_sock_send |
0x45D940 |
Send packet to one socket |
pkt_send_all |
0x4B1B80 |
?net_send_all@@YAXPAUNET_PKT@@@Z — broadcast to all peers |
pkt_set_header |
0x45DA10 |
Fill NET_PKT header fields |
pkt_sock_read |
0x45DB00 |
Read one packet from a socket |
TCP/IP¶
tcpinit (0x4ABBF0, ?tcpinit@@YADPAUNET_ADDRESS_LIST@@@Z) creates a TCP socket
(socket(AF_INET, SOCK_STREAM, 0) via WinSock), binds to the local address, calls
gethostbyname to enumerate local interfaces, and returns up to three local IP addresses
in a NET_ADDRESS_LIST struct. It is called during the TCP configuration screen.
| Function | VA | FA.SMS Name | Role |
|---|---|---|---|
tcpinit |
0x4ABBF0 |
?tcpinit@@YADPAUNET_ADDRESS_LIST@@@Z |
Socket create + local IP enumeration |
tcpinit2 |
0x4ABD40 |
?tcpinit2@@YADPAUNET_PROTOCOL@@PAUCN_INFO@@@Z |
Full TCP init with CN_INFO |
tcpconnect |
0x4ABF00 |
?tcpconnect@@YAIPAUNET_ADDRESS@@@Z |
Connect to remote peer |
winsock_load |
0x4A5990 |
?winsock_load@@YADPAU_winsock_funcs@@PAD@Z |
Load WinSock DLL, populate function table |
winsock_cleanup |
0x4A5CB0 |
?winsock_cleanup@@YAXXZ |
Unload WinSock |
socket_close |
0x4A5FA0 |
?socket_close@@YAXI@Z |
Close one socket |
socket_close_all |
0x4A61A0 |
?socket_close_all@@YAXXZ |
Close all sockets |
socket_get_state_ptr |
0x4A6220 |
?socket_get_state_ptr@@YAPAUsocket_state@@I@Z |
Look up socket_state by index |
socket_set_state |
0x4A62A0 |
?socket_set_state@@YAXPAUsocket_state@@W4SOCK_STATE@@@Z |
Update socket FSM state |
net_do_accept_connection |
0x4B1BF0 |
?net_do_accept_connection@@YADIP6ADIJHPAUsocket_state@@@Z@Z |
Accept incoming TCP connection |
NET_GetLocalAddressString |
0x4B0730 |
?NET_GetLocalAddressString@@YAXPAD@Z |
Return IP string |
NET_Addr2string |
0x4B1380 |
?NET_Addr2string@@YADPAUNET_ADDRESS@@PAD@Z |
Format NET_ADDRESS to string |
The socket FSM types are SOCK_STATE (enum) and SOCK_TYPE (enum). State-function dispatch is
registered per-socket via socket_add_state_func (0x4A6260) and
socket_set_all_players_state_funcs (0x4A62D0).
The TCP configuration screen is ?RunNetConfigurationScreen@@YGXJ@Z (0x405DF0), which
renders a dialog with the string "NETIPX3" as background. It calls MP_Initialize with
transport type 3 (TCP).
Serial / Modem¶
COM port strings (COM1–COM8) appear at 0x501730–0x501762. Modem init strings appear at
0x4F543D onward.
| Function | VA | FA.SMS Name | Role |
|---|---|---|---|
SER_Initialize |
0x4ACB20 |
?SER_Initialize@@YAJPAUCN_INFO@@J@Z |
Full serial init (calls phases 1–5) |
SER_Initialize1 |
0x44C570 |
?SER_Initialize1@@YAJPAUCN_INFO@@J@Z |
Phase 1: port selection |
SER_Initialize2 |
0x44C5D0 |
?SER_Initialize2@@YAJPAUCN_INFO@@J@Z |
Phase 2: baud rate |
SER_Initialize2_5 |
0x44C6E0 |
?SER_Initialize2_5@@YAJPAUCN_INFO@@J@Z |
Phase 2.5: flow control |
SER_Initialize3 |
0x44C980 |
?SER_Initialize3@@YAJPAUCN_INFO@@J@Z |
Phase 3: open COM port |
SER_Initialize4 |
0x44C990 |
?SER_Initialize4@@YAJPAUCN_INFO@@J@Z |
Phase 4: modem dial / wait |
SER_Initialize5 |
0x44CA70 |
?SER_Initialize5@@YAJPAUCN_INFO@@J@Z |
Phase 5: exchange names |
SER_Shutdown1 |
0x44CBD0 |
?SER_Shutdown1@@YAXXZ |
Shutdown phase 1 |
SER_Shutdown2 |
0x44CC00 |
?SER_Shutdown2@@YAXXZ |
Shutdown phase 2 |
SER_Write |
0x44BDB0 |
?SER_Write@@YAJJPAEJ@Z |
Write bytes to COM port |
SER_Read |
0x44BFC0 |
?SER_Read@@YAJJPAEJ@Z |
Read bytes from COM port |
SER_Flush |
0x44BD40 |
?SER_Flush@@YADJD@Z |
Flush COM send buffer |
SER_ExchangeNames |
0x44C260 |
?SER_ExchangeNames@@YADPAE@Z |
Handshake: exchange player names |
SER_ForegroundGetPacket |
0x44BC00 |
?SER_ForegroundGetPacket@@YADAAUSERIAL_PACKET_WRAPPER@@@Z |
Pull packet from foreground queue |
SER_ForegroundPutPacket |
0x44BCB0 |
?SER_ForegroundPutPacket@@YADAAUSERIAL_PACKET_WRAPPER@@@Z |
Push packet to foreground queue |
SER_ReadIncomingPackets |
0x492690 |
?SER_ReadIncomingPackets@@YAXXZ |
Process all pending incoming serial packets |
SER_ProcessIncomingPacket |
0x492570 |
?SER_ProcessIncomingPacket@@YAXUSERIAL_PACKET@@@Z |
Dispatch one incoming serial packet |
serIO |
0x44CCC0 |
?serIO@@YAXJ@Z |
Serial I/O background thread body |
MOD_Initialize |
0x49AFF0 |
?MOD_Initialize@@YAJPAUCN_INFO@@J@Z |
Modem full init |
MOD_FindModemAndInit |
0x49A850 |
?MOD_FindModemAndInit@@YAJPAUCN_INFO@@J@Z |
Auto-detect modem |
MOD_DoConnect |
0x49AD70 |
?MOD_DoConnect@@YAJPAUCN_INFO@@JPAJ@Z |
Dial and connect |
MOD_WaitForCall |
0x49AC00 |
?MOD_WaitForCall@@YAJXZ |
Answer incoming call |
MOD_Shutdown |
0x49B0D0 |
?MOD_Shutdown@@YAXXZ |
Hang up and release modem |
RunSerialConfigurationScreen |
0x49B1D0 |
?RunSerialConfigurationScreen@@YGXXZ |
Serial setup dialog |
RunModemConfigurationScreen |
0x49C780 |
?RunModemConfigurationScreen@@YGXXZ |
Modem setup dialog |
Serial packets are wrapped in SERIAL_PACKET_WRAPPER structs queued in SERIAL_QUEUE
ring buffers. Reliability is handled by sequence numbers, retransmit requests
(SER_SendRequests, 0x4AC2E0), and CRC verification (verifyPacketCRC, 0x49A040).
Packet Encode / Decode¶
No dedicated packetinit / packencode / packdecode symbols are present in FA.SMS for
this range. Packet framing is inline in the transport send/receive paths. The key structures
confirmed from decompilation:
NET_PKT— generic network packet; header set bypkt_set_header(0x45DA10), read bypkt_sock_read(0x45DB00).SERIAL_PACKET/SERIAL_PACKET_WRAPPER— serial framing layer.setPacketInfo(0x499F70) fills header fields.assignPacketCRC(0x49A070) computes and writes the CRC.verifyPacketCRC(0x49A040) validates on receive.PKT_PLAYER_AD— player advertisement packet; name is obfuscated bynet_mung_name(0x4B2120) and decoded bynet_unmung_name(0x4B2180).
Packet queue helpers: insertQueue (0x49A400), retrieveFromQueue (0x49A4A0),
updateQueueHead (0x49A3E0), fetchFromQueueTail (0x49A4D0).
Session Management¶
FA uses a master/slave model with no dedicated netsession / sessionjoin / sessionhost
symbols. The equivalent functions are:
| Function | VA | Role |
|---|---|---|
NET_MasterInit |
0x40AE50 |
Create session (host) |
NET_SlaveInit |
0x4016C0 |
Join session (client) |
NET_MasterStartGame |
0x40AFA0 |
Advance all players to in-mission |
NET_RequestPlayerList |
0x4017B0 |
Query lobby for current player list |
MP_Initialize |
0x494BC0 |
?MP_Initialize@@YAJPAUCN_INFO@@J@Z — high-level MP init (calls NET_Initialize + transport setup) |
MP_Shutdown |
0x494CC0 |
?MP_Shutdown@@YAXXZ — high-level teardown |
MP_SetTransmitTimeout |
0x494D40 |
?MP_SetTransmitTimeout@@YAXJ@Z — set send retry timeout |
MPMissionInit1 |
0x46C280 |
?MPMissionInit1@@YGXXZ — sync players before mission start |
MPMissionInit2 |
0x46C470 |
?MPMissionInit2@@YGXXZ — post-start sync |
MPCheckConnection |
0x46BE50 |
?MPCheckConnection@@YGXXZ — per-frame connection health |
MPConnect |
0x46C190 |
?MPConnect@@YGXXZ — initiate connection sequence |
MPTimeSync |
0x46C260 |
?MPTimeSync@@YGXXZ — time synchronization handshake |
net_start_game |
0x4B2530 |
?net_start_game@@YAXXZ — signal start from host |
fill_in_mpinfo |
0x4B1590 |
?fill_in_mpinfo@@YAXXZ — populate MP_Info struct |
dlg_list_init |
0x464660 |
?dlg_list_init@@YAPAXG@Z — player list dialog init |
dlg_list_add |
0x4648B0 |
?dlg_list_add@@YAXPAXW4PLAYER_ACTION@@PADPAUNET_ADDRESS@@J@Z — add player to lobby list |
dlg_list_get_selection |
0x464800 |
?dlg_list_get_selection@@YA?AW4DLG_SELECTION@@PAXPAUNET_ADDRESS@@PAD@Z — get selected player |
The PLAYER_ACTION and NET_CONNECTED_STATE enums are used in callback signatures
throughout the session management layer.
MP Entity Sync¶
These functions broadcast entity state changes to all peers. They all guard on
_numComputers != 1 (no-op in single-player) and call FUN_0046c0a0 to enqueue the
outbound packet, then MPFlushAll to send.
| Function | VA | FA.SMS Name | Packet type | What is synced |
|---|---|---|---|---|
MPSetFuel |
0x4723A0 |
?MPSetFuel@@YIXG@Z |
0x4B |
Current fuel quantity (DAT_0050D07C) for object alias |
MPSetHardpoints |
0x472400 |
?MPSetHardpoints@@YIXG@Z |
0x4C |
All hardpoint slots — resource name (13 bytes each) + load data (0x11 bytes each); up to DAT_0050D31D slots |
MPSetWaypoints |
0x472520 |
?MPSetWaypoints@@YIXGPAUWAYPOINT@@@Z |
0x4D |
Full waypoint chain (up to 0x334 bytes) with alias substitution via _OBJAliasWaypoint |
MPChangePlaneType |
0x472330 |
?MPChangePlaneType@@YGXGPAD@Z |
0x4A |
Aircraft type change — object alias + .PT resource name |
MPProjAdd |
0x470010 |
?MPProjAdd@@YGXXZ |
— | Fires 0x17 packet for current projectile |
MPEjectAdd |
0x470150 |
?MPEjectAdd@@YIXGG@Z |
— | Fires 0x18 packet for ejection event |
MPManAdd |
0x4701A0 |
?MPManAdd@@YGXGPADEPAUF24_POINT3@@1J@Z |
— | Fires 0x19 packet for manual entity |
MPSetControl |
0x472260 |
?MPSetControl@@YGXJGD@Z |
0x48 |
Object control assignment |
MPRequestControl |
0x4722D0 |
?MPRequestControl@@YIXG@Z |
— | Request control of object |
MPGraphicAddCrater |
0x4708B0 |
?MPGraphicAddCrater@@YIXPAUF24_POINT3@@J@Z |
0x1B |
Crater position + type |
MPGraphicAddExp |
0x470900 |
?MPGraphicAddExp@@YGXJPAUF24_POINT3@@GDDD@Z |
0x1C |
Explosion position + params |
MPGraphicAddSmoke |
0x470990 |
?MPGraphicAddSmoke@@YGXJPAUF24_POINT3@@JJJG@Z |
0x1D |
Smoke position + params |
MPGraphicAddDebris |
0x470A20 |
?MPGraphicAddDebris@@YGXJPAUF24_POINT3@@PAUWORD_POINT3@@JD@Z |
0x1E |
Debris |
MPGraphicAddFire |
0x470C30 |
?MPGraphicAddFire@@YGXEPAUF24_POINT3@@GPAUWORD_POINT3@@JJJ@Z |
0x23 |
Fire |
MPGraphicRemove |
0x470DC0 |
?MPGraphicRemove@@YGXXZ |
0x25 |
Remove graphic for current object |
MPSend |
0x46EED0 |
?MPSend@@YGXXZ |
varies | Outbound packet flush / batch send |
MPFlushAll |
0x46BDE0 |
?MPFlushAll@@YIXD@Z |
— | Flush all queued packets, calls MPReceive |
MPService |
0x46C520 |
?MPService@@YGXXZ |
— | Per-frame MP service (called from main loop) |
MPRemoveCurObj |
0x46FDB0 |
?MPRemoveCurObj@@YGXXZ |
0x13 |
Broadcast current object removal |
MPEndMission |
0x46FE00 |
?MPEndMission@@YGXXZ |
— | Signal mission end to all peers |
MPMsgSend |
0x470640 |
?MPMsgSend@@YIXPAUT_MSG@@@Z |
0x1A |
Send chat/voice message |
MPHUDMessage |
0x471490 |
?MPHUDMessage@@YIXPAD@Z |
0x2F |
Broadcast HUD message string |
MPSendAntiCheat |
0x471010 |
?MPSendAntiCheat@@YIXPADJ@Z |
0x40 |
Send anti-cheat hash for named file |
MPSendCheatsOn |
0x471070 |
?MPSendCheatsOn@@YIXD@Z |
0x41 |
Broadcast cheat state |
MPSendFrameRate |
0x4710B0 |
?MPSendFrameRate@@YIXJ@Z |
0x42 |
Broadcast frame rate |
MPSendPrefs |
0x471190 |
?MPSendPrefs@@YGXXZ |
0x36 |
Broadcast game prefs |
MPSendMissionSucceeded |
0x46FEF0 |
?MPSendMissionSucceeded@@YGXXZ |
0x51 |
Broadcast mission success |
MPCheckDisconnect |
0x4733E0 |
?MPCheckDisconnect@@YGDXZ |
— | Check for peer disconnection |
Position update path (0x14/0x15/0x16): Position/angle data is sent from entity
fields +0xA8/AC/B0 (XYZ world coords) and +0xCC/D0 (pitch/yaw/roll angles).
There is no dedicated MPSetPos / MPSetState symbol in FA.SMS; position broadcasting is
inline in FUN_0046c0a0 (the packet enqueue helper) called from the flying loop context.
Dark Zone: 0x482200–0x4AACEF¶
This range lies between the confirmed MP functions and the terrain/airport system. The
AnalyzeNetwork.txt output for this range covers mission stats, fort logic, and text parsing
helpers. Notable functions:
| VA | FA.SMS Name | What it does |
|---|---|---|
0x483C90 |
FUN_00483c90 |
Token scanner — skips whitespace (SP/TAB/CR/LF via FUN_00483D10) then copies the next non-whitespace token from a global text cursor (DAT_0055281C) into a caller buffer. Used by mission text / scripting parser. |
0x483D10 |
FUN_00483d10 |
isspace equivalent — returns 1 for SP, TAB, CR, LF. |
0x483D30 |
FUN_00483d30 |
Read next token, convert to number via _StringToNumber. |
0x483D50 |
FUN_00483d50 |
Map-key aircraft type remapper — translates aircraft type index 5/6/0xD/0xE/0xF for Turkey/Ukraine/Korea theaters (maps letters T/t/U/u/K/k in _mapName). Returns remapped index. |
0x484D90 |
_EndOfMissionStats@0 |
Computes end-of-mission scoring: player damage percentage (_dam._2_2_ vs _startingDamage), wingman survival, target/protect object tallies (_stats_targets, _stats_targetsDead, _stats_protects, _stats_protectsAlive). Sets DAT_0054DDB8 = 1. |
0x485040 |
_EndOfFortMissionStats@0 |
Fort (Fortress) mission variant of end-of-mission stats. |
0x4851C0 |
_MISSIONFortDestroyed@4 |
Check/update fort destruction state for one fort slot. Tests param_1 & 0x80 for destroyed flag, iterates fort objects. |
0x485260 |
_MISSIONFortDestroyedByFort@4 |
Fort-vs-fort destruction: checks if a fort was destroyed by another fort's fire. |
0x4852F0 |
_MISSIONFortStatus@4 |
Returns current fort status bitmask for a given fort index. |
0x485380 |
FUN_00485380 |
Fort status aggregation — walks all fort objects and tallies counts. |
0x485AE0 |
_ConvertPilotFiles@0 |
Pilot file format migration — converts old-format .PLT files to current format. |
0x485EF0 |
_CheckCD |
CD presence check — verifies disc is inserted before mission. |
0x486010 |
_MISSIONLoadCommonResources@0 |
Load shared mission resources (called during mission setup). |
0x4ABBF0 |
?tcpinit@@YADPAUNET_ADDRESS_LIST@@@Z |
TCP socket init (see TCP/IP section above). |
0x4A5990 |
?winsock_load@@YADPAU_winsock_funcs@@PAD@Z |
WinSock DLL loader. |
The text cursor globals used by the token scanner (DAT_0055281C = current position,
DAT_005528C0 = end pointer) suggest this is the mission-text / .MT file parser, operating
on a buffer loaded into the memory manager.
Key Globals Referenced¶
| Address | Symbol | Role |
|---|---|---|
0x546D58 |
— | ready_flags[peer] — set to 1 when peer sends 0x45 ready packet |
0x546D64 |
— | mission_file_received flag — set by packet 0x33 |
0x546D68 |
DAT_00546D68 |
Packet tick receive buffer (13 bytes per peer, written by 0x32) |
0x546DD8 |
— | random_seed_received flag — set by packet 0x35 |
0x546E08 |
DAT_00546E08 |
Last-send tick per peer |
0x546E30 |
DAT_00546E30 |
Last-receive tick per peer (keepalive check) |
0x546E78 |
DAT_00546E78 |
Last-successful-packet tick per peer (disconnect check) |
0x546E98 |
— | campaign_save_received flag — set by packet 0x34 |
0x547328 |
DAT_00547328 |
Per-peer frame rate table (written by 0x11) |
0x4F7798 |
?mpStatus@@3PAJA |
MP status array |
0x4F77B8 |
?mpStatusToDraw@@3PAJA |
MP status-to-draw array |
0x5026B4 |
_doMPStatusDraw |
Flag: whether to draw MP status overlay |
0x55281C |
DAT_0055281C |
Mission text parser cursor pointer |
0x5528C0 |
DAT_005528C0 |
Mission text parser end pointer |
Functions¶
Full record: db/symbols/network.csv.
| VA | Symbol | Role |
|---|---|---|
0x4016C0 |
NET_SlaveInit |
client-side session join |
0x401A60 |
NETSlaveConnect |
slave connect helper (proto vtable open) |
0x4024D0 |
NETProcessPlayerList |
process a NET_PLAYER_LIST packet |
0x46C980 |
MPReceive |
receive + dispatch multiplayer packets |
0x405CD0 |
NETFormatIP |
format an IP address string |
0x402330 |
NETArmKeepalive |
arm the socket send/keepalive timer |
0x496F40 |
spxinit |
SPX/IPX transport init — enumerate adapters |
0x4970C0 |
spxopensocket |
open an SPX socket (socket(6) + IPX ioctl) |
0x497150 |
spxconnect |
SPX connect to a NET_ADDRESS peer |
IPX/SPX transport¶
Alongside the IP/UDP path, FA ships an IPX/SPX transport (Novell NetWare LANs) built on
a dynamically-resolved winsock function-pointer table (socket/bind/listen/ioctl/
closesocket at 0x580CAA…). spxinit/spxinit2 enumerate adapters, spxopensocket
opens and configures an SPX socket, spxlisten/spxconnect accept or dial a peer, and
convert_addr_ipx2usnf/convert_addr_usnf2ipx translate between an IPX sockaddr and the
engine's NET_ADDRESS. These leaves were recovered via the
reproducibility audit (#241).
Open Questions¶
1. Dark zone 0x482200–0x4AACEF¶
The MP-adjacent code in this span (see § Dark Zone) is partly traced; a targeted pass would resolve the remaining session/sync helpers referenced but not yet named.
Recharacterized. The span is not a small set of unnamed session/sync helpers: it holds
~97 still-FUN_ functions, and most are not network — it overlaps the TIME/FPS timing
cluster (0x4869A0–0x486E60, see campaign.md) and other unmapped the game executable code.
The genuinely network-referenced helpers in it are already named; the remainder is a broad
unclaimed region, the same class of gap as the in-flight view/replay cluster, and naming it
is a discovery-scale effort rather than a targeted network read — folded into the unmapped-code
discovery #257.
Status: resolved — re-static (broad unclaimed region; naming folded into #257).
Related¶
- input.md — joystick/mouse input (the sibling of the serial/modem link owned here).
- campaign.md — multiplayer missions share the mission-load and scoring path.
- objects.md — MP entity sync mirrors object state across peers.
- formats/DAT.md — the
NET.DAT/EA.CFGconfigCN_INFOis read from.
MSAPI.DLL — matchmaking / internet-play client¶
MSAPI.DLL is the matchmaking / internet-play client the game executable links for
online multiplayer — EA's own MServerDLL (its InternalName), not the DirectPlay /
serial paths in network.md. It is a thin Winsock client for an EA
matchmaker server: it logs a player or host in, lists and selects games, uploads and
downloads game/player records, and reports mission results, all over a single TCP
connection.
Unlike the third-party middleware in the install (WAIL32 = Miles, the Cdrv comms suite),
MSAPI.DLL is EA-authored — the PE version resource carries CompanyName "Electronic
Arts", LegalCopyright "Copyright (C) 1998, Electronic Arts", and InternalName
"MServerDLL" — so it gets a full reconstruction of its own code. It is built on MFC
(CWinApp/CWinThread/CSocket); that statically-linked MFC/MSVC-CRT framework is the
boundary and is waived, not reversed.
Provenance: Ghidra static analysis of
MSAPI.DLL(imported intofa-re, auto-analysed; public names from the PE export table, internals named by tracing the Winsock protocol). EA-authored: the 14*MS*exports and the matchmaker protocol code are reversed; the statically-linked MFC / MSVC-CRT framework is waived at the boundary (#272 · #274 · #275). Confidence per spec-authoring.md.
The matchmaker link¶
Transport. connectMS resolves the server endpoint from the registry — a small
registry-cache helper class (ms_reg_open → ms_reg_select_subkey → ms_reg_read_value,
with get-or-create semantics) reads Server IP and Server Port from
HKLM\SOFTWARE\…\Matchmaker (or takes a default) — opens a TCP socket
(socket(AF_INET, SOCK_STREAM), kept in ms_socket), connect()s, then sends the literal
string WAKEUP (6 bytes) and expects OK. From then on the socket is the single
duplex link for every command.
Framing. Each export sends a single-byte ASCII command opcode, followed (per
command) by network-byte-order u32 length prefixes and payloads, and reads a one- or
two-byte response ack (O / OK / K). Two primitive pairs do all the I/O:
ms_send_all / ms_recv_all loop send() / recv() until exactly N bytes have moved
(logging "Send/Read Packet Error - Correcting..." on a short transfer), and
ms_send_u32 / ms_recv_u32 wrap them with htonl / ntohl for the length prefixes.
Session. initializeMS performs the registration handshake — it sends the record size,
the u (upload record) command and the player record, then ms_upload_init_arrays (the
i command: two groups of three htonl-byteswapped, length-prefixed u32 arrays), then the
machine's volume serial number (GetVolumeInformation, formatted with "%d") — and creates
the background receive worker ms_recv_thread (an MFC CWinThread, AfxBeginThread /
CREATE_SUSPENDED) which is resumed in host-login mode and suspended in player mode.
The wire protocol¶
Every command is one lowercase ASCII byte (getMSdatafile's mid-transfer ack is the
literal O):
| Opcode | Export | Payload → response |
|---|---|---|
u |
initializeMS / updateMSgame |
u32 size + player/game record → (upload) |
i |
ms_upload_init_arrays |
2×3 length-prefixed u32 arrays → (registration) |
h |
loginMShost |
— → resume receive worker |
p |
loginMSPlayer |
— → suspend receive worker |
r |
requestMSgame |
— → stream of P records (u32-length-prefixed) |
s / d |
selectMSgame / deselectMSgame |
u32 game id → O |
t |
resetMSfilter |
— → reset list cursor |
f |
fetchMSgame |
u32 game id → P + record |
v |
sendMSresults |
u32 length + results blob → O |
z |
getMSdatafilesize |
name → u32 size (0xFFFFFFFF = not found) + O |
x |
getMSdatafile |
name → file bytes; client acks O, server replies K |
l |
closeMS |
— → close socket |
requestMSgame accumulates the P-prefixed game records into a linked list of 0x24-byte
nodes (ms_game_list_head / ms_game_list_tail / ms_game_selected; each node is
+0x10 length · +0x14 payload pointer · +0x1C next · +0x20 list head), guarded by a
critical section, and hands the caller a 5-dword header plus the record payload.
Return codes. 1 = OK; 0x3E8 = socket() failed; 0x3E9 = handshake rejected;
0x3EA = connect / socket error; 0x3EB = protocol (short send/recv); 0x3EC = not
connected; 0x3ED = bad arguments; 0x3EE = empty game list; 0x3EF / 0x3F1 / 0x3F2 /
0x3F3 = command-specific NAKs (select / results / datafile-size / datafile).
Functions¶
The matchmaker exports and the protocol internals (all named in the symbol DB):
| VA | Function | Role |
|---|---|---|
0x100011E0 |
connectMS |
registry endpoint → TCP socket + WAKEUP/OK handshake |
0x10001670 |
initializeMS |
registration handshake; create receive worker |
0x10001A20 |
ms_upload_init_arrays |
opcode i — upload the init u32 arrays |
0x10001D20 |
loginMShost |
opcode h — host login; resume worker |
0x10001D80 |
loginMSPlayer |
opcode p — player login; suspend worker |
0x10001DE0 |
requestMSgame |
opcode r — download the game list |
0x10002030 |
selectMSgame |
opcode s — select a game |
0x100020C0 |
deselectMSgame |
opcode d — deselect a game |
0x10002120 |
resetMSfilter |
opcode t — reset the list cursor |
0x10002170 |
updateMSgame |
opcode u — upload/update a record |
0x100021D0 |
fetchMSgame |
opcode f — fetch one game record |
0x10002280 |
sendMSresults |
opcode v — report mission results |
0x10002330 |
getMSdatafilesize |
opcode z — query server data-file size |
0x10002440 |
getMSdatafile |
opcode x — download a server data-file |
0x10002570 |
closeMS |
opcode l — quit / close socket |
0x10002630 |
ms_recv_all |
recv() exactly N bytes |
0x10002680 |
ms_send_all |
send() exactly N bytes |
0x100026D0 |
ms_recv_u32 |
recv + ntohl length prefix |
0x10002700 |
ms_send_u32 |
htonl + send length prefix |
0x10002730 |
ms_disconnect |
worker teardown: quit + close + free list |
0x10002800 |
ms_reg_open |
registry-cache: open the base keys |
0x10002950 |
ms_reg_close |
registry-cache: flush + close |
0x100029A0 |
ms_reg_select_subkey |
registry-cache: open a named subkey |
0x10002A00 |
ms_reg_read_value |
registry-cache: read a value (get-or-create) |
0x100034D0 |
ms_atoi |
decimal string → int (parses the port) |
FA.EXE ↔ matchmaker boundary¶
The game executable links six of the fourteen exports — initializeMS, connectMS,
sendMSresults, getMSdatafilesize, getMSdatafile, closeMS (see
external-imports.md) — i.e. the connect → play → report results +
pull server data-files → disconnect path. The remaining eight (login*, requestMSgame,
select/deselect/fetch/updateMSgame, resetMSfilter) form the game-browse / lobby
half of the API. FA.EXE holds the socket only inside MSAPI.DLL; it never touches Winsock
for matchmaking itself — the DLL is the whole boundary.
Open Questions¶
1. On-wire record layouts and the matchmaker server¶
The field layouts of the player record (uploaded by u / i) and the P game records are
opaque from the client alone — they are sized by ms_record_size and moved as opaque
blobs; only a live matchmaker server (long defunct) or a packet capture would pin the
fields. The server itself is not part of the FA install.
Status: open — re-gameplay / unrecoverable (the EA matchmaker service is defunct).
2. MFC / MSVC-CRT framework internals¶
The bulk of MSAPI.DLL is statically-linked MFC (CWinApp / CWinThread / CSocket) and
MSVC-CRT — deliberately not reversed (framework code; the matchmaker protocol above is the
documented boundary). No FA understanding depends on it.
Status: resolved — boundary-documented (third-party framework; internals out of scope).
Related¶
- external-imports.md — the FA.EXE →
MSAPI.dllimport surface. - network.md — the game executable's multiplayer, which drives
MSAPI.dll. - ip-tool.md — IP.EXE, the other EA-authored MFC companion binary.
- reconstruction.md — the program this binary belongs to.
CDRVDL32.DLL — Cdrv RS-232 serial comms driver¶
CDRVDL32.DLL is the base RS-232 serial driver of the suite: raw serial I/O (ser_rs232_*), port management, and the bio_* timer helpers. The modem, transfer, and terminal DLLs all import it.
The Cdrv comms suite¶
The game executable's serial/modem multiplayer path — the peer of the DirectPlay / SPX/IPX path in network.md — is built on a four-DLL Cdrv comms library: CDRVDL32 (this file, RS-232 base), CDRVHF32 (Hayes modem), CDRVXF32 (file transfer), and COMMSC32 (terminal screen). It is generic serial/modem middleware (a .commdrv section, a complete Cdrv* / ser_rs232_* ABI, the Win32 comms API underneath, no game-specific content) — licensed third-party code, boundary-documented like WAIL32 (Miles) and the MS redistributables: the exported ABI is named, the internals are waived, not reversed.
Provenance: Ghidra static analysis of
CDRVDL32.DLL(imported intofa-re, auto-analysed; public names from the PE export table). Third-party middleware, documented at the boundary (#247 / #255): the exported ABI is named; internals and referenced data are waived, not reversed. Confidence per spec-authoring.md.
Functions¶
Representative exports (all named in the symbol DB):
| VA | Export | Role |
|---|---|---|
0x100019D0 |
ser_rs232_block |
Cdrv export |
0x100019E0 |
ser_rs232_cleanup |
Cdrv export |
0x10001A90 |
ser_rs232_dtr_off |
Cdrv export |
0x10001AE0 |
ser_rs232_dtr_on |
Cdrv export |
0x10001B30 |
ser_rs232_flush |
Cdrv export |
0x10001C50 |
ser_rs232_getbyte |
Cdrv export |
0x10001D20 |
ser_rs232_getpacket |
Cdrv export |
0x10001DF0 |
ser_rs232_getport |
Cdrv export |
Open Questions¶
1. Internals¶
The waived internals are the third-party Cdrv implementation and statically-linked CRT — deliberately not reversed (licensed middleware; the exported ABI above is the documented boundary). No FA understanding depends on them.
Status: resolved — boundary-documented (third-party; internals out of scope by license).
Related¶
- network.md — the game executable's multiplayer, which drives the serial/modem path.
- comms.md — the CDRVDL32 base driver and suite overview.
- reconstruction.md — the program this binary belongs to.
CDRVHF32.DLL — Cdrv Hayes-modem comms driver¶
CDRVHF32.DLL is the Hayes-modem driver: dialling and modem control (Dial, Modem*), framed data streams (DataStreamGet*), and CRC (CdrvCrc16/CdrvCrc32). Imports the CDRVDL32 base.
The suite's shared design, third-party rationale, and the FA-side boundary are described in comms.md (the CDRVDL32 base driver).
Provenance: Ghidra static analysis of
CDRVHF32.DLL(imported intofa-re, auto-analysed; public names from the PE export table). Third-party middleware, documented at the boundary (#247 / #255): the exported ABI is named; internals and referenced data are waived, not reversed. Confidence per spec-authoring.md.
Functions¶
Representative exports (all named in the symbol DB):
| VA | Export | Role |
|---|---|---|
0x10001000 |
InitializePort |
Cdrv export |
0x100013D0 |
SetBaud |
Cdrv export |
0x10001430 |
SetFlowControlCharacters |
Cdrv export |
0x100014B0 |
SetFlowControlThreshold |
Cdrv export |
0x10001510 |
SetPortCharacteristics |
Cdrv export |
0x100015A0 |
UnInitializePort |
Cdrv export |
0x10001640 |
SetSpecialBehavior |
Cdrv export |
0x10001720 |
Dial |
Cdrv export |
Open Questions¶
1. Internals¶
The waived internals are the third-party Cdrv implementation and statically-linked CRT — deliberately not reversed (licensed middleware; the exported ABI above is the documented boundary). No FA understanding depends on them.
Status: resolved — boundary-documented (third-party; internals out of scope by license).
Related¶
- network.md — the game executable's multiplayer, which drives the serial/modem path.
- comms.md — the CDRVDL32 base driver and suite overview.
- reconstruction.md — the program this binary belongs to.
CDRVXF32.DLL — Cdrv file-transfer comms driver¶
CDRVXF32.DLL is the file-transfer driver: a transfer UI (FileTransferDialog, CdrvXfer*) over a DOS-style file API (dos_*) and cdrvxfer_* send/receive. Imports the CDRVDL32 base.
The suite's shared design, third-party rationale, and the FA-side boundary are described in comms.md (the CDRVDL32 base driver).
Provenance: Ghidra static analysis of
CDRVXF32.DLL(imported intofa-re, auto-analysed; public names from the PE export table). Third-party middleware, documented at the boundary (#247 / #255): the exported ABI is named; internals and referenced data are waived, not reversed. Confidence per spec-authoring.md.
Functions¶
Representative exports (all named in the symbol DB):
| VA | Export | Role |
|---|---|---|
0x10002410 |
CdrvXferCreateDialog |
Cdrv export |
0x10002580 |
CdrvXferUpdateDialog |
Cdrv export |
0x100026A0 |
CdrvXferDestroyDialog |
Cdrv export |
0x10002A90 |
cdrvxfer_files |
Cdrv export |
0x10002AC0 |
cdrvxfer_sfiles |
Cdrv export |
0x10002AF0 |
FileTransferDialog |
Cdrv export |
0x10003080 |
cdrvxfer_gclose |
Cdrv export |
0x100033E0 |
cdrvxfer_getfiles |
Cdrv export |
Open Questions¶
1. Internals¶
The waived internals are the third-party Cdrv implementation and statically-linked CRT — deliberately not reversed (licensed middleware; the exported ABI above is the documented boundary). No FA understanding depends on them.
Status: resolved — boundary-documented (third-party; internals out of scope by license).
Related¶
- network.md — the game executable's multiplayer, which drives the serial/modem path.
- comms.md — the CDRVDL32 base driver and suite overview.
- reconstruction.md — the program this binary belongs to.
COMMSC32.DLL — Cdrv comms terminal-screen service¶
COMMSC32.DLL is the terminal-screen service: a character-cell terminal window (CdrvScr* create/paint/resize/write, commdrvw_char_screen) for the comms UI.
The suite's shared design, third-party rationale, and the FA-side boundary are described in comms.md (the CDRVDL32 base driver).
Provenance: Ghidra static analysis of
COMMSC32.DLL(imported intofa-re, auto-analysed; public names from the PE export table). Third-party middleware, documented at the boundary (#247 / #255): the exported ABI is named; internals and referenced data are waived, not reversed. Confidence per spec-authoring.md.
Functions¶
Representative exports (all named in the symbol DB):
| VA | Export | Role |
|---|---|---|
0x100011F0 |
commdrvw_char_screen |
Cdrv export |
0x10001990 |
CdrvScrDestroy |
Cdrv export |
0x100019B0 |
CdrvScrCreate |
Cdrv export |
0x100019D0 |
CdrvScrResize |
Cdrv export |
0x100019F0 |
CdrvScrWrite |
Cdrv export |
0x10001A10 |
CdrvScrKillFocus |
Cdrv export |
0x10001A30 |
CdrvScrSetFocus |
Cdrv export |
0x10001A50 |
CdrvScrPaint |
Cdrv export |
Open Questions¶
1. Internals¶
The waived internals are the third-party Cdrv implementation and statically-linked CRT — deliberately not reversed (licensed middleware; the exported ABI above is the documented boundary). No FA understanding depends on them.
Status: resolved — boundary-documented (third-party; internals out of scope by license).
Related¶
- network.md — the game executable's multiplayer, which drives the serial/modem path.
- comms.md — the CDRVDL32 base driver and suite overview.
- reconstruction.md — the program this binary belongs to.
Support
Memory & Resource Managers (MM / RM)¶
Two layers the whole engine loads assets through: MM, a handle-based memory allocator
with memory-mapped-file support (0x435C60–0x43631C), and RM, the resource manager — a
filename registry with an LRU cache that resolves per-type load hooks and pulls bytes from
LIB archives into MM handles (0x4A67F0–0x4A6E46).
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recorded in the symbol database and applied to the Ghidra project. Progress: reconstruction matrix. Markers follow spec-authoring.md: confirmed · inferred · unknown.
MM — a 28-byte handle allocator¶
MM hands out T_HANDLE handles (28-byte free-list records) backed by VirtualAlloc/
GlobalAlloc, with alloc-id group free (free everything allocated under an id at once —
the pattern mission load uses to reclaim per-mission memory). Accessing a handle
(MMAccessR/MMAccessW/MMAccessE) is a pointer dereference (a no-op "lock" — a Mac-port
heritage), and flag 0x4000 marks a memory-mapped-file handle. MMFreeHandle calls back
into RM (RMNotify) to keep the resource registry coherent when a cached asset's memory is
released.
RM — filename registry + LRU cache + per-type hooks¶
RM is a ~1400-slot filename registry (resList) with a 20-entry LRU cache. RMAccess by
name resolves through the cache; on a miss RMFindAndLoad pulls the bytes via LoadFile
(from a LIB) into an MM handle and runs the per-type hook — SMCallByName dispatches
<TYPE>_Load / _Setup / _Free (the same name-lookup mechanism the Chuck-Talk interpreter
uses). This is how a ~a10.SH reference in a .PT resolves to a loaded, engine-ready shape.
LIB name resolution — the hint index¶
Beneath RM sits the name→bytes layer. LoadFile (0x4AD3C0) uppercases the name and
resolves it in a fixed order: the LIB hint index first (DoLoadLibFile 0x479630 →
SearchLib 0x4798B0), then, on a miss, loose files on disk in extension-routed
subdirectories (shell, shapes, mission, types, ter_art, layer, …) via
LoadFile2 (0x4AD9B0). So an entry carried in a LIB always wins over a loose file of
the same name. LibFileExists (0x47A130) is the matching existence predicate: it calls
LibOpen (0x479BD0) to test LIB membership, then probes the same loose-file locations by
extension (FUN_0047A510). (This corrects an earlier label of 0x47A130 as an "MM text
keyword parser" — it is the resource manager's asset-existence test, invoked during MM load
among many other paths, not an MM-specific parser.)
The hint index is one flat namespace built at startup by LibStartUp (0x478BC0):
it enumerates the working directory with the glob *.* and, for each file, either opens it
and indexes its directory entries (names containing .LIB) or records the file itself as a
loose-file hint (type byte 0xFF). The result is a single array of 35-byte (0x23) records
in the hintHndl (0x4F7FC4) handle, counted by numHints (0x5473F0), kept sorted by
8.3 name so lookups are a bsearch (SearchLib, comparator CompareHintPtrs). The FA
TOOLKIT's CACHE/LIBPTR.* index (LIB.md) is not
consulted by the retail engine — no such strings exist in the executable; the engine
rebuilds the index itself on every run.
Duplicate-name precedence: the per-file hint inserter (0x47935E) reuses the existing
slot and overwrites it when SearchLib finds the name already present, so within the index
the last registration wins (registration order = directory-enumeration order). The
retail install sidesteps collisions in practice by partitioning content across archives
(the LIB.md inventory — each archive owns distinct
extensions/ranges). The two rules that always hold regardless of enumeration order:
LIB entries precede loose files, and within the LIB set a later mount overrides an
earlier one. Consumers that mount a whole install as one namespace (e.g. the fxs
workspace, gui.md) mirror this: LIB-before-loose, last-mounted-wins,
with every collision recorded rather than hidden.
Functions¶
Full record: db/symbols/memory-resource.csv.
| VA | Symbol | Role |
|---|---|---|
0x435C60 |
MMInit |
initialise the handle allocator |
0x4A6B30 |
RMFindAndLoad |
resolve + load + register a resource by name |
0x4A6AB0 |
RMCacheInsert |
insert into the LRU resource cache (evict oldest) |
0x4A6DF0 |
RMSetup |
post-load per-type hook (SMCallByName <type>_Setup) |
The LIB name-index layer (LoadFile 0x4AD3C0, LibStartUp 0x478BC0, SearchLib
0x4798B0, DoLoadLibFile 0x479630, LibFileExists 0x47A130) is documented in
prose above; those functions live in the executable's lzwlib unit and are not part of
this subsystem's symbol database.
Open Questions¶
1. FUN_004A6B10 ownership¶
FUN_004A6B10 (the ResolveTypeRecord helper) sits in the RM range but is owned by
objects.md — it resolves the MM handle at the type record's +0x0F. The
overlap is expected (RM and the object type-loader are tightly coupled); noted so the
boundary is explicit.
2. T_HANDLE flag bit 0x1000¶
Resolved statically (2026-07-05, #262):
0x1000 is the purged-handle mark from the allocator's Mac heritage (a purgeable
handle whose memory was discarded by compaction must be reloaded). The readers are
real and all follow the same recovery contract — RMFind (0x4A6990) drops a
registry entry whose entry flag +0x0E & 2 is set and whose handle carries 0x1000
(frees the husk, returns miss), RMFindAndLoad re-loads in a loop on the same test,
and BrushFromIndex (0x4AB860) / MAPDrawBG (0x4224EE) free-and-reload their
cached PIC handles. But the writer does not survive: MMUseHandle stores caller
flags verbatim and no call site passes 0x1000, MMFreeHandle zeroes the flag word,
and the one function that would purge — MMCompactRAM (0x4361B0) — is compiled to
return 0 on Win32. In the shipping game the flag can never be set; the recovery
paths are vestigial.
Status: resolved — vestigial purge protocol; writer stubbed out on Win32.
Related¶
- objects.md — the type-loader that resolves shapes through RM/MM.
- formats/LIB.md — the archive
LoadFilereads asset bytes from. - shape-selection.md —
RMAccessloads the shape variants.
IP.EXE — EA system-info & tech-support tool¶
IP.EXE (708 KB) is not a TCP/IP network transport — the epic's original assumption. Its
imports (DDRAW, DSOUND, MAPI32, comdlg32, WINSPOOL; no winsock), its AfxWinMain
entry, and its strings identify it as an MFC application that profiles the machine and
faxes / e-mails a system-configuration report to EA Tech Support (support@ea.com,
Fax to: (650) 286-5080). It is FA/EA-authored app logic wrapped around the statically-linked
Microsoft MFC framework — a bundled support utility, not game-engine or networking code.
Provenance: Ghidra static analysis of
IP.EXE(imported into thefa-reproject, auto-analysed; no export table, no.SMS— named from strings / imports / RTTI / call structure). Boundary-documented (#254): the identifiable FA/EA app-logic surface is named; the bulk (statically-linked MFC/CRT framework and un-analysed tool internals) is waived as third-party, the same license treatment as WAIL32 (Miles) and the comms suite. Confidence markers follow spec-authoring.md.
What it actually does¶
Run from the game's support/setup path, IP.EXE gathers a machine profile and sends it to EA:
- CD-ROM benchmark — times the CD-ROM drive ("Benchmarking CD-ROM Drive…", Single/Double/ Quad-Speed, "Data Transfer Rate: %d KB/s").
- Hardware & OS detection — loads
hdd.dll/cd.dll, shellssysdm.cpl, and reads CPU (vendor/MMX/count), video card + memory + supported modes, sound, modem(s), RAM, Windows version, BIOS. - Network config report — DirectPlay version, Winsock description, IP address, subnet mask, default gateway, RAS connections. These are diagnostics reported to support, not a transport — the actual multiplayer transport is DirectPlay (external) and the in-the game executable SPX/IPX/UDP path (see network.md).
- Report + submit —
BuildSupportReportassembles a[CPU]/[VIDEO]/[SOUND]/[MODEM]config file and sends it to EA Tech Support by fax or MAPI e-mail.
Functions¶
The identifiable EA app-logic entry points (VAs from the symbol DB):
| VA | Function | Role |
|---|---|---|
0x004019B0 |
CDROMBenchmark |
CD-ROM speed / transfer-rate benchmark |
0x00403FE0 |
LaunchSystemProperties |
ShellExecute of sysdm.cpl (System control panel) |
0x00404061 |
LoadDetectionDlls |
LoadLibrary of hdd.dll + cd.dll (hardware-detection helpers) |
0x0040DC60 |
BuildSupportReport |
build the system-config report and fax / e-mail it to EA support |
0x00436EF0 |
WinMain |
MFC AfxWinMain wrapper (Ghidra FID) |
Open Questions¶
1. Full reconstruction — mostly third-party MFC framework¶
IP.EXE has 1,805 functions: ~860 Ghidra-FID-matched (statically-linked MFC / MSVC CRT) and the
rest un-analysed. Because it is an MFC app, the large majority are Microsoft MFC framework
(third-party, statically linked — the same category as WAIL32's CRT and the MS redistributables),
with the FA/EA-authored part limited to the diagnostics logic named above. A full 100 % naming
pass is low-value (a bundled support utility, mostly third-party framework), so the FA/EA app-logic
surface is named and the framework is waived at the boundary.
Status: resolved — boundary-documented (FA/EA app logic named; MFC/CRT framework waived as third-party).
Related¶
- network.md — the game executable's actual multiplayer transport (SPX/IPX/UDP + DirectPlay).
- reconstruction.md — the program this binary belongs to.
- wail32.md — the other companion binary; same third-party-framework boundary pattern.
Modding Guide¶
Quick recipes for common FA modding tasks using fx. Formats, field tables, and offsets
used below are specified in formats/; per-format tooling status is in
the status matrix.
The commands are shell-neutral: fx takes the same arguments everywhere, and
forward-slash paths work in bash and PowerShell alike. Where a recipe needs the game
CD, <CD> stands for your CD or mounted ISO — e.g. F: on Windows,
/run/media/$USER/FA2 on Linux.
Extract PALETTE.PAL from FA_2.LIB once before any paletted image work:
fx lib unpack FA_2.LIB out/FA_2
# PALETTE.PAL is now at out/FA_2/PALETTE.PAL
Texture mod (FA_3.LIB aircraft skins)¶
FA_3.LIB lives on the CD. All 822 textures are raw (uncompressed) 8-bit indexed PICs. No palette is needed to decode them, but you do need the palette to re-encode.
# Extract textures from the CD (or mounted ISO)
fx lib unpack <CD>/FA_3.LIB out/FA_3
# Decode one texture to PNG
fx pic unpack out/FA_3/F16C_0.PIC -o F16C_0.png
# Edit F16C_0.png in GIMP, Photoshop, etc. -- keep the original dimensions.
# Re-encode to PIC (uses the system palette)
fx pic pack F16C_0.png -p out/FA_2/PALETTE.PAL -o F16C_mod.PIC
# Patch the modified texture back into a copy of the LIB
fx lib patch <CD>/FA_3.LIB F16C_0.PIC F16C_mod.PIC FA_3_mod.LIB
# Place FA_3_mod.LIB in the install directory -- the game prefers it over the CD copy
The re-encoded PIC is format 0 (dense) with an inline 256-color palette. The engine accepts this in place of the original JPEG format.
Text / data mod (mission text, pilot bios)¶
fx lib unpack FA_2.LIB out/FA_2
# Edit out/FA_2/BALTIC.TXT in any text editor, then patch it back
fx lib patch FA_2.LIB BALTIC.TXT out/FA_2/BALTIC.TXT FA_2_mod.LIB
Aircraft stats mod (.PT)¶
fx lib unpack FA_2.LIB out/FA_2
# Export to editable text
fx pt unpack out/FA_2/F16C.PT -o F16C.pt.txt
# Edit F16C.pt.txt -- thrust, max_speed, fuel_capacity, etc.
# Re-encode and patch
fx pt pack F16C.pt.txt -o F16C_mod.PT
fx lib patch FA_2.LIB F16C.PT F16C_mod.PT FA_2_mod.LIB
3D model inspection (.SH)¶
fx lib unpack FA_2.LIB out/FA_2
# Quick stats
fx sh info out/FA_2/F16C.SH
# Export to Wavefront OBJ and open in Blender / MeshLab
fx sh unpack out/FA_2/F16C.SH -o F16C.obj
Mission edit (.M)¶
fx lib unpack FA_2.LIB out/FA_2
fx mission unpack out/FA_2/BALTIC.M -o BALTIC.m.txt
# Edit object positions, weather, side assignments...
fx mission pack BALTIC.m.txt -o BALTIC_mod.M
fx lib patch FA_2.LIB BALTIC.M BALTIC_mod.M FA_2_mod.LIB
Cutscene edit (.SEQ)¶
fx lib unpack FA_2.LIB out/FA_2
fx seq unpack out/FA_2/KDEAD.SEQ -o KDEAD.seq.txt
# Edit timings, bitmap references, sound names...
fx seq pack KDEAD.seq.txt -o KDEAD_mod.SEQ
fx lib patch FA_2.LIB KDEAD.SEQ KDEAD_mod.SEQ FA_2_mod.LIB
Mission briefing text (.MT)¶
.MT files are plain-text companions to .M files and can be edited directly — no
fx command needed. They live alongside the .M in the .LIB.
fx lib unpack FA_2.LIB out/FA_2
# Open out/FA_2/BALTIC.MT in any text editor. Edit section 2 (briefing)
# and sections 3/4 (debrief success/failure), then patch back:
fx lib patch FA_2.LIB BALTIC.MT out/FA_2/BALTIC.MT FA_2_mod.LIB
See formats/M.md for the section and directive syntax.
Aircraft flight model reference data¶
The community has produced G-envelope spreadsheets for 70+ real aircraft
(F-4, F-14, F-15, F-16, F/A-18, F-22, MiG-25, Rafale, Typhoon, and many more),
measuring stall and max speeds in KTAS at altitude breakpoints from sea level to 65,000 ft
across −4 G to +9 G. These map directly to the env section in .PT files.
The Fighters Anthology Resource Center and USNRaptor community archives include spreadsheets
covering dozens of airframes. They use KTAS at altitude breakpoints — convert to .PT
env units as follows:
speed_ft_per_sec = ktas * 1.6878 # 1 knot = 1.6878 ft/s
altitude_ft = altitude_as_read # already in feet
Font mod (.FNT)¶
FA's fonts are compiled x86 inside PE DLLs, but the fx round trip makes
them editable as images (#97):
fx lib extract FA_1.LIB 4X6.FNT— pull the font from the archive.fx fnt unpack 4X6.FNT -o work/— rendersglyph_sheet.png(printable glyphs in a 16-column grid) andmetrics.csv(per-character advance widths and the font height).- Edit
glyph_sheet.pngin any editor — white pixels are set, black are transparent. Keep each glyph inside its cell; widths can be adjusted inmetrics.csv. fx fnt pack 4X6.FNT work/ -o 4X6.FNT— recompiles the glyphs to x86 with the engine's own encoding and rebuilds the function table. The recompiled code must fit the original code region (roughly: similar ink coverage) —packrefuses if it would overrun.fx lib patchthe font back into FA_1.LIB.
An unedited unpack→pack loop reproduces the original file byte-for-byte, so any diff you ship is exactly your edit.
Tips¶
- The game loads flags=0 (uncompressed) LIB entries just as well as flags=4 (compressed).
fx lib patchalways writes uncompressed — no need to re-compress. - Keep image dimensions unchanged. The engine does not resize at load time.
- Pixels are quantized to the nearest palette color on PIC re-encode. Keep source art at 256 colors or less for best fidelity.
- Test mods by placing the modified
.LIBin the install directory. The engine searches there before the CD, so you can override without burning a disc.
Recommended Tools¶
$= paid software. Free alternatives are listed first within each category.
Text editors¶
For SEQ, BRF (.OT/.NT/.PT/.JT/.SEE/.ECM/.GAS), mission text (.MT), and unpacked mission files.
| Tool | Platform | Notes |
|---|---|---|
| VS Code | Win / Mac / Linux | Multi-file search, find/replace across a full LIB unpack |
| Notepad++ | Windows | Lightweight; column editing useful for SEQ time fields |
| Notepad / TextEdit | Windows / macOS | Built-in; sufficient for small edits |
Image editors¶
For PIC textures and CB8 frames (after fx pic unpack / fx cb8 frames). Also for RAW screenshots (fx raw unpack).
| Tool | Platform | Notes |
|---|---|---|
| GIMP | Win / Mac / Linux | Handles indexed-color well; batch scripting via Script-Fu |
| Paint.NET | Windows | Simple and fast for texture touch-ups |
Photoshop $ |
Win / Mac | Industry standard; use 8-bit indexed mode |
Affinity Photo $ |
Win / Mac | One-time purchase; strong alternative to Photoshop |
Audio editors¶
For FA audio files (after fx audio unpack to WAV).
| Tool | Platform | Notes |
|---|---|---|
| Audacity | Win / Mac / Linux | Free; can also import raw PCM directly (File → Import → Raw Data: signed 8-bit, mono) |
Adobe Audition $ |
Win / Mac | Paid; professional mastering and spectral repair |
3D editors¶
For shape inspection (after fx sh unpack to OBJ). Geometry editing requires the FASHion + SketchUp 8 community workflow — see SH.md.
| Tool | Platform | Notes |
|---|---|---|
| Blender | Win / Mac / Linux | Free; best for inspecting and measuring OBJ exports |
| MeshLab | Win / Mac / Linux | Free; lightweight mesh viewer with basic statistics |
| FASHion | Windows | Free (FA-specific, community tool); vertex repositioning only |
| SketchUp 8 | Windows | Free (legacy version required by FASHion plugin) |
3ds Max $ |
Windows | Paid; full mesh editing |
Hex editors¶
For PAL files and binary formats without full ft support (PLT pilot saves, FBC).
| Tool | Platform | Notes |
|---|---|---|
| HxD | Windows | Free; fast and straightforward |
| VS Code + Hex Editor | Win / Mac / Linux | Free; convenient if already using VS Code for text editing |
010 Editor $ |
Win / Mac / Linux | Paid; binary templates enable structured editing once a format is fully mapped |
FA-specific tools¶
| Tool | Notes |
|---|---|
| fx (this toolkit) | Primary CLI for all LIB, PIC, audio, mission, shape, and screenshot operations |
| FATK (DuoSoft 1998) | Original GUI toolkit; free (abandonware). Does not run natively on 64-bit Windows — requires a compatibility layer. Supports pilot editing and project-based LIB management. |
Reference
Format Status Matrix¶
One row per format spec in this directory, generated from each spec's
front-matter and verified against the repository by CI (--check).
See docs/spec-authoring.md for the vocabulary.
| Format | Category | Spec | Gaps | Codec | Commands | Tests | Fixtures | Fuzz | GUI |
|---|---|---|---|---|---|---|---|---|---|
| 11K | audio | complete | — | round-trip (byte-identical) | fx audio |
tests/test_audio.cpp |
syn/real | fuzz/fuzz_audio.cpp |
gui/src/editors/audio_editor.cpp |
| AI | mission | complete | — | round-trip (by design) | fx ai |
tests/test_ai.cpp |
syn/real | fuzz/fuzz_ai.cpp |
gui/src/editors/ai_editor.cpp |
| BI | mission | complete | — | round-trip (by design) | fx bi |
tests/test_ai.cpptests/test_bi.cpp |
syn/real | fuzz/fuzz_bi.cpp |
— |
| BIN | system | complete | — | read-only (by design) | fx bin |
tests/test_bin.cpp |
syn/real | fuzz/fuzz_bin.cpp |
gui/src/editors/bin_editor.cpp |
| BRF | typedef | complete | — | round-trip (byte-identical) | — | tests/test_brf.cpp |
syn | fuzz/fuzz_brf.cpp |
gui/src/editors/brf_editor.cpp |
| CAM | mission | complete | — | read-only (by design) | fx cam |
tests/test_cam.cpp |
syn/real | fuzz/fuzz_cam.cpp |
gui/src/editors/cam_editor.cpp |
| CB8 | video | partial | re-gameplay #56 | round-trip (by design) | fx cb8 |
tests/test_cb8.cpp |
syn/real | fuzz/fuzz_cb8.cpp |
gui/src/editors/cb8_editor.cpp |
| CFG | system | complete | — | round-trip (byte-identical) | fx cfg |
tests/test_cfg.cpp |
syn | fuzz/fuzz_cfg.cpp |
— |
| DAT | system | complete | — | round-trip (byte-identical) | fx dat |
tests/test_dat.cpp |
syn | fuzz/fuzz_dat.cpp |
— |
| DLG | ui-overlay | partial | re-static #54 | read-only (by design) | fx dlg |
tests/test_dlg.cpp |
syn/real | fuzz/fuzz_dlg.cpp |
— |
| ECM | typedef | complete | — | round-trip (byte-identical) | fx ecm |
tests/test_brf.cpptests/test_ot.cpp |
syn/real | fuzz/fuzz_brf.cpp |
gui/src/editors/brf_editor.cpp |
| EFFECT | system | partial | re-static #54 | read-only (by design) | fx effect |
tests/test_effect.cpp |
syn | fuzz/fuzz_effect.cpp |
— |
| ESA | installer | partial | re-static #54 | round-trip (byte-identical) | fx esa |
tests/test_esa.cpp |
syn | fuzz/fuzz_esa.cpp |
— |
| FBC | video | complete | — | round-trip (byte-identical) | fx fbc |
tests/test_fbc.cpp |
syn/real | fuzz/fuzz_fbc.cpp |
gui/src/editors/vdo_editor.cpp |
| FNT | ui-overlay | complete | — | round-trip (byte-identical) | fx fnt |
tests/test_pe.cpptests/test_fnt.cpp |
syn/real | fuzz/fuzz_pe.cppfuzz/fuzz_fnt.cpp |
gui/src/editors/fnt_editor.cpp |
| GAS | typedef | complete | — | round-trip (byte-identical) | fx gas |
tests/test_brf.cpptests/test_ot.cpp |
syn/real | fuzz/fuzz_brf.cpp |
gui/src/editors/brf_editor.cpp |
| HGR | 3d | partial | re-static #54 | read-only (by design) | fx hgr |
tests/test_hgr.cpp |
syn/real | fuzz/fuzz_hgr.cpp |
— |
| HUD | ui-overlay | partial | re-gameplay #56 | round-trip (byte-identical) | fx hud |
tests/test_hud.cpp |
syn/real | fuzz/fuzz_hud.cpp |
gui/src/editors/hud_editor.cppgui/src/editors/overlay_preview.cpp |
| INF | 3d | partial | re-static #54 | round-trip (byte-identical) | fx inf |
tests/test_inf.cpp |
syn | fuzz/fuzz_inf.cpp |
gui/src/editors/inf_editor.cpp |
| JT | typedef | partial | re-static #54 | round-trip (byte-identical) | fx jt |
tests/test_brf.cpptests/test_ot.cpp |
syn/real | fuzz/fuzz_brf.cpp |
gui/src/editors/brf_editor.cpp |
| LAY | 3d | complete | — | round-trip (byte-identical) | fx lay |
tests/test_pe.cpptests/test_lay.cpp |
syn/real | fuzz/fuzz_pe.cppfuzz/fuzz_lay.cpp |
gui/src/editors/lay_editor.cppgui/src/editors/overlay_preview.cpp |
| LIB | archive | partial | re-static #54 | round-trip (byte-identical) | fx lib |
tests/test_ealib.cpptests/test_blast.cpp |
syn/real | fuzz/fuzz_ealib.cppfuzz/fuzz_blast.cpp |
— |
| M | mission | complete | — | round-trip (byte-identical) | fx mission |
tests/test_mission.cpp |
syn/real | fuzz/fuzz_mission.cpp |
gui/src/editors/mission_editor.cpp |
| MC | mission | complete | — | read-only (by design) | fx mc |
tests/test_mc.cpp |
syn/real | fuzz/fuzz_mc.cpp |
— |
| MM | terrain | partial | re-static #54 | round-trip (byte-identical) | fx mmfx mission |
tests/test_mission.cpp |
syn/real | fuzz/fuzz_mission.cpp |
gui/src/editors/mission_editor.cpp |
| MNU | ui-overlay | stub | re-static #54 | read-only (by design) | fx mnu |
tests/test_mnu.cpp |
syn/real | fuzz/fuzz_mnu.cpp |
— |
| MT | mission | complete | — | round-trip (byte-identical) | fx mt |
tests/test_mt.cpp |
syn/real | fuzz/fuzz_mt.cpp |
— |
| MUS | audio | partial | re-static #54 | read-only (by design) | fx mus |
tests/test_mus.cpp |
syn/real | fuzz/fuzz_mus.cpp |
gui/src/editors/mus_editor.cpp |
| NT | typedef | complete | — | round-trip (byte-identical) | fx nt |
tests/test_brf.cpptests/test_ot.cpp |
syn/real | fuzz/fuzz_brf.cpp |
gui/src/editors/brf_editor.cpp |
| OT | typedef | complete | — | round-trip (byte-identical) | fx ot |
tests/test_brf.cpptests/test_ot.cpp |
syn/real | fuzz/fuzz_brf.cpp |
gui/src/editors/brf_editor.cpp |
| P | system | partial | re-gameplay #29 re-gameplay #29 re-gameplay #29 re-gameplay #29 |
round-trip (byte-identical) | fx plt |
tests/test_plt.cpp |
syn | fuzz/fuzz_plt.cpp |
gui/src/editors/plt_editor.cpp |
| PAL | graphics | complete | — | round-trip (byte-identical) | fx pal |
tests/test_pal.cpp |
syn/real | fuzz/fuzz_pal.cpp |
gui/src/editors/pal_editor.cpp |
| PIC | graphics | complete | — | round-trip (byte-identical) | fx pic |
tests/test_pic.cpp |
syn/real | fuzz/fuzz_pic.cpp |
gui/src/editors/pic_editor.cpp |
| PT | typedef | partial | re-static #54 | round-trip (byte-identical) | fx pt |
tests/test_brf.cpptests/test_ot.cpp |
syn/real | fuzz/fuzz_brf.cpp |
gui/src/editors/brf_editor.cpp |
| PTS | ui-overlay | complete | — | read-only (by design) | fx pts |
tests/test_pts.cpp |
syn/real | fuzz/fuzz_pts.cpp |
— |
| RAW | graphics | complete | — | round-trip (byte-identical) | fx raw |
tests/test_raw.cpp |
syn | fuzz/fuzz_raw.cpp |
gui/src/editors/raw_viewer.cpp |
| RGN | installer | complete | — | round-trip (byte-identical) | fx rgn |
tests/test_rgn.cpp |
syn | fuzz/fuzz_rgn.cpp |
— |
| RTP | installer | partial | re-static #54 | read-only (by design) | fx patch |
tests/test_rtpatch.cpp |
syn | fuzz/fuzz_rtpatch.cpp |
— |
| SEE | typedef | partial | re-static #54 | round-trip (byte-identical) | fx see |
tests/test_brf.cpptests/test_ot.cpp |
syn/real | fuzz/fuzz_brf.cpp |
gui/src/editors/brf_editor.cpp |
| SEQ | video | complete | — | round-trip (byte-identical) | fx seq |
tests/test_seq.cpp |
syn/real | fuzz/fuzz_seq.cpp |
gui/src/editors/seq_editor.cpp |
| SH | 3d | partial | re-static #52 | read-only (by design) | fx sh |
tests/test_sh.cpp |
syn/real | fuzz/fuzz_sh.cpp |
gui/src/editors/sh_editor.cppgui/src/editors/sh_scene.cpp |
| SMS | system | complete | — | read-only (by design) | fx sms |
tests/test_sms.cpp |
real | fuzz/fuzz_sms.cpp |
— |
| SSF | installer | complete | — | round-trip (byte-identical) | fx ssffx install |
tests/test_ssf.cpptests/test_install.cpp |
syn | fuzz/fuzz_ssf.cppfuzz/fuzz_install.cpp |
— |
| T2 | terrain | complete | — | round-trip (byte-identical) | fx t2 |
tests/test_t2.cpp |
syn/real | fuzz/fuzz_t2.cpp |
gui/src/editors/terrain_preview.cpp |
| TXT | text | complete | — | round-trip (byte-identical) | fx txt |
tests/test_txt.cpp |
syn/real | fuzz/fuzz_txt.cpp |
gui/src/editors/txt_editor.cpp |
| VDO | video | partial | re-static #55 | read-only (by design) | fx vdo |
tests/test_vdo.cpp |
syn/real | fuzz/fuzz_vdo.cpp |
gui/src/editors/vdo_editor.cpp |
| XMI | audio | partial | re-static #54 | read-only (by design) | fx xmi |
tests/test_xmi.cpp |
syn/real | fuzz/fuzz_xmi.cpp |
gui/src/editors/xmi_editor.cpp |
Reconstruction Matrix¶
Progress of the FA reconstruction programs — the
game-executable program (epic #209)
and the overlay-binary program (epic #247)
— one section per binary, one row per subsystem, generated from the
symbol database and verified against the per-binary Ghidra
inventory export (db/inventory/, local-only — regenerated and checked where the
canonical Ghidra project lives; see db/README.md).
FA.EXE¶
| Subsystem | Range(s) | Funcs named | Ref. globals | Doc | Diagram | Issue | Status |
|---|---|---|---|---|---|---|---|
| Object / entity system & shape selection | 0x462600–0x4646600x4A6EB0–0x4A72400x473A40–0x473DA00x491240–0x491AA0 |
83/83 (100%) | 15 named · 145 waived | doc | ✓ | #210 | complete |
| Renderer & rasterizer (GG/G_) | 0x45DBD0–0x45E4600x497330–0x4993800x4B7900–0x4BA700 |
125/125 (100%) | 3 named · 167 waived | doc | ✓ | #211 | complete |
| Flight model / physics (FM/HARD) | 0x451480–0x454800 |
62/62 (100%) | 0 named · 142 waived | doc | ✓ | #212 | complete |
| HUD / cockpit | 0x405E30–0x40AE50 |
49/49 (100%) | 16 named · 189 waived | doc | ✓ | #213 | complete |
| Weapons — projectiles / seekers / ECM (PROJ) | 0x4C0690–0x4C5D30 |
59/59 (100%) | 8 named · 47 waived | doc | ✓ | #215 | complete |
| AI interpreter (CT) | 0x464C60–0x467110 |
122/122 (100%) | 11 named · 27 waived | doc | ✓ | #216 | complete |
| Wingman / group AI (WNG/GRP) | 0x45E460–0x45FEC0 |
40/40 (100%) | 1 named · 16 waived | doc | ✓ | #217 | complete |
| Campaign / mission / pilot (MAP/CAM/MC/MM/PLT) | 0x421D40–0x421DD00x421DE0–0x4221D00x422230–0x4223000x422350–0x4223800x4224B3–0x4224EE0x42256A–0x42267F0x4226CB–0x4226F00x422828–0x4228510x42297E–0x422A0D0x422A71–0x42B8000x467240–0x4672C00x4674F0–0x4678B00x467E30–0x467F300x468C40–0x4699600x46A57B–0x46A6400x47A510–0x47A5A00x47FAAE–0x47FEC00x4805AC–0x4806900x4809D0–0x480A300x481920–0x4819400x481A7B–0x481B800x483C90–0x484D900x485380–0x4854E00x4856F0–0x4858200x4867D0–0x4868100x486BF0–0x486C600x486DC0–0x486DF0 |
130/130 (100%) | 0 named · 360 waived | doc | ✓ | #218 | complete |
| Network / multiplayer (NET/SER/UDP/MP) | 0x4016C0–0x4023000x402330–0x4023800x4024D0–0x4026400x405360–0x4053D00x4054F0–0x405E300x45D090–0x45DBD00x46BD90–0x46BDE00x46C0A0–0x46C1500x46C28C–0x46C4700x46C680–0x46C9100x46C980–0x46EED00x46EEDF–0x46FDB00x46FF20–0x46FF700x47001C–0x4701500x4701AC–0x4702500x47025C–0x4705600x470780–0x4708B00x4715BC–0x4719E00x471A90–0x471BD00x471BDF–0x4722600x472670–0x472A900x4735D0–0x4736800x499F70–0x499F900x49A000–0x49A3800x49A3E0–0x49A5200x49A660–0x49A8500x49A9B0–0x49AD700x49AF30–0x49B1D00x4AC180–0x4AC3E00x4AC480–0x4AC510 |
117/117 (100%) | 0 named · 186 waived | doc | ✓ | #219 | complete |
| Sound / music (incl. WAIL32) | 0x4328B0–0x435C60 |
53/53 (100%) | 5 named · 179 waived | doc | ✓ | #220 | complete |
| Terrain (T_) | 0x4A7310–0x4ABBE20x4C5D30–0x4C60E8 |
85/85 (100%) | 1 named · 113 waived | doc | ✓ | #221 | complete |
| Collision (COL) | 0x42B800–0x42E680 |
21/21 (100%) | 38 named · 6 waived | doc | ✓ | #222 | complete |
| Memory & resource managers (MM/RM) | 0x435C60–0x43631C0x4A67F0–0x4A6E46 |
50/50 (100%) | 12 named · 22 waived | doc | ✓ | #223 | complete |
| Input — joystick / serial / modem | 0x494270–0x494BB00x499CF0–0x499F700x49B1D0–0x49D1B0 |
20/20 (100%) | 0 named · 61 waived | doc | ✓ | #224 | complete |
| Core shell / menu / dialog UI | 0x40B8A0–0x40BA100x40BC20–0x40C2900x40C410–0x40D7A00x42E680–0x42E7200x42E9A0–0x42F2E00x432F80–0x432F8B0x433170–0x4331800x47F0B0–0x47F1000x47FA30–0x47FA500x487A3A–0x4891700x4891A0–0x48D2B00x4A08A0–0x4A0FE00x4A26F0–0x4A2A30 |
136/136 (100%) | 0 named · 124 waived | doc | ✓ | #225 | complete |
| Startup / Phar Lap DOS extender / config | 0x4D715A–0x4E8A2F |
338/338 (100%) | 47 named · 174 waived | doc | ✓ | #226 | complete |
| Video decode (FMV/Cobra) | 0x456300–0x45CDA0 |
23/23 (100%) | 0 named · 0 waived | doc | ✓ | #227 | complete |
| 3D render core / SH interpreter (GR) | 0x4CD588–0x4D6C00 |
163/163 (100%) | 0 named · 349 waived | doc | ✓ | #228 | complete |
| .SEQ scripted-cutscene / sequence player (SEQ) | 0x444F70–0x446D900x446F10–0x4471E0 |
40/40 (100%) | 25 named · 37 waived | doc | ✓ | #240 | complete |
| View / camera & replay (VIEW) | 0x40D7A0–0x40F6B0 |
19/19 (100%) | 5 named · 24 waived | doc | ✓ | #257 | complete |
FA.EXE totals: 20/20 subsystems complete; 1735/1735 in-scope functions named; 2555/4721 referenced globals resolved.
WAIL32.DLL¶
| Subsystem | Range(s) | Funcs named | Ref. globals | Doc | Diagram | Issue | Status |
|---|---|---|---|---|---|---|---|
| WAIL32.DLL — Miles Sound System (AIL) audio driver | 0x20001000–0x20019E00 |
503/503 (100%) | 0 named · 577 waived | doc | ✓ | #253 | complete |
WAIL32.DLL totals: 1/1 subsystems complete; 503/503 in-scope functions named; 577/756 referenced globals resolved.
IP.EXE¶
| Subsystem | Range(s) | Funcs named | Ref. globals | Doc | Diagram | Issue | Status |
|---|---|---|---|---|---|---|---|
| IP.EXE — EA system-info & tech-support tool (MFC) | 0x401000–0x4BD800 |
1805/1805 (100%) | 0 named · 1339 waived | doc | ✓ | #254 | complete |
IP.EXE totals: 1/1 subsystems complete; 1805/1805 in-scope functions named; 1339/1737 referenced globals resolved.
CDRVDL32.DLL¶
| Subsystem | Range(s) | Funcs named | Ref. globals | Doc | Diagram | Issue | Status |
|---|---|---|---|---|---|---|---|
| CDRVDL32.DLL — Cdrv RS-232 serial comms driver | 0x10001000–0x10010000 |
104/104 (100%) | 0 named · 129 waived | doc | ✓ | #255 | complete |
CDRVDL32.DLL totals: 1/1 subsystems complete; 104/104 in-scope functions named; 129/175 referenced globals resolved.
CDRVHF32.DLL¶
| Subsystem | Range(s) | Funcs named | Ref. globals | Doc | Diagram | Issue | Status |
|---|---|---|---|---|---|---|---|
| CDRVHF32.DLL — Cdrv Hayes-modem comms driver | 0x10001000–0x10010000 |
154/154 (100%) | 0 named · 114 waived | doc | ✓ | #255 | complete |
CDRVHF32.DLL totals: 1/1 subsystems complete; 154/154 in-scope functions named; 114/150 referenced globals resolved.
CDRVXF32.DLL¶
| Subsystem | Range(s) | Funcs named | Ref. globals | Doc | Diagram | Issue | Status |
|---|---|---|---|---|---|---|---|
| CDRVXF32.DLL — Cdrv file-transfer comms driver | 0x10001000–0x10010000 |
121/121 (100%) | 0 named · 171 waived | doc | ✓ | #255 | complete |
CDRVXF32.DLL totals: 1/1 subsystems complete; 121/121 in-scope functions named; 171/322 referenced globals resolved.
COMMSC32.DLL¶
| Subsystem | Range(s) | Funcs named | Ref. globals | Doc | Diagram | Issue | Status |
|---|---|---|---|---|---|---|---|
| COMMSC32.DLL — Cdrv comms terminal-screen service | 0x10001000–0x10010000 |
60/60 (100%) | 0 named · 109 waived | doc | ✓ | #255 | complete |
COMMSC32.DLL totals: 1/1 subsystems complete; 60/60 in-scope functions named; 109/153 referenced globals resolved.
MSAPI.DLL¶
| Subsystem | Range(s) | Funcs named | Ref. globals | Doc | Diagram | Issue | Status |
|---|---|---|---|---|---|---|---|
| Matchmaking / internet-play client (MSAPI) | 0x100011E0–0x10002AB0 |
25/25 (100%) | 12 named · 23 waived | doc | ✓ | #275 | complete |
MSAPI.DLL totals: 1/1 subsystems complete; 25/25 in-scope functions named; 35/36 referenced globals resolved.
Program totals (all binaries): 27/27 subsystems complete; 4507/4507 in-scope functions named; 5029/8050 referenced globals resolved.
Symbol Map — Organized Reference¶
FA.SMS ships with Jane's Fighters Anthology and contains 3,829 MSVC C++ mangled symbols with virtual addresses spanning 0x00401000–0x005937E0. This document organizes them by address range into functional subsystems and highlights format-related entry points.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; the file itself is specified in formats/SMS.md. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
Reconstruction program: for subsystems the game-executable reconstruction program (epic #209) has completed, the machine-readable symbol database and its per-subsystem doc are canonical (they include recovered names beyond FA.SMS, mechanically checked against the Ghidra project). This page stays the FA.SMS overview and the reference for ranges not yet migrated.
Summary Table¶
| Subsystem | Address Range | Approx. Symbol Count |
|---|---|---|
| Network (NET/MP/SER) | 0x401000–0x409000 | ~60 |
| HUD / cockpit display | 0x405E30–0x40AE50 | ~40 |
| Core shell / menu | 0x40AE50–0x421C70 | ~90 |
| Sound / music | 0x432920–0x435F80 | ~70 |
| Memory manager (MM) | 0x435C60–0x436320 | ~35 |
| Campaign map (MAP/CAM) | 0x421C70–0x42B800 | ~25 |
| Collision (COL) | 0x42B800–0x42E690 | ~20 |
| Flight model (FM/HARD) | 0x451480–0x454800 | ~80 |
| Video decode (FMV/Cobra) | 0x456300–0x45D090 | ~45 |
| Network (UDP/PKT layer) | 0x45D090–0x45DBD0 | ~30 |
| Graphics low-level (GG/G_) | 0x45DBD0–0x499380 | ~130 |
| Wingman/Group AI (WNG/GRP) | 0x45E460–0x460FB0 | ~50 |
| Object system (OBJ/chain) | 0x462600–0x464C80 | ~40 |
| AI interpreter (CT) | 0x464C80–0x467110 | ~120 |
| Pilot / mission / campaign | 0x467110–0x490000 | ~180 |
| Joystick / serial / modem | 0x494270–0x4AC510 | ~110 |
| Terrain renderer (T_) | 0x4A6E50–0x4C5D70 | ~90 |
| Projectile / weapons (PROJ) | 0x4C0690–0x4C5D30 | ~55 |
| 3D renderer (GR/render) | 0x4C5D70–0x4D5C00 | ~100 |
| Airport / carrier (AP) | 0x4BA750–0x4BEE60 | ~40 |
| World render / palette (WR) | 0x4B3010–0x4B4B30 | ~30 |
| Multiplayer protocol (MP) | 0x46ADE0–0x473680 | ~95 |
| Dialog / UI shell | 0x487A3A–0x48D200 | ~70 |
| SAY / voice callout | 0x48D2B0–0x491240 | ~20 |
| CRT / Win32 imports | 0x4D6F5C–0x4E8B66 | ~300 |
| Data globals / BSS | 0x4EB5F4–0x593800 | ~300 |
Subsystem symbol registry¶
The per-subsystem tables below are generated from the symbol database so they cannot drift from the Ghidra project. Each row is a named symbol; the full record (including waived interiors) is the linked CSV, and the narrative for each subsystem is on its own page. Progress: reconstruction matrix.
Generated from db/symbols/; each subsystem's detailed prose lives on its own page.
Binary: FA.EXE
Network / multiplayer (NET/SER/UDP/MP)¶
network.csv · page — 107 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004016C0 |
NET_SlaveInit |
sms | client: open connection to master at NET_ADDRESS; registers PLAYER_ACTION/NET_CONNECTED_STATE callbacks |
0x00401780 |
NET_SlaveShutdown |
sms | client leave/teardown |
0x004017B0 |
NET_RequestPlayerList |
sms | query lobby for player list (async) |
0x00401850 |
NET_CancelPlayerList |
sms | cancel pending player-list query |
0x004019A0 |
player_list_process_pkt |
sms | dispatch player-list-query reply packets |
0x00401A60 |
NETSlaveConnect |
re | slave connect helper: proto_ptr->open (vtbl+0x56), register slave_events (0x401B20) via socket_add_state_func, seed socket_state addr fields; net_set_often_state(5). Body @0x401A60 cited |
0x00401CD0 |
handle_slave_connection_failed |
sms | retry-or-fail: emits 'Connection to %s failed', pkt_send_error(6), net_set_often_state(0) |
0x00401E30 |
NETSlaveLostConn |
re | slave lost-connection notifier: 'Lost connection to %s', socket_close, net_set_often_state(0). Body @0x401E30 |
0x00401EB0 |
slave_process_pkt |
sms | slave lobby FSM: type 3=can-i-play reply,4=player_list,7=err,8=new_player,0xB/0xC=ready,0xD=play_game,0x11/0x12=sync,0x13=lost,0x16=msg,0x1B=connected |
0x00402330 |
NETArmKeepalive |
re | arm socket send timer (state+8 = timerTicks+0x400; state+4 = 4 unless already ready). Body @0x402330 |
0x00402360 |
NETResetTimer |
re | reset socket send timer to now (state+8 = timerTicks; state+4 = 4). Body @0x402360 |
0x004024D0 |
NETProcessPlayerList |
re | process NET_PLAYER_LIST: find self via NETIsAddrLocal, net_add_self, connect to every other listed peer (mesh) via proto vtbl+0x56 + socket_add_state_func(...,0x401B20); if all ready -> pkt_send_ready. Called from slave_process_pkt case 4. Body @0x4024D0 |
0x00405360 |
NETWaitMasterScreen |
re | slave: spin polling MPReceive/MPCheckDisconnect while _masterNextScreen==-1, then adopt it as _curScreen (thisComputer>0). Body @0x405360 |
0x004053A0 |
NETApplyMasterScreen |
re | slave: apply pending _masterNextScreen to _curScreen once (non-blocking). Body @0x4053A0 |
0x004054F0 |
netDialogAppIO |
sms | net-config dialog appIO callback: 0x65=CN_NewPrint,0x66/67=info box,0x68=yes/no; else poll key/mouse |
0x00405CD0 |
NETFormatIP |
re | format IP address as '%d.%d.%d.%d' via Sprintf (s__d__d__d__d_004EBCFC). Body @0x405CD0 |
0x00405D10 |
ip2long |
sms | parse dotted-quad string -> packed u32 (strchr('.')+atoi x4) |
0x00405DF0 |
RunNetConfigurationScreen |
sms | net config screen entry; calls doConfigurationScreen(1|4) |
0x0045D090 |
pkt_send_can_i_play |
sms | client->host join request |
0x0045D120 |
pkt_send_can_i_play_player |
sms | join request naming a player slot |
0x0045D1B0 |
pkt_send_sync |
sms | send sync request |
0x0045D1C0 |
pkt_send_type |
sms | send a bare typed packet to peer index |
0x0045D200 |
pkt_sock_send_type |
sms | send bare typed packet to a socket |
0x0045D240 |
pkt_sock_send_message |
sms | send text message packet to a socket |
0x0045D280 |
pkt_build_message |
sms | fill NET_PKT message body |
0x0045D2D0 |
pkt_send_you_can_play |
sms | host->client accept |
0x0045D320 |
pkt_send_ready |
sms | ready handshake |
0x0045D360 |
pkt_send_error |
sms | send error code to peer |
0x0045D3A0 |
pkt_send_new_player |
sms | announce new player to peer |
0x0045D440 |
pkt_sock_send_new_player |
sms | announce new player over socket |
0x0045D580 |
pkt_sock_send_lost_player |
sms | announce lost player over socket |
0x0045D700 |
pkt_sock_send_error |
sms | send error over socket |
0x0045D760 |
pkt_build_sync_reply |
sms | fill sync-reply header |
0x0045D780 |
pkt_build_play_game |
sms | fill play-game (start) header |
0x0045D810 |
pkt_send_player_info |
sms | send NET_PLAYER_LIST entry |
0x0045D890 |
pkt_build_player_info |
sms | serialize NET_PLAYER_LIST into NET_PKT |
0x0045D940 |
pkt_sock_send |
sms | low-level: send NET_PKT to one socket |
0x0045D970 |
pkt_send |
sms | low-level: send NET_PKT to peer index |
0x0045DA10 |
pkt_set_header |
sms | fill NET_PKT header (type/len) |
0x0045DA30 |
pkt_queue_write |
re | append payload to a socket's output ring (state+0x2C86 head,+0x2C8E cap,+0x2C92 count,+0x2C96 busy); flush via net_write_output_q when full; socket_build_write_fds. Body @0x45DA30 |
0x0045DB00 |
pkt_sock_read |
sms | read one NET_PKT from a socket |
0x0046C0A0 |
MPEnqueue |
re | core outbound primitive: enqueue param_3 bytes to peers matching param_1 (peer id / -1 all-others / -2 all) via MP_WriteAvail/MP_Write, gated by MP_Info connected_mask +0x158; stamps DAT_00546E30 last-send. Body @0x46C0A0 (docs: 'packet enqueue helper') |
0x0046C680 |
MPInterpPosAxis |
re | interpolate one position axis from packet tick delta (uses MPUpdateInterval). Body @0x46C680 |
0x0046C780 |
MPUpdateInterval |
re | per-entity net update interval (LOD): class _cg==6/==4, distance from last-sent (+0x8C/8E/90 vs +0x1D/1F/21), _slowComm, CloseToAnything. Body @0x46C780 |
0x0046C860 |
MPInterpAngleAxis |
re | interpolate one angle axis (uses MPWrapAngle). Body @0x46C860 |
0x0046C980 |
MPReceive |
sms | per-frame inbound dispatcher (entry; body=FUN_0046C98F). Keepalive/timeout scan + 0x10-0x51 dispatch + MP_Often tail |
0x0046EC40 |
MPRelToAbsTime |
re | resolve relative packet timestamp to absolute (+currentT; skips sentinels 0/0x7FFF). Body @0x46EC40 |
0x0046EC60 |
MPResolveAlias |
re | map network alias <-> local object id via entity +0x74; walks _objPtrs[1.._nextObjId]. Body @0x46EC60 (the OBJ/net bridge) |
0x0046ECD0 |
MPReadAlloc |
re | MMAllocPtr(n) then MP_Read n bytes from peer 0 (bulk payloads: file/waypoint sync). Body @0x46ECD0 |
0x0046ED10 |
MPDecodeState16 |
re | decode packed pos(+angle) delta scaled by hdr+3 into int[3]/short[3] (packet 0x16). Body @0x46ED10 |
0x0046EDB0 |
MPDecodePos |
re | decode packed position delta bytes*0x1000 into int[3] (packet 0x14/0x15). Body @0x46EDB0 |
0x0046EE00 |
MPGetType |
re | peek/pull next packet type byte from peer (MP_PeekByte/MP_Read); -0x100 on EOF. Body @0x46EE00 (docs cite as FUN_0046EE00) |
0x0046EE40 |
MPReadPayload |
re | read param_3 bytes iff fully available (MP_ReadAvail then MP_Read). Body @0x46EE40 |
0x0046EE90 |
MPClearDeadStatus |
re | zero _mpStatus[peer] for peers no longer in MP_Info connected_mask. Body @0x46EE90 |
0x0046FA40 |
MPAbsToRelTime |
re | encode absolute time to relative packet timestamp (-currentT; skips sentinels). Body @0x46FA40 |
0x0046FA60 |
MPEncodeState14 |
re | encode packet 0x14: quantized pos delta (auto-exponent) + BAM angle delta /0xB6, writes alias +0x74. Body @0x46FA60 |
0x0046FBF0 |
MPEncodeState15 |
re | encode packet 0x15: small position-only delta (>>0xC). Body @0x46FBF0 |
0x0046FD50 |
MPSendSyncOnce |
re | broadcast one 0x10 sync byte once per session (guard DAT_004F78C8) via MPEnqueue(-1). Body @0x46FD50 |
0x0046FF20 |
MPSendScenarioEndTime |
re | broadcast packet 0x50 (_endScenarioSetTime - _currentTime) via MPEnqueue(-1). Body @0x46FF20 |
0x00470780 |
MPMsgRemapAliases |
re | remap object ids embedded in a T_MSG to/from net aliases (+8=0x4000/-0x8000/-1, sub-type +10) via MPResolveAlias. Body @0x470780 |
0x00471880 |
MPChatChecksum |
re | checksum of CHAT edit-line + all chat lines (DAT_00546EA0 stride 0x79 x DAT_00546DD4) for change/anti-cheat detection. Body @0x471880 |
0x004718F0 |
MPDrawStatusLine |
re | truncate string to width + G_ColorPrint (MP status/chat draw helper). Body @0x4718F0 |
0x00471A90 |
MPWaitStatus |
re | modal loop: poll MPReceive/MPCheckDisconnect until all peers reach status (or key/mouse abort); master uses MPStatusToDrawSet, slave MPStatusSet. Body @0x471A90 (docs: wait-for-everyone-status) |
0x00471B80 |
MPAllPeersAtStatus |
re | test whether every connected peer's _mpStatus == param_1. Body @0x471B80 |
0x00471FA0 |
MPAssignPlanePlayers |
re | per-plane helper in MPAssignPlayers: for obj class 4 w/ flag, iterate DAT_00547324 player table. Body @0x471FA0; called from MPAssignPlayers |
0x00472130 |
MPBuildSpawnPayload |
re | apply spawn position offset (+0x3E800/+0x1F400) and build up-to-500-byte payload; called from MPAssignPlayers. Body @0x472130 |
0x00472670 |
MPRevive |
re | apply player revive/respawn; entry (12B) into body FUN_0047267C. Called from MPReceive packet 0x30 (docs: 'increments _playerRevives[peer]') and MPKey. Body @0x472670 |
0x004735D0 |
MPChatStore |
re | append incoming chat/SAY message to on-screen buffer DAT_00546EA0 (6 lines x 0x79, count DAT_00546DD4); shifts when full. Body @0x4735D0; called from MPReceive 0x1A / MPKey |
0x004874C0 |
sapopensocket |
sms | SAP open socket - IPX Service Advertising Protocol; name-dispatched (label-only in a clean rebuild) |
0x00493780 |
RunIPXOptionsDialog |
sms | IPX/SPX network options dialog (switch over frame types); label-only in a clean rebuild |
0x00496F40 |
spxinit |
sms | SPX transport init - enumerate IPX adapters into a NET_ADDRESS_LIST |
0x00497000 |
spxinit2 |
sms | SPX secondary init from NET_PROTOCOL/CN_INFO |
0x00497010 |
spxlisten |
sms | open+bind an IPX socket and start SPX listening (backlog 5) |
0x004970C0 |
spxopensocket |
re | open an SPX socket - socket(6) then SPX ioctl 0x8004667e; sibling of sapopensocket |
0x00497150 |
spxconnect |
sms | SPX connect to a NET_ADDRESS |
0x004971D0 |
convert_addr_ipx2usnf |
sms | convert an IPX sockaddr to the engine NET_ADDRESS |
0x00497210 |
convert_addr_usnf2ipx |
sms | convert an engine NET_ADDRESS to an IPX sockaddr |
0x00497290 |
spxbuildaddress |
sms | build a NET_ADDRESS for an SPX peer from NET_PROTOCOL/CN_INFO |
0x00499F70 |
setPacketInfo |
sms | write SERIAL_PACKET type (low2 bits of byte0 | 0xfc) and seq (byte3) |
0x0049A000 |
packetCRC |
sms | extract stored CRC (byte1 [+byte10 for type0/3]) by packet type |
0x0049A040 |
verifyPacketCRC |
sms | packetCRC()==computePacketCRC() |
0x0049A070 |
assignPacketCRC |
sms | store computed CRC into byte1 (+byte10 for type0/3) |
0x0049A0A0 |
SER_EnterCriticalCodeBackground |
sms | spin-acquire Ctrl busy flag (DAT_00570bb4) under critical section w/ Sleep(0) |
0x0049A100 |
SER_LeaveCriticalCodeBackground |
sms | clear Ctrl busy flag under critical section |
0x0049A120 |
SER_CheckDisconnect |
sms | detect link loss: modem carrier (IsCarrierDetect) / 8s idle timeout; on loss SER_ShutdownLowLevel + set disconnect flags |
0x0049A1B0 |
SER_GetOutholdingLimit |
sms | transmit-buffer flow control: BytesInTransmitBuffer -> out-holding budget (250-byte / 0xfa window) |
0x0049A3E0 |
updateQueueHead |
sms | head = seq % capacity; returns seq / capacity (wrap count) |
0x0049A400 |
insertQueue |
sms | copy wrapper (0xc dwords) into slot (seq%cap)*0x30; mark valid (+0x10=1); bump count |
0x0049A460 |
overwriteQueue |
sms | overwrite slot by wrapper seq without count bump (history record) |
0x0049A4A0 |
retrieveFromQueue |
sms | copy out slot at (idx%cap)*0x30 |
0x0049A4D0 |
fetchFromQueueTail |
sms | pop tail slot then zero it; advance tail; returns wrap count |
0x0049A660 |
SER_InitializeLowLevel |
sms | init control struct + 3 queues: InQueue(1024)/OutQueue(64)/HistoryQueue(256) with their packet buffers |
0x0049A6B0 |
SER_ShutdownLowLevel |
sms | mark link inactive (DAT_00570cc5=0); set player-drop mask; clear connected flag |
0x0049A700 |
strToCom |
sms | map "COM1".."COM8" (strcmpi vs DAT_005015d8..5015a0) -> 0..7; -1 if none |
0x0049A7D0 |
MOD_InitPortAndModem |
sms | SER_Initialize1/2 then ModemAttention/SetPortCharacteristics/ModemInit on port handle DAT_00570dcc |
0x0049A9B0 |
MOD_FindModemAndInitPCMCIA |
sms | enumerate Enum\PCMCIA for Class=modem PORTNAME=COMx; strToCom; MOD_InitPortAndModem |
0x0049AC00 |
MOD_WaitForCall |
sms | answer mode: ModemAnswerMode + poll IsRing / RX buffer for RING; ModemWaitForCall; appIO "Ring..." |
0x0049AD00 |
MOD_Initialize1 |
sms | dispatch: explicit COM (CN_INFO+0xbc!=8 -> +0x64) else auto FindModemAndInit then PCMCIA; serIO(0x19) on fail |
0x0049AF30 |
MOD_InitializeAndConnect |
sms | MOD_Initialize1 -> MOD_DoConnect -> SER_Initialize2_5/3/4/5 handshake; SER_Shutdown on any failure |
0x0049AFF0 |
MOD_Initialize |
sms | top-level modem entry: capture appIO (CN_INFO+0xdac); MOD_InitializeAndConnect; set connection type DAT_00500304=2; carrier-detect debounce |
0x0049B0D0 |
MOD_Shutdown |
sms | SER_Shutdown1 + Sleep + ModemHangup/AnswerMode + SER_Shutdown2/3 |
0x004AC180 |
SER_SendBytes |
sms | append bytes to holding buffer DAT_00570bc2; debit out-holding budget DAT_00570bba |
0x004AC1D0 |
SER_SendHoldingBuffer |
sms | flush holding buffer via ser_rs232_putpacket; on error set flag + SER_ShutdownLowLevel |
0x004AC210 |
SER_OkToSendPacket |
sms | budget check (>0x17=23 bytes free); set pending flag DAT_00570edc |
0x004AC230 |
SER_SendPacket |
sms | per-type window check; stamp ack byte; assignPacketCRC; SER_SendBytes 0x18 bytes; bump per-type tx counters |
0x004AC2E0 |
SER_SendRequests |
sms | scan InQueue for gaps; send retransmit-request (type 3) via setPacketInfo+SER_SendPacket |
0x004AC480 |
SER_SendStatus |
sms | send status/ACK packet (type0) carrying last-tx seq + per-player state |
HUD / cockpit¶
hud.csv · page — 42 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00405E30 |
HUDInitMessages |
sms | |
0x00405E50 |
HUDDrawMessages |
sms | |
0x00405F50 |
HUDMessage |
sms | |
0x00406010 |
HUDReprintMessages |
sms | |
0x00406040 |
HUDInit |
sms | |
0x00406920 |
HUDRevive |
sms | |
0x00406950 |
HUDShutdown |
sms | |
0x00406A50 |
HUDDraw |
sms | |
0x004075D0 |
HUDDrawTargetView |
re | render a 3D shape into the HUD bitmap (_T_Make/_T_Render on _hudShape), flip and blit; target/padlock silhouette or combining-glass image |
0x004077B0 |
HUDSetWarning |
sms | |
0x004078B0 |
HUDDrawWarning |
re | draw the blinking warning string set by HUDSetWarning (STALL / LOW FUEL / ...) while unexpired |
0x00407930 |
HUDDrawConfigFlags |
re | stacked gear/flaps/speedbrake/hook annunciators gated by entity config bits (DAT_0050cfef) |
0x00407A00 |
HUDDrawGLoadThrottle |
re | lower data block: G-load, throttle (THR AFT / THR %d%%) and thrust-vector line |
0x00407B60 |
HUDDrawHeading |
sms | |
0x00407EC0 |
HUDSpeedToY |
sms | |
0x00407EE0 |
HUDDrawSpeed |
sms | |
0x00408400 |
HUDAltToY |
sms | |
0x00408420 |
HUDDrawAlt |
sms | |
0x00408930 |
InitScreenMove |
sms | |
0x004089A0 |
HUDDrawPitchLadder |
re | climb/dive pitch ladder: rotate the pitch-bar table by roll, position vs waterline, dashed below / solid above horizon |
0x00408C80 |
HUDDrawLeadCaret |
re | lag/lead aim caret at the padlock target using a time-lagged sample; SymFont glyph bucketed by range |
0x00408E20 |
HUDDrawHVel |
sms | |
0x00409030 |
HUDDrawWeaponInfo |
sms | |
0x004092D0 |
HUDDrawRangeInfo |
sms | |
0x00409760 |
HUDDrawBombFall |
re | CCIP bomb fall line/pipper from the ballistic solution (PROJMakeBombEq/PROJBombPos) |
0x00409910 |
HUDDrawGunReticle |
re | gun aiming circle + bearing tick + vertical range tape with weapon/lock/target markers |
0x00409BF0 |
HUDDrawApproach |
re | ILS/carrier glideslope box in landing submode (APApproachPath/CheckLandingParms) |
0x00409F30 |
HUDDrawTargetBox |
re | target-designator box over the padlock target (PROJLock tone/lock, GRTo2d projection, IFF glyph) |
0x0040A450 |
HUDSquawk |
sms | |
0x0040A530 |
HUDFindNearest |
sms | |
0x0040A6C0 |
HUDDrawTargetLabels |
re | name tags over visible targets; player's current target in a distinct color |
0x0040A7F0 |
HUDDrawContacts |
re | radar/IR sensor contacts (CPGetContact), SymFont glyph per contact, locked one highlighted |
0x0040AAC0 |
HUDBrightness |
sms | |
0x0040AB10 |
HUDSetFont |
sms | |
0x0040AB30 |
HUDSetSymFont |
sms | |
0x0040AB50 |
HUDSetWinFont |
sms | |
0x0040AB70 |
HUDSetDisrupt |
sms | |
0x0040ABB0 |
HUDDrawDisrupt |
sms | |
0x0040AC80 |
HUDSetStability |
sms | |
0x0040ACE0 |
HUDDrawStability |
sms | |
0x0040AD40 |
ComputeBombPosition |
sms | |
0x0040AE40 |
HUDHasFlaps |
sms |
Core shell / menu / dialog UI¶
shell-ui.csv · page — 136 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x0040B8A0 |
MouseLoadPtr |
sms | load per-screen mouse pointer bitmap (PLANE.C etc.); reads _curScreen/_menuResolution |
0x0040BC20 |
MaybeCampaignMenu |
sms | conditionally overlay the campaign action bar (MAINMENU.MNU) on a screen |
0x0040BD00 |
MaybeCampaignMenu2 |
sms | tail variant of MaybeCampaignMenu |
0x0040BD30 |
MenuStartUp |
sms | build+show a menu bar from a .MNU name; calls ShellSetup, clears _menuSelecting |
0x0040BF40 |
MenuInstallRemap |
re | install a palette color-remap table into a G-remap slot (0x114-0x118); called 5x by MenuCreateRemaps |
0x0040BF60 |
MenuMeasureItemWidth |
re | measure widest menu item label via G_ColorStringWidth over the item linked list (+0x12 text, +submenu via FUN_0040c130) |
0x0040C130 |
MenuItemSubString |
re | skip a menu label's leading marker bytes (>1) and return the submenu/secondary substring, or NULL |
0x0040C160 |
MenuLoadFont |
re | load MENUFONT.PIC (640) or MFONT320.PIC (320) per _menuResolution into DAT_004ec21c |
0x0040C1A0 |
MenuRemoveItem |
sms | remove a menu item by packed G-index |
0x0040C1D0 |
MenuLinkTerminate |
re | walk _firstMenu linked list to the tail and null-terminate it |
0x0040C1F0 |
MenuCreateRemaps |
sms | build 5 shadow palette-remaps (menuShadow1..5 at 0x5221f0/0x5220f0/0x521ef0/0x521df0/0x521ff0, %=60/66/74/84/96) via FUN_0040d4e0; install slots 0x114-0x118 via FUN_0040bf40 |
0x0040C410 |
MenuDrawBar |
sms | save region under bar (menuBarSave) then draw the top menu bar from _firstMenu list |
0x0040C4F0 |
MenuUpdate |
sms | per-frame menu poll: ShellMousePos, hover/hit-test, returns selected G-index; skips mouse poll while _dialogOn |
0x0040C5A0 |
MenuCurrentIndex |
re | close open sub-item/menu highlights, then compute the packed (menu<<8 | item) index of the current selection |
0x0040C670 |
MenuMouseSelect |
re | hit-test mouse over bar entries/items via MouseInBox; update _curMenu/_curSubMenu/_curItem, open/close dropdowns |
0x0040C990 |
MenuDrawDropdown |
re | draw an opened submenu: save background (FUN_0040cea0/cf40), MenuSteelRect frame, item rows |
0x0040CB10 |
MenuClearItemInvert |
re | restore the current item's inverted-highlight brush (pair of FUN_0040cb80); called by MenuCurrentIndex |
0x0040CB40 |
MenuCloseSubMenu |
re | close/restore the open sub-menu highlight; called by MenuCurrentIndex and MenuMouseSelect |
0x0040CB80 |
MenuInvertItem |
re | toggle the highlight brush over _curItem (invertItemBrush: AllocBitmap+BlitToBrush+URect2, or blit back+free) |
0x0040CC50 |
MenuInvertSubItem |
re | toggle the highlight brush over _curSubItem (invertSubItem) |
0x0040CD20 |
MenuInvertBar |
re | toggle the highlight brush over the _curMenu bar entry (invertMenuBrush) |
0x0040CDE0 |
CheckItem |
sms | set/clear checkmark on a menu item (via FUN_0040ce00 index lookup) |
0x0040CE00 |
MenuItemByIndex |
re | resolve a packed index (hi=menu#, lo=item#) to a MENU_ITEM* by walking _firstMenu; used by Check/Enable/DisableItem |
0x0040CE70 |
EnableItem |
sms | enable a menu item by index |
0x0040CE80 |
DisableItem |
sms | disable/dim a menu item by index |
0x0040CEA0 |
MenuSaveBackground |
re | save the screen region under a submenu into menuSaveBrush (+shadowWidth/Height margin) |
0x0040CF00 |
MenuRestoreBackground |
re | blit menuSaveBrush back to screen and free it |
0x0040CF40 |
MenuSaveBackground2 |
re | second save-background path (item flag==0 branch of MenuDrawDropdown); pairs with FUN_0040cfa0 |
0x0040CFA0 |
MenuRestoreBackground2 |
re | restore pair for FUN_0040cf40 |
0x0040CFE0 |
ShadowBox |
sms | 11-byte entry; drop-shadow frame around a rect. Worker is FUN_0040cfeb (tail) |
0x0040CFEB |
ShadowBoxDraw |
re | 403-byte worker behind ShadowBox@0x40CFE0: renders the 8-piece drop-shadow frame (shadowUR/LL/LR/H/V handles) |
0x0040D180 |
MenuSteelRect |
sms | draw the brushed-steel panel rectangle (uses steelHandle, FUN_0040d390 pattern) |
0x0040D390 |
MenuSteelPattern |
re | builds an i*i square table and tiles the brushed-steel gradient (DAT_004ec260) used by MenuSteelRect |
0x0040D4E0 |
MenuBuildRemap |
re | build a palette remap table scaling _curPalette RGB by param%/100; confirmed by MenuCreateRemaps callsites |
0x0040D5F0 |
PushShellAlloc |
sms | push MM alloc-id scope for shell allocations (paired with PopShellAlloc) |
0x0040D620 |
PopShellAlloc |
sms | pop shell MM alloc scope |
0x0040D640 |
ShellShowMouse |
sms | show software mouse cursor (ref counts via mouseShown) |
0x0040D6B0 |
ShellHideMouse |
sms | hide software mouse cursor |
0x0040D6E0 |
ShellMousePos |
sms | sample mouse: writes _shellMousePos, _shellButtons, _shellEvent |
0x0040D790 |
MouseInBox |
sms | hit-test _shellMousePos against a BOX; used by menu bar/item hover |
0x0042E680 |
QuickMultiButton |
sms | quick-mission wizard: store button value into DAT_00537360[i]/DAT_00537260[i]; just past collision's range |
0x0042E690 |
QuickMultiButtonText |
sms | set a quick-mission wizard button's label |
0x0042E9A0 |
QuickMission |
sms | quick-mission creator loop (drives the 24 QUICKB*.DLG wizard steps) |
0x0047F0B0 |
InTextButton |
sms | hit-test mouse against the _buttonBoxes[_lastButton] MNU text-button array; in campaign seed range |
0x0047FA30 |
RunDisconnectScreen |
sms | multiplayer disconnect confirmation screen (DDIAG.DLG) |
0x00487A3A |
WaitTicks |
sms | busy-wait N ticks (TIMESystemTime); dialog animation delay |
0x00487A63 |
DialogSetup |
sms | push a DIALOG frame (_curDialog = &_dialogStruct + ++_dialogNum*0x29); ChoosePreload header, link records |
0x00487E90 |
DialogLinkRecords |
re | per-record setup pass over the DIALOG record list (short-field init); called by DialogSetup |
0x004880D0 |
DialogShow |
sms | render the dialog: alloc/lazy-init background bitmap (+0x16/+0x1a), draw all records |
0x00488170 |
DialogBeginDraw |
re | common draw prologue: PushShellAlloc + ShellHideMouse |
0x00488180 |
DialogEndDraw |
re | paired draw epilogue (ShellShowMouse/PopShellAlloc) |
0x00488190 |
DialogShutDown |
sms | blit saved background back (param gated), free +0x16/+0x1a/+0x12 handles |
0x00488300 |
DialogDone |
sms | free all shared dialog fonts (wheel/list/panel/panelDisabled/panel2/actionBlue) |
0x00488470 |
DialogDraw |
sms | record draw dispatcher: walks next_record_ptr calling (**draw_fn_ptr)(record) |
0x00488490 |
DialogUpdate |
sms | event dispatcher (2617 B): per-record PointInBox hit-test, focus, slider/rocker/edit input, returns selected item |
0x00488F00 |
DialogWaitUntilTick |
re | spin on TIMESystemTime until a target tick reached (returns bool) |
0x00488F30 |
DialogHelper488f30 |
re | small dialog helper (31 B); role not confirmed |
0x00488F50 |
DialogRadioGroupClear |
re | walk records; for type 1/5 matching group id at +0x17, clear the pressed flag (+0xb) — radio de-select |
0x00488FC0 |
DialogWhatItem |
sms | return _dialogItemPtr (last record that passed hit-test) |
0x00488FD0 |
DialogScrollbarHit |
re | type-7 scrollbar custom hit handler: PointInBox(+0x18) then callback +0x24 (DLG.md hit-zone table) |
0x00489070 |
DialogSliderRelease |
re | active-slider mouse handler: on button-up call slider callback +0x2c and clear _activeSlider |
0x004891A0 |
DialogScrollThumbInit |
re | initialise scrollbar thumb position from +0xb/+0xc/+0xe/+0x10 (DLG.md: called on show) |
0x00489220 |
DialogClampThumb |
re | clamp scrollbar thumb (+0x12) within track bounds (+0x16/+0x1a/+0x1e) |
0x004892E0 |
DialogGetPtr |
sms | resolve a record pointer by item id |
0x00489300 |
DialogGetValue |
sms | read a control's current value |
0x00489360 |
DialogMatchListString |
sms | find a list-box row by string |
0x00489400 |
DialogSetRocker |
sms | set a rocker control's state |
0x00489430 |
DialogSetValue |
sms | set a control's value |
0x004894F0 |
DialogSelectItem |
sms | mark a record selected (highlight) |
0x00489580 |
DialogDeselectItem |
sms | clear a record's selected/disabled bit |
0x004895D0 |
DialogSetString |
sms | set an edit/text record's string |
0x00489660 |
DialogUpdateString |
sms | refresh a text record after edit |
0x004896A0 |
DialogGetString |
sms | read an edit box's text buffer |
0x00489710 |
TopCenterDialog |
sms | center dialog: x=(sw-w)/2, y=(sh-h)/3 |
0x00489760 |
Info2640Preload |
sms | preload header for INFO2640.DLG (OK+Cancel 640) |
0x00489780 |
Info640Preload |
sms | preload header for INFO640.DLG |
0x004897A0 |
GrafPrefPreload |
sms | preload header for GRAFPREF.DLG |
0x004897D0 |
SndPrefPreload |
sms | preload header for SNDPREF.DLG |
0x004897F0 |
ChoosePreload |
sms | DLG header record: PushShellAlloc, load action-button PIC/font by type (DLG.md); dispatched via computed indirect call |
0x00489810 |
MultiPreload |
sms | preload header for multiplayer dialogs |
0x00489AC0 |
DrawText |
sms | type-9 static text renderer (DLG.md field layout) |
0x00489B90 |
DrawAction |
sms | type-0 clickable action-button renderer (DLG.md field layout) |
0x0048A080 |
DialogFlush |
re | ShellShowMouse + G_Flush + ShellHideMouse |
0x0048A260 |
DialogBlitModuleBitmap |
re | blit from the DLG module bitmap (MMAccessR of _curDialog+0x1a) at dialog-relative x/y |
0x0048A2B0 |
DialogSetupBitmap |
re | SetupBitmapAccess wrapper for dialog rendering |
0x0048A4C0 |
DrawDial |
sms | rotary dial control renderer |
0x0048A730 |
DrawLight |
sms | indicator light/LED renderer |
0x0048A7D0 |
PrintPageNums |
sms | render 'page N of M' for paged list widgets |
0x0048A8E0 |
DialogEnsureListFont |
re | lazy-load SMLFONT into _listFont if null |
0x0048A910 |
DrawFormattedText |
sms | type-9 variant multi-line/paged text renderer (DLG.md) |
0x0048A9F0 |
DrawMissList |
sms | single-mission list renderer |
0x0048ADE0 |
DrawListBox |
sms | generic scrollable list-box renderer |
0x0048B320 |
DrawCheck |
sms | checkbox renderer (type 3) |
0x0048B450 |
DialogRockerRepeat |
re | rocker auto-repeat: reset rockerTicks/rockerLastTicks, step _activeRocker parent (+0x22) by +0x16 |
0x0048B4E0 |
DrawRocker |
sms | type-6 rocker/toggle renderer (two hit halves) |
0x0048B8B0 |
DialogHelper48b8b0 |
re | rocker/slider draw helper (62 B) |
0x0048B8F0 |
DialogHelper48b8f0 |
re | rocker/slider draw helper (62 B) |
0x0048B930 |
DrawToggle |
sms | two-state toggle renderer (type 8) |
0x0048BAD0 |
DrawSliderHoriz |
sms | horizontal slider renderer |
0x0048BBE0 |
DialogHelper48bbe0 |
re | small slider helper (31 B) |
0x0048BC00 |
DialogHelper48bc00 |
re | slider helper (96 B) |
0x0048BC60 |
DrawSliderVert |
sms | vertical slider renderer |
0x0048BDF0 |
DialogHelper48bdf0 |
re | slider/edit helper (99 B) |
0x0048BE60 |
CheckMousePtr |
sms | test/redraw mouse pointer over a widget rect during draw |
0x0048BEC0 |
DialogEditGeom |
re | compute edit-box on-screen geometry from _curDialog + focused record (+0x1e) |
0x0048BF50 |
DialogHelper48bf50 |
re | edit-box helper (66 B) |
0x0048BFA0 |
DialogDrawEditCaret |
re | draw the blinking text caret (G_Vline) at _cursorAt within the focused edit box; _selectEnd gate |
0x0048C040 |
DialogEditKey |
re | edit-box keystroke handler (1220 B; char insert/delete/cursor) |
0x0048C510 |
DialogHelper48c510 |
re | edit helper (94 B) |
0x0048C570 |
DialogHelper48c570 |
re | edit helper (45 B) |
0x0048C5A0 |
DialogDrawEditText |
re | render edit-box text/selection (359 B) |
0x0048C710 |
DrawEditBox |
sms | type-2 edit-box renderer (DLG.md field layout) |
0x0048C800 |
DrawText320 |
sms | 320x200 static text renderer |
0x0048C8A0 |
DrawCheck320 |
sms | 320x200 checkbox renderer |
0x0048C970 |
DrawDial320 |
sms | 320x200 dial renderer |
0x0048CB00 |
Do320Button |
sms | 320x200 action-button dispatcher |
0x0048CBE0 |
DrawYes320 |
sms | 320 'Yes' button label renderer |
0x0048CC10 |
DrawNo320 |
sms | 320 'No' button label renderer |
0x0048CC40 |
DrawCancel320 |
sms | 320 'Cancel' button label renderer |
0x0048CC70 |
DrawDone320 |
sms | 320 'Done' button label renderer |
0x0048CCA0 |
DrawOK320 |
sms | 320 'OK' button label renderer |
0x0048CD40 |
DrawLight320 |
sms | 320 indicator light renderer |
0x0048CD70 |
DrawSwitch320 |
sms | 320 switch renderer |
0x0048CF10 |
SliderVert320 |
sms | 320 vertical slider renderer |
0x0048D030 |
ShellClickSound |
sms | play the UI click sound on a valid activation |
0x0048D090 |
ShellDisabledSound |
sms | play the 'disabled' buzz when a dimmed control is clicked |
0x0048D0D0 |
DisableActionButton |
sms | set record type_flags bit15 (dim); see DLG.md +0x00 |
0x0048D0E0 |
EnableActionButton |
sms | clear record type_flags bit15 (undim) |
0x0048D0F0 |
DialogEnableItem |
sms | enable/disable a dialog item |
0x0048D140 |
DialogItemIsEnabled |
sms | query a dialog item's enabled bit |
0x0048D150 |
LimitEditFieldLength |
sms | cap an edit field's character length |
0x0048D160 |
DialogTextStreamInit |
re | init a paged text-stream reader object (vtable[2]=LAB_0048d1d0, [3]=FUN_0048d1e0; alloc 0x26+0x1000) |
0x0048D1E0 |
DialogTextStreamRead |
re | text-stream read callback: FUN_00486f20 decode into 0x1000 buffer; sets state 0x29/0x74 |
0x004A08A0 |
ChooseActivity |
sms | TOP-LEVEL shell screen dispatcher loop: gates on _doScreens, MP sync (MPSendGameMode/MPWaitEveryoneStatus), random CHOOSEAC/CHOOSE3 background, drives main-menu screen selection |
0x004A26F0 |
DoDialogInfoBox |
sms | modal info-box driver; freezes time (_timeCompression=0x7fff) when in cockpit (_curScreen==0x10) |
0x004A27C0 |
DialogInfoBox |
sms | generic INFO320/INFO640 message-box builder+run |
View / camera & replay (VIEW)¶
view.csv · page — 19 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x0040D7A0 |
VIEWSlew |
sms | slew (free-look) camera control |
0x0040D7F0 |
VIEWApplyMode |
re | if the view mode word (+0xB4) is set delegate to the view builder VIEWBuild |
0x0040D810 |
VIEWFromObject |
re | position the view from the tracked object (_objPtrs[view+0x1C]) and its state |
0x0040E240 |
VIEWUpdateByType |
re | per-object-type view update (object class 7/0xC/0xF branches) |
0x0040E2C0 |
VIEWFitDistance |
re | compute camera stand-off distance from the object radius (_ObjRadius) |
0x0040E330 |
VIEWAngleScale |
re | angle-to-scale clamp helper for the view transform |
0x0040E380 |
VIEWImmediateVisibility |
sms | force the view to immediate (no-transition) visibility |
0x0040E3A0 |
VIEWInit |
re | allocate/initialise a view slot (MMPushAllocId; zero +0x1C/+0x1E) |
0x0040E450 |
VIEWFree |
re | free the view's allocated buffer (_MMFreePtr on +0x60) |
0x0040E470 |
VIEWSnapshot |
re | copy the 0x30-dword view state block (snapshot/restore helper) |
0x0040E930 |
VIEWInTransition |
sms | returns non-zero while the view is mid-transition |
0x0040E960 |
VIEWReplayRecordGate |
re | replay record gate: inside the _timerTicks window (DAT_005223F0/F4) set replay-active DAT_005224C0 |
0x0040EBA0 |
VIEWReplayPlayback |
re | replay playback: when replay-active copy the 0x30-dword saved-view buffer (DAT_00522400) into the view |
0x0040EBC0 |
VIEWBuild |
re | build the external/spot view for the given mode (the view builder VIEWApplyMode calls) |
0x0040F230 |
VIEWModeLookup |
re | scan the view-mode table at DAT_004EC420 |
0x0040F270 |
VIEWScaleClamp |
re | clamp/scale helper for the view field-of-view or zoom |
0x0040F2D0 |
VIEWSlewIntegrate |
re | frame-rate-scaled slew integration (_LMultDiv256 by _systemFrameTicks) |
0x0040F590 |
VIEWChangeObj |
sms | switch the view to a different tracked object |
0x0040F5D0 |
VIEWCanSeeTarget |
re | visibility/padlock check (_WRCanSee) gated on a _gamePrefs bit |
Campaign / mission / pilot (MAP/CAM/MC/MM/PLT)¶
campaign.csv · page — 125 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00421D40 |
ZONEActive |
re | zone active-window test: currentTime within [start,end] (param[7],param[8]); gate for ZONEUpdate |
0x00421DE0 |
ZONEServiceRange |
re | per-zone service worker (variant of _ZONEUpdate@0 over a range); fires _PROJAdd/_PROJHit/_GRAPHICAddExp on schedule using _Rand/_Percent |
0x00422120 |
ZONEPickTarget |
re | resolve a target plane index for a zone from _planes/_numPlanes (base &DAT_005713a6) |
0x00422190 |
MAPWPListBounds |
re | walk a waypoint list: find head via flag bit0(&1), count entries to tail via bit1(&2); stride 0x44 |
0x00422230 |
MAPAdjustObjAlt |
re | raise current object altitude (DAT_0050ce95) to terrain via _T_Info; helper of @MAPObjAlts@4 |
0x00422350 |
MAPMarkDirty |
re | clear DAT_00536720 hover, set DAT_00536518 redraw flag |
0x004224B3 |
MAPScreenToWorld |
re | inverse of _MAPWorldToScreen: screen point * _mapScale + _worldCenter |
0x0042256A |
MAPLoadBG |
re | load theater map background bitmap via _RMAccessHandle into DAT_004f0564 (alloc-id 7) |
0x004225D4 |
MAPRefreshBG |
re | reload map BG if theater name (_th+0x54) changed; recomputes map extents DAT_00536418.._00536430 |
0x00422667 |
MAPFreeBG |
re | free the map background handle DAT_004f0564 |
0x004226CB |
MAPSetObjWP |
re | set an object's active waypoint pointer (+0xe4); helper of @MAPUpdateWPPtrs@8 |
0x004226EE |
MAPSetFormationWP |
re | set new WP for an object plus its wing (_WNGPart) and group (_GRPPart) members |
0x00422828 |
MAPClearSelection |
re | clear map selection state: DAT_00536500 obj / _00536514 wp / _00536530 special / _005363e8 mode |
0x00422840 |
MAPClearHover |
re | 17-byte map-state setter (clears hover/redraw); role low-confidence, revisit |
0x0042297E |
MAPAddSpecial |
re | allocate a 0x3a-byte 'special' map marker into the _specials/DAT_0053665c table |
0x00422A71 |
MAPScreen |
re | mission-map/planning screen main loop (_curScreen==3); CAM.md 'FUN_00422a71'. Loads mc_menu/mc_dlg, drives waypoint/plane edit, save-mission, slew view |
0x00423ED4 |
MAPRebuildWPLoop |
re | recompute a waypoint loop back-pointer after edit (uses MAPWPListBounds + MAPWPOwnerIndex) |
0x00423F30 |
MAPStoreLeafTmap |
re | write selected tmap id+rotation into _tlist at current leaf coords; _T_SortTmapList |
0x00423F90 |
MAPFindLeafTmap |
re | linear search _tlist[_tlistSize] for the entry at (x&0xfffc,y&0xfffc) |
0x00423FD0 |
MAPReadLeafTmap |
re | read tmap id/rot at current leaf coords into DAT_00536724/_0053652c |
0x00424040 |
MAPSetLeafTmapPic |
re | load '%s%d.PIC' tmap thumbnail via _RMAccess for the selected leaf |
0x004240D0 |
MAPEditTmapDialog |
re | mc_name dialog: prompt for a tmap index, then apply via MAPStoreLeafTmap |
0x004241A0 |
MAPReadLeafTmap2 |
re | duplicate of MAPReadLeafTmap (separate call site) |
0x004241D0 |
MAPSelWPIsPlayers |
re | true if the selected waypoint (DAT_00536514) belongs to the player's wing |
0x00424220 |
MAPObjInPlayerWing |
re | true if object id shares the player's wing (_WNGPart of _playerId) |
0x00424290 |
MAPSpecialSelectable |
re | returns 0 in this build (special markers not directly selectable) |
0x004242A0 |
MAPResetObjects |
re | reset/place ALL mission objects to start state: clear parks (_APClearParks), reassign airfield parking (_APNearest/_APAssignPark), reset positions/speeds/waypoints. Called after load and before save |
0x00424DE0 |
MAPSetWPTargetPos |
re | copy a waypoint target's world position (_WPTarget) onto an object |
0x00424E60 |
MAPClaimObjControl |
re | set current object owner byte DAT_0050ce90 = _thisComputer|0x80 (mark human-controlled) |
0x00424EC0 |
MAPSetObjWPFlags |
re | set object's waypoint-capable flag (bit2 of +1) from class (5/7, or 3 with a group) |
0x00424F20 |
MAPStatusText |
re | set the map help/status line text (DAT_005365a8) and render it via _PrepareText |
0x00424FA3 |
MAPWPOwnerIndex |
re | return the object index that owns a given waypoint pointer |
0x00425023 |
MAPSnapWPToStrip |
re | snap a waypoint onto the nearest airstrip (_APNearest) |
0x00425072 |
MAPWPNearStrip |
re | test whether a point is near an airstrip (_APNearest) |
0x00425096 |
MAPScreenSpan |
re | compute world-space span of the current map viewport (two MAPScreenToWorld corners) |
0x004250CE |
MAPPickObjIcon |
re | hit-test object icons at a screen point (12x10 px box), honoring MAPObjVisible |
0x00425196 |
MAPObjVisible |
re | object map-visibility/side filter using view mask _DAT_00536628 and side flags |
0x00425249 |
MAPPickWPIcon |
re | hit-test waypoint icons at a screen point |
0x0042532A |
MAPSwapPalette |
re | swap map palette DAT_00536590 <-> _curPalette (enter/leave map draw) |
0x00425358 |
MAPDraw |
re | full map render: 2D map (grid/BG/specials/icons/paths) or 3D leaf preview via _T_Make/_T_Render depending on mode DAT_005363f0 |
0x00425948 |
MAPDrawRuler |
re | draw the map scale ruler bar |
0x00425A8F |
MAPIconRadius |
re | compute on-screen icon radius from current map scale |
0x00425ACD |
MAPDrawEra |
re | draw 'Historical Era' year range text (non-campaign multiplayer maps) |
0x00425B8B |
MAPSyncSliders |
re | sync dialog X/Y sliders (items 4,3) to current _worldCenter/DAT_00536528 |
0x00425C0A |
MAPRedrawSelection |
re | redraw highlight when the selected object changes |
0x00425C77 |
MAPDrawObjIcon |
re | draw one object's map icon: side color ring, type glyph (mcicons.PIC), padlock/target markers, label |
0x00426277 |
MAPDrawTargetLink |
re | draw the dashed target link line from an object to its target |
0x004262DE |
MAPDrawAllPaths |
re | iterate visible objects and draw each waypoint path |
0x00426325 |
MAPDrawObjPath |
re | draw one object's full waypoint polyline + target links |
0x0042648F |
MAPDrawWPIcon |
re | blit a single waypoint node icon (mcicons.PIC) |
0x0042658D |
MAPSetSelection |
re | set current selection to an object id / waypoint ptr / special index (DAT_00536500/_514/_530) |
0x004265C1 |
MAPStepSelection |
re | advance waypoint selection +/-1 along the owning object's list |
0x00426696 |
MAPAssignWPTarget |
re | assign escort/target to the selected waypoint with validation ('can't escort yourself', 'can't target a zone') |
0x004267E4 |
MAPInsertWP |
re | insert a new waypoint after the selection: alloc (count+2)*0x44, _MMAllocPtr, splice via MAPUpdateWPPtrs, 'Only ten waypoints allowed' |
0x00426ACB |
MAPObjEditable |
re | ownership/editability test for an object (compares against DAT_00536504/_005364f8 special ids) |
0x00426B70 |
MAPObjEditableP |
re | wrapper for MAPObjEditable(param,1) |
0x00426BF0 |
MAPInitWPSpeed |
re | initialize a new waypoint's speed from _COCornerSpeed + formation defaults |
0x00426C45 |
MAPSetWPFormationParams |
re | copy formation offset/params from table &DAT_004f057e[idx*7] into a waypoint |
0x00426C6D |
MAPDeleteWP |
re | delete the selected waypoint (frees loop node, memmoves list, fixes head flag) |
0x00426D6D |
MAPRequirePlayerPlane |
re | error 'You must first designate a plane' if no flyable/human plane exists |
0x00426D98 |
MAPHasFlyablePlane |
re | scan objects for a human-controllable class-4 plane (flags +0x10 bit7) |
0x00426DE2 |
MAPMakeSelPlayer |
re | make the selected class-4 object the human/player (calls MAPClaimObjControl) |
0x00426E37 |
MAPAddObject |
re | add a new object to the mission: _DialogPickFiles, _T_AddObj, seed position/altitude/side per class flags |
0x00427195 |
MAPDeleteObject |
re | delete an object from the mission: _GRPRemove, _APDelete, clear chains, MAPResetObjects |
0x004271ED |
MAPWPButtons |
re | waypoint-properties dialog button handler: name/altitude/speed/formation/react/loop edits (mc_name dialog) |
0x004276A0 |
MAPWPFormationIndex |
re | return a waypoint's formation-table index (&DAT_004f0578) |
0x004276E9 |
MAPQuantizeAlt |
re | round an altitude to selectable increments within [min,max] |
0x00427721 |
MAPQuantizeToTable |
re | snap a value to the nearest entry of a speed/step table (&DAT_004f0740) |
0x00427769 |
MAPObjButtons |
re | object-properties dialog button handler: side (mc_nat2), pilot name, plane type (_ChangePlaneType), fly-this-plane, success-condition flags |
0x004281DD |
MAPObjCampaignLocked |
re | editability gate that is stricter when _campaignFile != 0 (campaign missions are locked) |
0x00428270 |
MAPObjEditableCheck |
re | wrapper: MAPObjEditable then flag test |
0x004282D0 |
MAPToggleObjControl |
re | toggle human control ownership of an object across the current-obj stack |
0x00428340 |
MAPCenterOnPlayer |
re | center the map _worldCenter/DAT_00536528 on the player object; select it |
0x00428412 |
MISSIONLoad |
re | canonical campaign/mission loader (CAM.md): MISSIONShutdown->Init1->CallMissionProc(.mc[_nato]_M or named)->Init2->MAPResetObjects->CenterOnPlayer->T_NamedTmaps/T_InitDictionary |
0x004284CA |
MAPSaveMission |
re | save-mission dialog: prompt filename (mc_name), validate, write .M via FUN_00495e80 |
0x0042866A |
MAPDragItem |
re | mouse-drag a selected object/waypoint to a new map position (ghost bitmap follow) |
0x004289EE |
MAPSelWorldPos |
re | get the world position of the current selection (obj/wp/special) |
0x00428A3B |
MAPDrawSelInfo |
re | draw the selection highlight glyph + rebuild the info panel (dispatches to MAPBuildObjInfo/MAPBuildWPInfo) |
0x00428AEF |
MAPBuildWPInfo |
re | build the waypoint info-panel text and hot-button rects: heading/ETA/formation/react/target strings |
0x00429245 |
MAPFormatWPTarget |
re | format a waypoint's target name string (_WPTarget + _NextString) |
0x004292D2 |
MAPFormatReactFlags |
re | map a react-flag byte to the button-index table (&DAT_004f0550) |
0x0042934C |
MAPBuildObjInfo |
re | build the object info-panel text and hot-button rects: pilot/heading/altitude/mission-success/attack flags |
0x00429DDE |
MAPMissionMenu |
re | mission-editor command dispatcher: view-filter toggles (DAT_00536628), CampaignMenu, save-changes prompt, opens all mc* scenario dialogs; drives wing/group add via MAPWingAdd/MAPGroupAdd |
0x0042A656 |
MAPRandomizeSkill |
re | randomize a side's object AI-skill byte (DAT_0050cf62) via _Rand |
0x0042A71A |
MAPLoadMissionDialog |
re | pick a mission file (_DialogPickFiles) and load it into the editor via MISSIONLoad |
0x0042A780 |
MAPDlgWeather |
re | weather/time-of-day dialog (mc_weth); sets _startTimeOfDay/_currentTimeOfDay, random cloud offset |
0x0042A93A |
MAPDlgEndTime |
re | end-scenario time dialog (mc_time); sets _endScenarioSetTime |
0x0042A9C4 |
MAPDlgRevive |
re | revive/lives dialog (mc_lives); sets _reviveAllowed |
0x0042AA50 |
MAPDlgReviveDelay |
re | revive-delay dialog (mc_delay); sets _reviveDelay |
0x0042AADC |
MAPDlgReviveDist |
re | revive-distance dialog (mc_dist); sets _reviveDist |
0x0042AB68 |
MAPDlgEndKills |
re | end-scenario kills dialog (mc_kills); sets _endScenarioKills |
0x0042ABF4 |
MAPDlgEndKillType |
re | end-scenario kill-type dialog (mc_killt); sets _endScenarioKillsType |
0x0042AC80 |
MAPDlgNatoFighters |
re | NATO-fighters side dialog (mc_natf) |
0x0042AD35 |
MAPDlgScoring |
re | scoring dialog (mc_scr); reads 4 score-weight fields |
0x0042AE3F |
MAPWingRejoin |
re | rejoin/reposition a wing at its leader (_WNGPart, _wingIds/_wingSizes) |
0x0042AEDF |
MAPWingSetLeader |
re | make the selected object its wing's leader (_wingIds[slot]=sel) |
0x0042AF86 |
MAPWingAdd |
re | add an aircraft to wing slot N (_WNGAdd); 'No more aircraft can be added' |
0x0042B056 |
MAPGroupRejoin |
re | rejoin/reposition a group at its leader (_GRPPart, _groupIds/_groupSizes) |
0x0042B0F6 |
MAPGroupSetLeader |
re | make the selected object its group's leader (_groupIds[slot]=sel) |
0x0042B19D |
MAPGroupAdd |
re | add an object to group slot N (_GRPAdd, _GRPHumansFirst) |
0x0042B275 |
MAPDeleteSpecial |
re | delete the selected special marker (_MMFreePtr on _specials[sel]) |
0x00467240 |
PilotFindFreeSlot |
re | find an unused pilot save slot by probing PLT%03d.P (s_PLT_03d_P) with _Rand until _Open fails |
0x004674F0 |
PilotBuildPaper |
re | build the pilot logbook 'paper' text (mission count, Available/MIA/KIA/Retired status via _AddStats) and blit photo (_PilotPhoto). AnalyzePLT 'pilot card display' |
0x00467860 |
PilotPaperAddLine |
re | append one label/value line pair into the pilot-paper text buffer |
0x00467880 |
PilotPaperEndLine |
re | append the final/terminating line to the pilot-paper buffer |
0x00467E30 |
PilotListAddAvail |
re | load a pilot file (_RMAccess 0x810c) and insert it sorted into _sortedPilots (_totalPilots++) |
0x00468C40 |
PilotListAddUnavail |
re | load a pilot into the unavailable list _unAvailNames (_unAvailPilots++) |
0x00468CA0 |
PilotMakeCopyName |
re | generate a unique 'NAME Copy N' pilot name, scanning both pilot lists |
0x00468DF0 |
PilotStripCopySuffix |
re | strip a trailing ' Copy' from a pilot name (_strstr s_Copy) |
0x00468E40 |
PilotLoadBySortIndex |
re | load the pilot at sorted index (PLT%03d.P) and copy fields into _pilotName etc. |
0x00468F00 |
PilotFormatRank |
re | format a pilot's rank string from the _pilotRanks table |
0x00468F40 |
PilotDiskSpaceError |
re | 'You don't have enough free disk space' dialog before a pilot save |
0x00468F80 |
PilotSetField |
re | small pilot-record field setter (cdecl int,char); exact field low-confidence, revisit |
0x0047FAAE |
JOGCFetchMission |
re | download a mission file from the JOGC online server (_getMSdatafile/_getMSdatafilesize, _SaveFile), then run single mission. BORDERLINE: online path may belong to network #219 |
0x004809D0 |
MISSIONLoadOrdIcons |
re | load ordnance HUD icon PICs (ord_air3.PIC ...) during MISSIONInit2 when no player plane / at home airport |
0x00481920 |
CampaignProcInvoke |
re | low-level campaign-DLL call: latch __campaignFailures=DAT_004fab40 then (*_campaignProc)(cmd). Inner worker of _CallCampaignProc@4 |
0x00481A7B |
MISSIONEnemiesAlive |
re | scan objects for a live enemy during the first 300 ticks (_Alive, _currentTime<300); mission start-grace test used near _AlmostHome |
0x00483C90 |
TextNextToken |
re | whitespace-delimited token scanner over the parse cursor DAT_0055281c..DAT_005528c0. MC.md: MISSIONTextProc tokenizer FUN_00483c90 |
0x00483D10 |
TextIsDelim |
re | predicate: is char a token delimiter/whitespace (helper of TextNextToken) |
0x00483D30 |
TextNextNumber |
re | read next token and convert to integer (_StringToNumber) |
0x00483D50 |
TextTokenToValue |
re | scalar token->value conversion helper (uint->uint); low-confidence, revisit |
0x00485380 |
CampaignAccumStats |
re | fold end-of-mission stats into campaign running totals (DAT_004fab44.. += DAT_0054ddc4..) via StatsAddPair. AnalyzePLT 'stats flush' |
0x004854A0 |
StatsAddPair |
re | add a fired/hit counter pair (accumulator). AnalyzePLT 'weapon accuracy accumulator' |
0x004856F0 |
StatsBucketFor |
re | resolve the per-player weapon-stat bucket for a shooter/target id (_playerId/_playerWMId). AnalyzePLT 'weapon accuracy dispatch' |
0x004867D0 |
MISSIONPlayerSlot |
re | resolve the player-score array slot index for a computer/object id (used by _MISSIONAddScore) |
Collision (COL)¶
collision.csv · page — 19 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x0042B800 |
Collision |
sms | |
0x0042BD30 |
COLSetAngle |
sms | |
0x0042BDC0 |
COLSweepTerrain |
re | swept segment-vs-terrain grid walk (<=20 cells; ClipSegToCell + TestTerrainCell) |
0x0042BFC0 |
COLTestTerrainCell |
re | test one terrain grid cell's two triangles (corner heights + normals) |
0x0042C1A0 |
COLTestTerrainTri |
re | segment vs one terrain triangle plane; records a blocking hit |
0x0042C420 |
COLClipSegToCell |
re | Cohen-Sutherland XZ clip of the segment to one terrain cell column |
0x0042C840 |
COLTestObjects |
re | object broad-phase: AABB-overlap the frame's registered ids |
0x0042C9B0 |
COLTestObj |
re | object narrow-phase: ray into object local frame, box-hierarchy clip |
0x0042D050 |
COLClipSegToBox |
re | segment vs oriented box: 6-plane clip with closure radius |
0x0042DDA0 |
COLFlatGround |
sms | |
0x0042DE60 |
COLRecordHit |
re | keep-nearest hit accumulator (blocking slot / object slot) |
0x0042DF80 |
COLPitchToAvoidTerrain |
sms | |
0x0042E0C0 |
COLGetInfo |
sms | |
0x0042E100 |
COLGetBox |
sms | |
0x0042E140 |
COLDrawInfo |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x0042E4E0 |
COLTerrainBlocking |
sms | |
0x0042E530 |
COLInit |
sms | |
0x0042E540 |
COLAddObj |
sms | |
0x0042E5C0 |
COLRemoveCurObj |
sms |
Sound / music (incl. WAIL32)¶
sound.csv · page — 49 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004328B0 |
InitMusic |
sms | |
0x00432920 |
ShutDownMidi |
sms | |
0x004329A0 |
DMusicOn |
sms | |
0x004329E0 |
MusicOn |
sms | |
0x00432A80 |
DMusicVolume |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00432A90 |
SoundActive |
sms | |
0x00432B40 |
MusicVolume |
sms | |
0x00432BD0 |
DMusicOff |
sms | |
0x00432C00 |
MusicOff |
sms | |
0x00432C30 |
ScoreOn |
sms | |
0x00432C70 |
ScoreOff |
sms | |
0x00432CA0 |
ScoreUpdate |
sms | |
0x00432F70 |
ScorePlaying |
sms | |
0x00432F80 |
ShellMusicUpdate |
sms | |
0x00433170 |
ShellMusic |
sms | |
0x00433180 |
InitSound |
sms | |
0x00433280 |
ShutDownSndDriver |
sms | |
0x004332F0 |
ShutDownMixer |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00433300 |
InitMixer |
sms | |
0x00433480 |
SoundPoints |
sms | |
0x004334D0 |
SoundNoMixer |
sms | |
0x00433580 |
SingleSound |
sms | |
0x004335C0 |
BasicSound |
sms | |
0x004335F0 |
BasicLinkSound |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00433640 |
LoopSound |
sms | |
0x00433680 |
SoundOn |
sms | |
0x00433CE0 |
SoundOff |
sms | |
0x00433D40 |
SoundAllOff |
sms | |
0x00433D80 |
SoundSetup |
sms | |
0x004343B0 |
ViewPan |
sms | |
0x00434550 |
Turbulence |
sms | |
0x00434620 |
SoundRelease |
sms | |
0x004347A0 |
CheckSndPurge |
sms | |
0x004347E0 |
SetupLoopSounds |
sms | |
0x00434800 |
MaybeLoopSound |
sms | |
0x00434920 |
UpdateLoopSounds |
sms | |
0x004349D0 |
ServiceSounds |
sms | |
0x00435480 |
StopGameSounds |
sms | |
0x004354B0 |
PollMod |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004357C0 |
SoundPrioritize |
sms | |
0x00435900 |
StartWaitingSounds |
sms | |
0x00435980 |
StartVoice |
sms | |
0x00435A00 |
SetVolPitchPan |
sms | |
0x00435AE0 |
CheckSndLink |
sms | |
0x00435B40 |
GetMixerStatus |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00435B80 |
SoundStatus |
sms | |
0x00435BC0 |
SndLostFocus |
sms | |
0x00435C20 |
SndGotFocus |
sms | |
0x00435C30 |
SoundName |
sms |
Memory & resource managers (MM/RM)¶
memory-resource.csv · page — 50 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00435C60 |
MMInit |
sms | alloc handle table operator_new(count*0x1C); thread all handles onto free list; GetSystemInfo page size; sets mm_initialized |
0x00435D40 |
MMShutdown |
sms | MMFreeAllId for ids 0..0x12 (19 pools) then free handle array; clears mm_initialized |
0x00435D80 |
MMAllocHandle |
sms | core allocator: MMInternalAlloc(size)+MMUseHandle -> T_HANDLE*; frees block if no free handle |
0x00435DC0 |
MMMapFile |
sms | |
0x00435E90 |
MMUnmapFile |
sms | UnmapViewOfFile + CloseHandle(mapping) + CloseHandle(file) |
0x00435ED0 |
MMUseHandle |
sms | pop mm_unused_list; fill data(+8)/len(+C)/flags(+10)/allocId(+12); push onto mm_used_list |
0x00435F70 |
MMAllocPtr |
sms | MMAllocHandle then return handle->data (+8) |
0x00435F80 |
MMFreeHandle |
sms | unmap(if flag 0x4000) or MMInternalFree data; RMNotify if flag 0x0010; unlink used-list; push to free list |
0x00436040 |
MMFreePtr |
sms | MMPtrToHandle then MMFreeHandle |
0x00436060 |
MMReallocHandle |
sms | |
0x004360D0 |
MMReallocPtr |
sms | MMPtrToHandle then return handle->data (thin realloc-by-ptr shim) |
0x004360E0 |
MMInternalAlloc |
sms | |
0x00436110 |
MMInternalFree |
sms | |
0x00436140 |
MMPtrToHandle |
sms | linear scan mm_used_list for handle whose data(+8)==ptr |
0x00436170 |
MMPushAllocId |
sms | save mmAllocId onto id save-stack @0x538250 (depth @0x4F3DCC); set new id |
0x00436190 |
MMPopAllocId |
sms | restore mmAllocId from id save-stack |
0x004361B0 |
MMCompactRAM |
sms | stub -> return 0 (no heap compaction on Win32) — the Mac-heritage purger that would discard purgeable handles and set T_HANDLE flag 0x1000 (purged); its readers (RMFind/RMFindAndLoad/BrushFromIndex/MAPDrawBG free-and-reload on the flag) are dead recovery paths in the shipping build |
0x004361C0 |
MMFreeAllId |
sms | walk mm_used_list; MMFreeHandle every handle whose allocId(+12)==id (group free) |
0x004361F0 |
MMHandleLen |
sms | return handle->len(+C) if data non-null else 0 |
0x00436210 |
MMLock |
sms | |
0x00436220 |
MMLockW |
sms | |
0x00436230 |
MMLockR |
sms | |
0x00436240 |
MMLockE |
sms | SMS-named; not in inventory; extended-lock variant in the MMLock family (undecompiled) |
0x00436260 |
MMUnlock |
sms | no-op ret (Win32 memory is fixed) |
0x00436270 |
MMAccessR |
sms | |
0x00436280 |
MMAccessW |
sms | |
0x00436290 |
MMAccessE |
sms | return handle->data(+8)+offset (0 if handle/data null); THE handle-deref primitive |
0x004362C0 |
MMAreaFree |
sms | SMS-named; not in inventory; free-area query (returns KA/ulong) |
0x004362D0 |
MMByteAt |
sms | (int8)(base+off) |
0x004362E0 |
MMWordAt |
sms | (int16)(base+off) |
0x004362F0 |
MMUWordAt |
sms | SMS-named; not in inventory; unsigned 16-bit read primitive |
0x00436300 |
MMLongAt |
sms | SMS-named; not in inventory; signed 32-bit read primitive |
0x00436310 |
MMULongAt |
sms | (uint32)(base+off) |
0x004A67F0 |
RMInit |
sms | zero resList[1400] (0x19FA dwords) and resCache[20]; set rmInitialized(@0x50A618)=1 |
0x004A6820 |
RMShutdown |
sms | RMFree every live resList slot; zero table; clear rmInitialized |
0x004A6860 |
RMType |
sms | |
0x004A6870 |
RMChangeType |
sms | |
0x004A68F0 |
RMLocate |
sms | register key(uppercased)+flags(+0E)+ptr(+0F) in first free RES_LIST slot; tag allocId(+0D)=mmAllocId |
0x004A6970 |
RMUnlocate |
sms | RMFind(name) then clear name byte (release the RES_LIST slot) |
0x004A6990 |
RMFind |
sms | uppercase key; check 20-entry LRU resCache then linear-scan resList; refresh timerTicks; purge dead handles (flag&2 && handle+0x11&0x10) |
0x004A6AB0 |
RMCacheInsert |
re | insert RES_LIST ptr into resCache evicting the oldest (min timerTicks) slot; stamps timerTicks [FUN_004a6ab0] |
0x004A6AE0 |
RMAccess |
sms | |
0x004A6B30 |
RMFindAndLoad |
re | core resolve+load+register; name from embedded string "RMFindAndLoad: can't load %s" @0x50A624; SMCallByName |
0x004A6CC0 |
RMAccessHandle |
sms | RMFindAndLoad then return the raw handle/ptr (+0x0F) WITHOUT dereferencing |
0x004A6CE0 |
RMFree |
sms | RMFind; if flag bit0 SMCallByName |
0x004A6D60 |
RMFreeAllId |
sms | RMFree every resList entry whose allocId(+0D)==id; brackets with rmNotifyEnabled=0/1 |
0x004A6DB0 |
RMNotify |
sms | callback from MMFreeHandle: find resList entry whose ptr(+0F)==freed handle and invalidate it; gated by rmNotifyEnabled |
0x004A6DF0 |
RMSetup |
re | post-load per-type hook: SMCallByName |
0x004A6E20 |
SetupBitmapAccess |
sms | |
0x004A7240 |
RMLegalFilename |
sms | canonicalize a resource filename in place (IsBadStringPtr len 0xD; collapse to a single '.'); OUT-OF-RANGE claim (sits in terrain span) |
.SEQ scripted-cutscene / sequence player (SEQ)¶
seq.csv · page — 40 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00412C10 |
PlaySeq |
sms | public entry - SeqStart then GetKey/SeqContinue/flush loop then SeqEnd; scattered out-of-range |
0x00444F70 |
SeqInit |
sms | zero all seq lists/free-lists/arrays (seqList seqGrArray seqFontArray seqLabels seqText) |
0x00445060 |
SeqStart |
sms | load .SEQ file - alloc a 0x38-byte SEQUENCE slot - build background bitmap - returns slot index |
0x00445330 |
SeqLoadScript |
re | LoadFile the .SEQ then loop skip/expand/include lines into compiled command buffer; recursive for includes |
0x00445440 |
SeqSkipComments |
re | scan script text past blank lines - ; and // comments - whitespace; return next content or NULL |
0x004454D0 |
SeqSubstitute |
sms | expand %N token into the N-th SeqStart argument string |
0x00445550 |
SeqExpandLine |
re | tokenize one script line into seqLine - handle quotes and %-substitution; return next line |
0x004456B0 |
SeqParseInclude |
re | detect an include directive and extract the quoted filename |
0x00445700 |
SeqContinue |
sms | main per-tick interpreter - fade + labels + fetch line + build SEQ+cmd name and SMAddress-dispatch the sub-op |
0x00445B40 |
SeqFetchLine |
re | read next raw line from compiled buffer into seqLine - parse leading timecode (/abs +rel frame) into next-command tick |
0x00445CC0 |
SeqParseLabel |
re | parse =label definition - copy name into a seqLabelList node and set seqLabelPtr |
0x00445D30 |
SeqStop |
sms | tear down one sequence slot - free handle - return label/graphic nodes to free lists |
0x00445E30 |
SeqEnd |
sms | stop all active sequences - SoundAllOff - MusicOff - RMFreeAllId(4) |
0x00445E70 |
SeqRect |
sms | script op - set current sequence clip rectangle (x y w h); absent-from-inventory |
0x00445ED0 |
SeqRender |
re | per-frame render - SetupBitmapAccess then expire nodes then walk seqGraphics ring drawing dirty SEQGR nodes under clip box |
0x00446090 |
SeqExpireGraphics |
re | age SEQGR ring nodes vs timerTicks - set expired/dirty flags; return redraw-needed |
0x00446100 |
SeqNextOverlap |
re | iterate SEQGR ring for next node whose rect overlaps a given rect |
0x00446170 |
SeqGraphicOrder |
re | SEQGR list walk via prev-links (+0x28) to a target node - returns its left-x |
0x004461A0 |
SeqDrawGraphic |
re | draw one SEQGR node by type - 1 bitmap blit - 2 filled rect - 4 multiline color text; mark overlaps dirty |
0x00446330 |
SeqAccessResource |
re | RMAccessHandle wrapper that credits the load time back into seqIgnoreTicks |
0x00446360 |
SeqRedrawRegion |
re | redraw a SEQGR node clipped to a sub-rect (partial refresh of overlapped area) |
0x00446500 |
SeqNewGraphic |
re | allocate a SEQGR node from seqGrList free-list - link into seqGraphics ring - set rect/type/expiry/name |
0x00446610 |
SeqGRFind |
sms | find a SEQGR node by its name string; absent-from-inventory |
0x00446660 |
SEQbitmap |
sms | script op - load and display a bitmap as a SEQGR node; absent-from-inventory |
0x00446710 |
SeqLinkLabel |
re | link seqLabelPtr into the sequence label list and set the synch wait target when seqSynch |
0x004467B0 |
SEQblock |
sms | script op - draw a filled colored block as a SEQGR type-2 node |
0x00446850 |
SEQcall |
sms | script op - chain/call another sequence or label; absent-from-inventory |
0x00446890 |
SEQfadein |
sms | script op - begin palette fade-in (save curPalette - set seqFading=1 fade start/len) |
0x00446910 |
SEQfadeout |
sms | script op - begin palette fade-out (seqFading=-1) |
0x00446990 |
SeqFadeOut |
sms | apply a fade-out step - blacken curPalette by elapsed/len ratio; return done |
0x004469F0 |
SeqFadeIn |
sms | apply a fade-in step - un-blacken curPalette by ratio; return done |
0x00446A50 |
SEQfont |
sms | script op - load a font into seqFontList; absent-from-inventory |
0x00446B70 |
SEQmusic |
sms | script op - start a music track via MusicOn(name priority) |
0x00446BE0 |
SEQpalette |
sms | script op - load/set the sequence palette; absent-from-inventory |
0x00446C60 |
SEQrun |
sms | script op - resume/run control (16-byte leaf); absent-from-inventory |
0x00446C70 |
SEQsound |
sms | script op - play a sound effect; absent-from-inventory |
0x00446D30 |
SEQsndoff |
sms | script op - stop sound(s); absent-from-inventory |
0x00446F10 |
SEQtext |
sms | script op - build a wrapped-text SEQGR type-4 node using FormatText; absent-from-inventory |
0x00447090 |
SEQvideo |
sms | script op - play an AVI/video clip (drives videoState); absent-from-inventory |
0x00447120 |
SEQwait |
sms | script op - wait/synchronize N ticks; absent-from-inventory |
Flight model / physics (FM/HARD)¶
flight-model.csv · page — 58 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004514C0 |
FMUpdateGearPitch |
sms | |
0x00451580 |
FMUpdateGear |
sms | |
0x004515E0 |
FMUpdateWingSweep |
sms | |
0x00451680 |
FMUpdateThrustVector |
sms | |
0x004516B0 |
FMGetWeight |
sms | |
0x00451820 |
FMCopyAngles |
sms | |
0x004518A0 |
FMInitPlane |
sms | |
0x00451A60 |
LimitThrottle |
sms | |
0x00451B00 |
SetThrottle |
sms | |
0x00451B60 |
FMFlaps |
sms | |
0x00451C30 |
FMHook |
sms | |
0x00451C90 |
FMGear |
sms | |
0x00451D70 |
FMBrakes |
sms | |
0x00451E00 |
FMVector |
sms | |
0x00451E50 |
FMFuelConsumption |
sms | |
0x00451E80 |
BurnFuel |
sms | |
0x00452050 |
FMBurnNPCFuel |
sms | |
0x00452140 |
FMUpdatePlaneFields |
sms | |
0x00452630 |
FMBay |
sms | |
0x00452710 |
FMUpdateBay |
sms | |
0x00452760 |
FMBayIsOpen |
sms | |
0x00452770 |
HARDPtrs |
sms | |
0x004527F0 |
HARDUnload |
sms | |
0x00452940 |
HARDStoreWeight |
sms | |
0x00452980 |
HARDCanLoad |
sms | |
0x00452C20 |
HARDLoad |
sms | |
0x00452D10 |
HARDLoadAll |
sms | |
0x00452D90 |
HARDBestSeekers |
sms | |
0x00452E60 |
HARDBestSeeker |
sms | |
0x00452EA0 |
HARDFindJammer |
sms | |
0x00452F10 |
HARDFindECMForObj |
sms | |
0x00452F80 |
HARDFindStore |
sms | |
0x00452FF0 |
HARDFindProj |
sms | |
0x004530A0 |
HARDSetFlags |
sms | |
0x00453220 |
HARDUnrotatedHardPos |
sms | |
0x004532A0 |
HARDPos |
sms | |
0x004533D0 |
HARDGunsOnly |
sms | |
0x00453440 |
HARDGunsOnlyAll |
sms | |
0x00453490 |
HARDGunLoadPercent |
sms | |
0x00453640 |
HARDUnlimited |
sms | |
0x00453710 |
HARDPodHack |
sms | |
0x00453800 |
HARDClearUnloadedHarpoints |
sms | |
0x00453870 |
HARDResourceName |
sms | |
0x00453890 |
HARDStoreName |
re | resolve a hardpoint store's resource-name string via _NextString (name at +5 for loaded stores, +1 otherwise) |
0x004538C0 |
HARDSaveHumanLoads |
sms | |
0x004539C0 |
HARDRestoreHumanLoad |
sms | |
0x00453A70 |
HARDTotalFuel |
sms | |
0x00453AF0 |
HARDHasInternalBay |
sms | |
0x00453B90 |
HARDRearmTest |
sms | |
0x00453C50 |
HARDRearmHumanLoad |
sms | |
0x00453D10 |
HARDUsedWeapons |
sms | |
0x00453D50 |
HARDSaveFortLoads |
sms | |
0x00453EC0 |
HARDRestoreFortLoad |
sms | |
0x00453F90 |
HARDRearmFortTest |
sms | |
0x00454060 |
HARDRearmFortLoad |
sms | |
0x00454140 |
ChangePlaneType |
sms | |
0x004543A0 |
RepairTime |
sms | |
0x004543C0 |
SelectRepairPlane |
sms |
Video decode (FMV/Cobra)¶
video.csv · page — 23 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00456300 |
DecodeDSVGA8Frame |
sms | key/intra frame -> 8bpp paletted SVGA output with 2x pixel doubling (Double); ExpandDB/ExpandSB books + DrawAcrossBank |
0x00456AD0 |
EDB |
sms | expand-book: 512 iters, reads 2 index bytes -> 8 out bytes; neighbor (idx+-1) squared-RGB-distance<=8 smoothing against frame color table at FrameHeader+0x18; builds interpolated 2x2 index pattern for 8bpp dither path |
0x00456EC0 |
DecodeSVGA8Frame |
sms | |
0x00457230 |
DecodeDBook |
sms | decode 15/16-bit codebook: 256 entries x (4 luma + 2 chroma) -> 4 RGB555/565 px each; YUV->RGB via GlobalData+0xC1B4(luma scale)/+0xC1B6(Cb off)/+0xC1B8(Cr off)/+0xC1BA(grayscale flag); param_3==0xF selects 5:5:5 else 5:6:5; channels clamped by ClampU8 |
0x004575E0 |
ClampU8 |
re | saturate short to unsigned byte [0,255]: <0 -> 0, >=256 -> 255; leaf color-channel clamp called ~12x by DecodeDBook/DoubleDecodeDBook/Decode24Book/DoubleDecode24Book in YUV->RGB. (only FUN_ in range) |
0x00457600 |
DoubleDecodeDBook |
sms | 15/16-bit codebook decode with horizontal 2x replication (Double path) |
0x00457A50 |
DecodeSVGA15Frame |
sms | key/intra frame -> 15/16bpp hi-color SVGA (single); dispatch case 3 depth 0xF..0x10 |
0x00457E00 |
DecodeDSVGA15Frame |
sms | key/intra frame -> 15/16bpp hi-color SVGA with 2x doubling; dispatch case 4 depth 0xF..0x10 |
0x004581C0 |
Decode24Book |
sms | decode 24-bit (true-color) codebook: 256 entries -> RGB888 (uint out); YUV->RGB with ClampU8 |
0x00458480 |
DoubleDecode24Book |
sms | 24-bit codebook decode with 2x replication |
0x004587B0 |
DecodeSVGA24Frame |
sms | key/intra frame -> 24bpp true-color SVGA (single); dispatch case 3 depth==0x20 |
0x00458D10 |
DecodeDSVGA24Frame |
sms | key/intra frame -> 24bpp true-color SVGA with 2x doubling; dispatch case 4 depth==0x20 |
0x00459390 |
DecodeInterSVGA15Frame |
sms | INTER/delta (P) frame -> 15/16bpp SVGA (single); requires prior key frame (MovieContext+0x14!=0) |
0x00459B20 |
DecodeInterDSVGA15Frame |
sms | INTER/delta (P) frame -> 15/16bpp SVGA with 2x doubling |
0x0045A2F0 |
DitherCodeSingle |
sms | ordered-dither a 15/16-bit codebook down to 8bpp palette indices (single) |
0x0045A520 |
DitherCode |
sms | dither a 24-bit codebook to 8bpp palette indices |
0x0045A800 |
DitherCodeDouble |
sms | dither codebook to 8bpp with 2x replication (Double) |
0x0045AB90 |
DecodeDSVGA15NONFrame |
sms | key frame, 15-bit codebook rendered to 8bpp (NON path, depth==8) with 2x doubling; dispatch submode6 case2 (+4==0) |
0x0045B170 |
DecodeDSVGA15NONSkipFrame |
sms | NON 8bpp doubled key frame with skip-map (GlobalData+4!=0); dispatch submode6 case2 (+4!=0) |
0x0045B610 |
DecodeSVGA15NONFrame |
sms | key frame 15-bit codebook -> 8bpp (NON path) single; dispatch submode6 case1 |
0x0045B9C0 |
DecodeInterSVGA15NONFrame |
sms | INTER/delta NON 8bpp single; dispatch inter submode6 case1 |
0x0045BE60 |
DecodeInterDSVGA15NONFrame |
sms | INTER/delta NON 8bpp doubled; dispatch inter submode6 case2 (+4==0) |
0x0045C500 |
DecodeInterDSVGA15NONSkipFrame |
sms | INTER/delta NON 8bpp doubled with skip-map; dispatch inter submode6 case2 (+4!=0) |
Renderer & rasterizer (GG/G_)¶
renderer.csv · page — 118 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x0045CA70 |
DrawAcrossBankInter |
sms | |
0x0045CDA0 |
DrawAcrossBank |
sms | |
0x0045DBD0 |
GG_InitMode |
sms | |
0x0045DCB0 |
GG_ShutdownMode |
sms | |
0x0045DCE0 |
GG_GetMode |
sms | |
0x0045DE70 |
GG_SetPalette |
sms | |
0x0045DEC0 |
GG_Shake |
sms | |
0x0045DEDF |
GG_FlushShaken |
re | present the back buffer with the current screen-shake offset (reads _vShakeTicks/_hShakeTicks); shake variant of GG_Flush with no direct callers in the image (dead or indirect shake path) |
0x0045E120 |
GG_Flush |
sms | |
0x0045E13F |
GG_FlushDirtyLines |
re | the dirty-line blit region of GG_Flush (0x45E120, whose SEH body spans 0x45E120-0x45E356); a Ghidra mid-function split reached only by fall-through (no callers) — not separately callable |
0x0045E370 |
flushLineStats |
sms | |
0x0045E3A0 |
GG_RestoreSurfaces |
sms | |
0x0045E410 |
GG_WaitRetrace |
sms | |
0x0045E440 |
GG_VideoModesAvailable |
sms | |
0x00497330 |
G_FlipY |
sms | |
0x00497340 |
G_Init |
sms | |
0x004973F0 |
G_Shutdown |
sms | |
0x00497490 |
G_InitLineStats |
sms | |
0x004974C0 |
G_SetPalette |
sms | |
0x004974E0 |
G_SetBitmap |
sms | |
0x004974F0 |
G_SetClipBox |
sms | |
0x004975F0 |
G_SetFullClipBox |
sms | |
0x00497610 |
G_PushClipBox |
sms | |
0x00497650 |
G_PopClipBox |
sms | |
0x00497680 |
G_SetColor |
sms | |
0x004976D0 |
G_Point |
sms | |
0x00497700 |
G_UPoint |
sms | |
0x00497770 |
G_UHline |
sms | |
0x00497A10 |
G_Hline |
sms | |
0x00497A60 |
G_UVline |
sms | |
0x00497B00 |
G_Vline |
sms | |
0x00497B60 |
G_URect |
sms | |
0x00497BF0 |
G_Rect |
sms | |
0x00497D10 |
G_URect2 |
sms | |
0x00497D40 |
G_UBox |
sms | |
0x00497D90 |
G_Box |
sms | |
0x00497DE0 |
G_ClipLine |
sms | |
0x00498160 |
G_Line |
sms | |
0x004981A0 |
G_ULine |
sms | |
0x00498380 |
G_PatLine |
sms | |
0x004983E0 |
G_DrawYLR |
sms | |
0x00498410 |
G_SetScaleMax |
sms | |
0x00498420 |
G_Flush |
sms | |
0x00498430 |
G_ReverseVertices |
sms | |
0x00498480 |
G_Flip |
sms | |
0x004984F0 |
G_UPolygonFlip |
sms | |
0x00498570 |
G_PointFlip |
sms | |
0x004985E0 |
G_ULineFlip |
sms | |
0x00498610 |
G_LineFlip |
sms | |
0x00498640 |
G_CircleFlip |
sms | |
0x00498670 |
G_URectFlip |
sms | |
0x004986A0 |
G_SetFont |
sms | |
0x004986B0 |
G_Print |
sms | |
0x004988F0 |
G_PrintOutline |
sms | |
0x00498980 |
G_Printf |
sms | |
0x004989F0 |
G_StringWidth |
sms | |
0x00498A20 |
G_StringHeight |
sms | |
0x00498AF0 |
code |
sms | |
0x00498B90 |
clipT |
sms | |
0x00498CE0 |
clipB |
sms | |
0x00498E30 |
clipL |
sms | |
0x00498F90 |
clipR |
sms | |
0x004990F0 |
G_ClipDestPoly |
sms | |
0x00499200 |
G_SetHShake |
sms | |
0x00499240 |
G_SetVShake |
sms | |
0x00499280 |
G_Hline2 |
sms | |
0x004992B0 |
G_UHline2 |
sms | |
0x004992E0 |
G_Box2 |
sms | |
0x00499330 |
G_UBox2 |
sms | |
0x004B7910 |
G_AllocBitmapBuffer |
re | allocate a 0x112-byte bitmap buffer via the class allocator and clear its trailing flag |
0x004B7930 |
G_RelocBitmap |
sms | |
0x004B79B0 |
G_AllocBitmap |
sms | |
0x004B7A80 |
G_AllocSurfaceBitmap |
sms | |
0x004B7BF0 |
G_FreeSurfaceBitmap |
sms | |
0x004B7C30 |
RemapAdd |
sms | |
0x004B7C60 |
RemapRelocate |
sms | |
0x004B7CD0 |
G_LoadBitmap |
sms | |
0x004B7E10 |
G_RemapBitmapToPalette |
re | remap a bitmap's RGB triples to the nearest _curPalette index (sum-of-squared-channel-diff nearest-colour match) |
0x004B7F60 |
G_BlitToBrush |
sms | |
0x004B7FA0 |
G_BlitToScreen |
sms | |
0x004B7FE0 |
G_Blit |
sms | |
0x004B8460 |
G_ColorPrint |
sms | |
0x004B85F0 |
G_ColorStringWidth |
sms | |
0x004B8620 |
G_ColorStringHeight |
sms | |
0x004B8630 |
G_NextTab |
sms | |
0x004B8670 |
G_Scale |
sms | |
0x004B8710 |
G_ScaleFlip |
sms | |
0x004B87F0 |
G_AcTexture |
sms | |
0x004B8820 |
G_TextureFlip |
sms | |
0x004B8890 |
G_PerspectiveFlip |
sms | |
0x004B8920 |
G_HFlipBitmap |
sms | |
0x004B8960 |
G_DoubleBitmapY |
sms | |
0x004B8BF0 |
G_DoubleBitmapX |
sms | |
0x004B8D90 |
carefulDiv |
sms | |
0x004B8E10 |
NPM_clipTop |
sms | |
0x004B8F70 |
NPM_clipTri |
sms | |
0x004B90C0 |
NPM_clipAndScan |
sms | |
0x004B9430 |
NPM_FlatTri |
sms | |
0x004B9630 |
NPM_TextureLinearTri |
sms | |
0x004B9B90 |
NPM_TexturePerspectiveTri |
sms | |
0x004BA400 |
G_FloatFlatFlip |
sms | |
0x004BA500 |
G_FloatTextureFlip |
sms | |
0x004BA660 |
G_FloatPerspectiveFlip |
sms | |
0x004C619C |
G_UPatLine |
sms | |
0x004C6334 |
G_UBresenhamLine |
sms | |
0x004C6ECC |
G_UPolygon |
sms | |
0x004C77D0 |
G_SUPolygon |
sms | |
0x004C8A38 |
G_Polygon |
sms | |
0x004C8A74 |
G_SPolygon |
sms | |
0x004C8FD4 |
Horizon2d |
re | scanline fill for the solid horizon band — orders the span endpoints (hhigh/hxlow/hxhigh/hlow) and fills the sky/ground colour band into the raster surface; terminal step of _SolidHorizon (renderer.md §10) |
0x004C9224 |
NoHorizon |
re | off-screen fallback for _SolidHorizon when the tilted horizon line falls outside the viewport — emits no raster output |
0x004C924C |
SolidHorizon |
re | solid-colour sky/ground band: stores _sky_color_data/_ground_color_data, derives the horizon quad from the camera up-vector (top_up/right_up/forward_up) plus __amtMoveHorizon, then calls Horizon2d (on-screen) or NoHorizon (renderer.md §10) |
0x004C942C |
GouraudHorizon |
re | Gouraud sky/ground gradient: stages gradient-polygon vertices/colours (tilted by heading vector _headv_x/_headv_z) into 0x50FDA0-0x50FE40, then rasterizes them through the vector_table SH draw-opcodes (renderer.md §10) |
0x004CA028 |
G__AC_Texture |
sms | |
0x004CAE38 |
G__Texture |
sms | |
0x004CBD0B |
G__Perspective |
sms | |
0x004CBE7C |
G__ScaleBitmap |
sms | |
0x004CC8B0 |
DrawYLRP |
sms |
Wingman / group AI (WNG/GRP)¶
wingman.csv · page — 38 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x0045E460 |
WNGInit |
sms | |
0x0045E490 |
WNGAdd |
sms | |
0x0045E520 |
WNGRemove |
re | remove _curId from a wing; MSGSend 0x14 handoff (twin of GRPRemove) |
0x0045E630 |
WNGLeader |
re | first live member of a wing (twin of GRPLeader) |
0x0045E690 |
WNGWingman |
re | undetected twin of GRPWingman; materialized on apply |
0x0045E6E0 |
WNGWingmen |
sms | |
0x0045E710 |
WNGPart |
sms | |
0x0045E790 |
WNGWingmenNearby |
re | count wingmen within distance/alt (twin of GRPWingmenNearby) |
0x0045E8A0 |
WNGControl |
re | if wingman, optional FormationMove; return formControl (twin of GRPControl) |
0x0045E8F0 |
WNGLeaderLanding |
sms | |
0x0045E970 |
WNGFormationMove |
sms | |
0x0045EAC0 |
WNGSetControl |
re | set formation control; MSGSend subcode 10 |
0x0045EB30 |
WNGSetType |
re | set formation type; MSGSend subcode 9 |
0x0045EB70 |
WNGSetSpacingH |
re | set horizontal spacing; MSGSend subcode 7 |
0x0045EBB0 |
WNGSetSpacingV |
re | set vertical spacing; MSGSend subcode 8 |
0x0045EBF0 |
WNGSetStateTarget |
re | EnterState + set target; MSGSend 0x11 (twin of GRPSetStateTarget) |
0x0045ED90 |
WNGSendWM |
sms | |
0x0045EEF0 |
WNGResponseSize |
re | response capacity from the orders-block flags |
0x0045EF20 |
WNGAttackingObj |
re | count wing members attacking a target |
0x0045EFB0 |
WNGHumansFirst |
re | move human-controlled members to slot 0 |
0x0045F030 |
WNGInHumanWing |
re | true if id shares the player's wing |
0x0045F090 |
WNGPlayerWM |
sms | |
0x0045F100 |
WNGName |
re | format 'Wingleader'/'Wingman'/'Wingman %d' (twin of GRPName) |
0x0045F190 |
GRPInit |
sms | |
0x0045F1C0 |
GRPAdd |
sms | |
0x0045F250 |
GRPRemove |
sms | |
0x0045F360 |
GRPLeader |
sms | |
0x0045F410 |
GRPWingmen |
sms | |
0x0045F440 |
GRPPart |
sms | |
0x0045F5D0 |
GRPControl |
sms | |
0x0045F6A0 |
GRPFormationMove |
sms | |
0x0045F7F0 |
GRPSetControl |
sms | |
0x0045F860 |
GRPSetType |
sms | |
0x0045F8A0 |
GRPSetSpacingH |
sms | |
0x0045F8E0 |
GRPSetSpacingV |
sms | |
0x0045FC20 |
GRPResponseSize |
sms | |
0x0045FC50 |
GRPAttackingObj |
sms | |
0x0045FCE0 |
GRPHumansFirst |
sms |
Object / entity system & shape selection¶
objects.csv · page — 83 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00436B30 |
MoveObj |
sms | |
0x004382D0 |
MoveGoalValue |
re | resolve one move-goal operand by kind (heading/altitude/speed/...); executes CreateMoveGoal records for MoveObj |
0x00442C00 |
GRAPHICInit |
sms | init the 100-entry _graphics effect pool + per-type .SH handle table (crater/smoke/fire/exp/debris/chaff/flare/spd/mpd/lpd); see objects.md GRAPHIC effect spawning |
0x00442DE0 |
GRAPHICUpdate |
sms | step every live GRAPHIC entry via FUN_00442e10 (motion/fuse/adder emission), then _UpdateLoopSounds |
0x004431B0 |
GRAPHICAddYourObjs |
sms | |
0x004432D0 |
GRAPHICAddExp |
sms | spawn an explosion: random type-variation, chained debris/cluster-release/smoke children, MP mirror |
0x00462600 |
InitChain |
sms | |
0x00462620 |
RemoveFromChains |
sms | |
0x00462640 |
ChainRemoveCurObj |
re | unlink the current object from a service chain head; clears in-chain flag (entity +0x01 bit1) |
0x004626B0 |
ImmediateService |
sms | |
0x004626D0 |
ChainInsertCurObj |
re | ordered insert of the current object by service key (+0x68); honors correctChainPlacement |
0x004627B0 |
RemoveCurObj |
sms | |
0x004628B0 |
GetCurObj |
sms | |
0x00462980 |
PutCurObj |
sms | |
0x004629E0 |
PushCurObj |
sms | |
0x00462A20 |
PopCurObj |
sms | |
0x00462A50 |
ServiceObjects |
sms | |
0x00462B70 |
ChainMergeSorted |
re | merge the re-queue chain back into chainStart keeping +0x68 order |
0x00462C91 |
ProcessHitMsgs |
re | drain MSG 0x800B/0x800C remote hit events; raises event 0x4000 and spawns explosion via GRAPHICAddExp |
0x00462D40 |
ProcessEffectMsgs |
re | drain per-computer MSG 0x8003+n remote effect spawns: explosion / smoke / MANAdd |
0x00462E70 |
Service |
sms | |
0x004631B0 |
CheckForEvents1 |
sms | |
0x004631F0 |
CheckForEvents2 |
sms | |
0x00463730 |
PadlockTarget |
re | pick the object id the player is visually tracking (angle+distance gates, wingman fallback) |
0x00463900 |
NearbyGroupLeader |
re | return the group leader id when the current object flies in role 2 within 0xC800 |
0x00463980 |
MaybeCallEventProc |
sms | |
0x004639C0 |
CallEventProc |
sms | |
0x00463A20 |
CreateMove |
sms | |
0x00463AF0 |
CreateMoveGoal |
sms | |
0x00463B90 |
TimeAddSat |
re | saturating add to currentT, clamped at 0x7FFF |
0x00463BC0 |
ObjPlusAngleParm |
sms | |
0x00463BE0 |
ObjPlusDeltaParm |
sms | |
0x00463C50 |
WriteCmdBuf |
sms | |
0x00463CA0 |
WriteCmdBufProcptr |
sms | |
0x00463CC0 |
WriteCmdBufMove |
sms | |
0x00463CD0 |
WriteCmdBufEnd |
sms | |
0x00463CE0 |
FinishCmdBuf |
sms | |
0x00463D00 |
AllocCmdBuf |
sms | |
0x00463D40 |
ReadCmdBuf |
sms | |
0x00463E50 |
CancelCmdBuf |
sms | |
0x00463EA0 |
MaskEvents |
sms | |
0x00463EC0 |
CallDamageProc |
sms | |
0x00463F30 |
GetObjProc |
re | resolve a proc selector: entity override (+0x6C) else class proc (type +0x7D) |
0x00463F60 |
CallUtilProc |
sms | |
0x00463FA0 |
Ignore |
sms | |
0x00464040 |
Reaction |
sms | |
0x00464300 |
EnterState |
sms | |
0x00464420 |
InSearchArea |
sms | |
0x0046442B |
InSearchAreaBody |
re | Ghidra mid-function split: body of @InSearchArea@4 (entry +0xB); route-corridor DistToLine test |
0x004644F0 |
PreferredTargetId |
sms | |
0x00464520 |
PreferredProtectId |
sms | |
0x00464550 |
CloseToAnything |
sms | |
0x00464640 |
SetScenarioEndTime |
sms | |
0x00473A40 |
OBJEventProc |
sms | label-only in FA.SMS import; ApplySymbols materializes the function |
0x00473B40 |
OBJDamageProc |
sms | label-only in FA.SMS import; ApplySymbols materializes the function |
0x00473BE0 |
OBJProc |
sms | |
0x00473C10 |
Kill |
sms | |
0x00491240 |
OBJGet |
sms | |
0x00491250 |
OBJInit |
sms | |
0x004912C0 |
OBJShutdown |
sms | |
0x00491300 |
OBJAlloc |
sms | label-only in FA.SMS import; ApplySymbols materializes the function |
0x00491320 |
OBJStopAdding |
sms | |
0x00491340 |
OBJFindHumans |
sms | |
0x004913E0 |
OBJAdd |
sms | |
0x00491490 |
OBJSubtract |
sms | |
0x004914C0 |
OBJAlias |
sms | |
0x00491530 |
OBJCreateAliases |
sms | |
0x00491610 |
OBJAliasPreferred |
sms | |
0x00491670 |
OBJAliasAll |
sms | |
0x00491720 |
OBJAliasWaypoint |
sms | |
0x00491780 |
OBJAliasForMulti |
sms | |
0x004917D0 |
OBJNextAliasForMulti |
sms | |
0x004917F0 |
OBJTempAlias |
sms | |
0x00491810 |
OBJSetControl |
sms | |
0x004918D0 |
OBJHumanName |
sms | |
0x004A6B10 |
ResolveTypeRecord |
re | resolve the OT/NT/PT/JT type record from the MM handle at wrapper +0x0F (MMAccessE when +0x0E bit1 set); SetupOT's first step |
0x004A6EB0 |
SetupOT |
sms | |
0x004A71C0 |
LoadShapeVariantPair |
re | load the _a slot; aircraft (obj_class & 0xC000) also load the _b slot (+0x1B) |
0x004A71E0 |
LoadShapeSlot |
re | resolve one shape-slot filename to a loaded pointer via RMAccess (was proposed type_load_shape_slot) |
0x004A7200 |
SetupNT |
sms | |
0x004A7220 |
SetupPT |
sms | |
0x004A7230 |
SetupJT |
sms | |
0x004AB450 |
ShapeSetup |
sms |
AI interpreter (CT)¶
ai.csv · page — 122 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00464C60 |
CTInit |
sms | |
0x00464C80 |
CTShutdown |
sms | |
0x00464C90 |
CTRespondToCancelCmdBuf |
sms | |
0x00464CD0 |
CTLoadProgram |
re | load/switch the BI CODE resource by name (RMAccess 0x8000); set IP=base, CTResetPC |
0x00464DB0 |
CTResetPC |
re | reset IP=base and line=1; if arg!=0 also zero stack depth |
0x00464DE0 |
CTVarDiff |
sms | |
0x00464E20 |
CTEval_time |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464E30 |
CTEval_do_nothing |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464E40 |
CTEval_do_evade |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464E50 |
CTEval_do_attack |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464E60 |
CTEval_do_radar_launch |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464E70 |
CTEval_do_ir_launch |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464E80 |
CTEval_do_hit |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464E90 |
CTEval_tgtattackingme |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464ED0 |
CTEval_tgtattackinganyone |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464F10 |
CTEval_tgt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464F50 |
CTEval_tgtclass |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464F90 |
CTEval_tgtisfighter |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464FB0 |
CTEval_tgtisbomber |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464FD0 |
CTEval_tgtisplane |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00464FF0 |
CTEval_tgtisship |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465000 |
CTEval_tgtissam |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465010 |
CTEval_tgtisaaa |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465020 |
CTEval_tgthumancontrol |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465040 |
CTEval_maxrange |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465060 |
CTEval_maxrangediff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465080 |
CTEval_bestrange |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465090 |
CTEval_bestrangediff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004650A0 |
CTEval_radar |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004650B0 |
CTEval_tgtradar |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004650E0 |
CTEval_ir |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004650F0 |
CTEval_tgtir |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465120 |
CTEval_tgtoffbeam |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465150 |
CTEval_tgtahead |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465180 |
CTEval_tgtfacing |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004651D0 |
CTEval_hrzdisttotgt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465220 |
CTEval_disttotgt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465240 |
CTEval_htotgt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465290 |
CTEval_ptotgt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004652E0 |
CTEval_tgtaspectangle |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465380 |
CTEval_canclimb |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004653A0 |
CTEval_speed |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004653B0 |
CTEval_speeddiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004653D0 |
CTEval_minspeed |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004653E0 |
CTEval_minspeeddiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004653F0 |
CTEval_cornerspeed |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465400 |
CTEval_corner |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465410 |
CTEval_cornerspeeddiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465420 |
CTEval_maxrudderh |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465430 |
CTEval_maxrudderp |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465440 |
CTEval_maxspeed |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465450 |
CTEval_maxspeeddiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465460 |
CTEval_betterspeed |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465480 |
CTEval_twr |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465490 |
CTEval_twrdiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004654A0 |
CTEval_bettertwr |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004654C0 |
CTEval_turnrate |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004654D0 |
CTEval_turnratediff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004654F0 |
CTEval_turnradius |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465500 |
CTEval_turnradiusdiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465510 |
CTEval_alt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465540 |
CTEval_altdiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465560 |
CTEval_maxalt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465590 |
CTEval_maxaltdiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004655B0 |
CTEval_minalt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004655C0 |
CTEval_minaltdiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004655E0 |
CTEval_waypointalt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465640 |
CTEval_disttowaypoint |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004656B0 |
CTEval_cloudalt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004656C0 |
CTEval_skill |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004656D0 |
CTEval_h |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465710 |
CTEval_p |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465730 |
CTEval_b |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465750 |
CTEval_hdiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465770 |
CTEval_pdiff |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465790 |
CTEval_any |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004657A0 |
CTEval_engagep |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004658A0 |
CTEval_wingapproach |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465960 |
CTEval_wingcombat |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004659F0 |
CTEval_wm_hspacing_is |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465A00 |
CTEval_wm_vspacing_is |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465A10 |
CTEval_wm_formation_is |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465A20 |
CTEval_wm_control_is |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465A30 |
CTDo_exit |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465A50 |
CTDo_restart |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465A70 |
CTDo_maneuver |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465AD0 |
CTPop |
re | eval-stack pop; underflow raises CTError(4) |
0x00465B00 |
CTDo_print |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465B60 |
CTDo_printnum |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465BC0 |
CTDo_play |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465C20 |
CTDo_rudder |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465C90 |
CTReadAngle |
re | pop then clamp [-90,90] x182 (binary degrees) |
0x00465CC0 |
CTDo_move |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465D40 |
CTReadHeading |
re | pop then normalize [0,359] x182 |
0x00465DA0 |
CTReadAngle180 |
re | pop then clamp [-180,180] x182; passthrough sentinel 0x7FFFFFFF |
0x00465DE0 |
CTReadDuration |
re | pop then clamp [0,15] |
0x00465E00 |
CTReadSpeed |
re | pop then clamp [COMinSpeed,COMaxSpeed] read live |
0x00465E20 |
CTDo_movetoalt |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00465EA0 |
CTDo_turn |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00466052 |
CTDo_yoyo |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004660C0 |
CTDo_circle |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004661A0 |
CTDo_homeangle |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00466290 |
CTPush |
re | eval-stack push; overflow (>0x13) raises CTError(5) |
0x004662C0 |
CTDo_homepos |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004663E0 |
CTDo_uhomepos |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004663F0 |
CTDo_jink |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004664C0 |
CTDo_invert |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004664F0 |
CTDo_btoh |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00466540 |
CTDo_splits |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00466590 |
CTDo_immelman |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004665E0 |
CTDo_wm_break |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00466630 |
CTDo_wm_approach |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00466700 |
CTDo_wm_hspacing |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00466750 |
CTDo_wm_vspacing |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004667A0 |
CTDo_wm_formation |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004667E0 |
CTDo_wm_control |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x00466820 |
CTError |
re | map error code 1..0xc to a string and ErrorExit('Chuck Talk error: %s, line %u') |
0x004668F0 |
CTRestoreState |
re | restore _ctState from _ctStateCheckpoint (or zero it) |
0x00466920 |
CTSaveState |
re | copy _ctState to the heap checkpoint, then zero it |
0x00466970 |
CTExecProgram |
sms | |
0x00466A80 |
CTStep |
re | fetch one opcode and dispatch via switch; CALL_BY_NAME(0x27) resolves via SMAddress then self-patches to CALL_DIRECT(0x26) |
0x004670E0 |
CTVarPtr |
re | return &_ctState[i] for script var index i in [0,4]; out-of-range raises CTError(3) |
Input — joystick / serial / modem¶
input.csv · page — 18 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00494270 |
ReadSticksRaw |
sms | poll X/throttle/rudder/POV via ReadDevice gated by joystickFunctions bits (1/4/8/0x10) |
0x004942D0 |
InitJoysticks |
sms | joyGetNumDevs (cap 16); probe each joyGetPos+joyGetDevCapsA(0x194) into joystickCaps; build joystickMask; assign X/Y/throttle/rudder/POV device roles -> joystickFunctions |
0x00494430 |
GetJoystickType |
sms | JOYRESULT enum: 4=uninit 3=absent 0=legacy(JOYINFO) 1=extended(JOYINFOEX); decides by caps axes<3 && buttons<4 |
0x004944A0 |
ReadDevice |
sms | read one device joyGetPos/joyGetPosEx into joystickInfo/Ex[id]; 50ms (0x32) rate-limit via joystickLastRead + timeGetTime |
0x00494580 |
ScaleToRange |
sms | clamp/scale a float axis into calibrated min/center/max range via __ftol; stack-arg helper for NormalizeStick |
0x004946B0 |
NormalizeStick |
sms | normalize X/Y/throttle/rudder to ints; first-read center auto-capture (gotCenterX/Y/R -> DAT_00554ec4/ec8/ebc); calls ScaleToRange |
0x00494A50 |
GetPOV |
sms | read POV hat; map centidegrees/100 to keypad scancode 0x48/0x4D/0x50/0x4B (U/R/D/L); 0xFFFF=centered->0 |
0x00494AE0 |
ASynchJoystick |
sms | poll buttons via ReadJoystickButtons; edge-detect new presses into buttonPresses[] per JOYCAPS button count |
0x00494B50 |
ReadJoystickButtons |
sms | return button mask from joystickInfo+0x14 (legacy) or joystickInfoEx+0x28 (extended) |
0x00499CF0 |
MOUSESetLimits |
sms | set cursor clamp limits (mouse-ring limit fields DAT_00560f38/f3a) |
0x00499D10 |
MOUSESetPos |
sms | center mousePos to screen/2 (DAT_0055c06a/c06c halved) |
0x00499D40 |
MOUSECenter |
sms | FA.SMS symbol at 0x499d40 not split by current Ghidra inventory; ~16-byte helper between MOUSESetPos and MOUSERead — candidate to define |
0x00499D50 |
MOUSERead |
sms | dequeue next event from 16-entry ring (critical-section); returns queued pos+buttons or current mousePos/mouseButtons if empty |
0x00499DF0 |
MOUSEInit |
sms | InitializeCriticalSection(mouse_critical_section); reset ring indices; set initialized flag DAT_00501598 |
0x00499E30 |
MOUSEShutdown |
sms | DeleteCriticalSection(mouse_critical_section) if initialized |
0x00499E50 |
MOUSEEvent |
sms | WndProc mouse handler WM_MOUSEMOVE/L/R DOWN+UP (0x200/201/202/204/205); edge-count mouseButtonPresses & DAT_00560ef1; push to ring; update mousePos |
0x0049B1D0 |
RunSerialConfigurationScreen |
sms | large device-config dialog for direct-serial link (baud/port/etc.); ~5.4KB UI — could alternatively be shell-ui |
0x0049C780 |
RunModemConfigurationScreen |
sms | large device-config dialog for modem link (init string/dial/port); ~2.6KB UI — could alternatively be shell-ui |
Terrain (T_)¶
terrain.csv · page — 82 named functions
| VA | Symbol | Src | Role | |
|---|---|---|---|---|
0x004A7310 |
T_InitPlane |
sms | spawns player plane on terrain at mission start (FMInitPlane/FMUpdatePlaneFields/EnterState/PLANESetFeetWet); writes _cg mirror fields | |
0x004A73B0 |
T_AddObj |
sms | add a static object/building to the terrain object database (1642 B) | |
0x004A7A40 |
T_AddYourObjs |
sms | terrain "add your objects" service pass | |
0x004A7D70 |
T_ImmediateVisibility |
sms | ||
0x004A7DF0 |
T_ObjList |
sms | ||
0x004A7E50 |
T_Render |
sms | ||
0x004A7F20 |
T_InitForestProc |
sms | label-only in FA.SMS import; ApplySymbols materializes (0x20 B setup preceding T_ResolveDecorShapes) | |
0x004A7F40 |
T_ResolveDecorShapes |
re | walk a decoration brush-list (stride 0x1a) resolving each entry's SH via _RMAccess into +2; called by T_InitWaterProc(&_waterList) / T_InitCloudProc(&_cloudList) | |
0x004A7F70 |
T_ForestProc |
sms | scatters forest decoration (desert vs _forestList) via T_ScatterGrid | |
0x004A8090 |
T_ScatterGrid |
re | 2^n x 2^n tiling loop invoking T_ScatterDecorTile; called by every T_*Proc (evidence: T_ForestProc/T_MooseProc/T_WaterProc) | |
0x004A8130 |
T_ScatterDecorTile |
re | place one decoration cluster per tile: per-band distance LOD, T_GetLeaf gate, T_Info altitude, T_QueueDecor | |
0x004A83E0 |
T_InitForest1Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8400 |
T_Forest1Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8520 |
T_InitForest2Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8540 |
T_Forest2Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8660 |
T_InitFarmProc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8670 |
T_FarmProc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8730 |
T_InitMooseProc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8750 |
T_MooseProc |
sms | scatters "moose" decoration (desert vs _mooseList) | |
0x004A8870 |
T_InitVietRicePaddy1Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8880 |
T_VietRicePaddy1Proc |
sms | Vietnam theater rice-paddy decoration proc | |
0x004A8950 |
T_InitVietRicePaddy2Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8960 |
T_VietRicePaddy2Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8970 |
T_InitVietRicePaddy3Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8980 |
T_VietRicePaddy3Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8990 |
T_InitVietPalms1Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A89A0 |
T_VietPalms1Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A89B0 |
T_InitVietPalms2Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A89C0 |
T_VietPalms2Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A89D0 |
T_InitVietPalms3Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A89E0 |
T_VietPalms3Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A89F0 |
T_InitVietTrees1Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8A00 |
T_VietTrees1Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8A10 |
T_InitVietTrees2Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8A20 |
T_VietTrees2Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8A30 |
T_InitVietTrees3Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8A40 |
T_VietTrees3Proc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8A50 |
T_InitVietWaterBuffaloProc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8A60 |
T_VietWaterBuffaloProc |
sms | label-only in FA.SMS import; ApplySymbols materializes | |
0x004A8A70 |
T_InitWaterProc |
sms | copies _waterCfg string then resolves _waterList via T_ResolveDecorShapes | |
0x004A8AB0 |
T_WaterProc |
sms | scatters water-surface decoration; lazy-inits via T_InitWaterProc | |
0x004A8B90 |
T_InitCloudProc |
sms | resolves _cloudList via T_ResolveDecorShapes(&_cloudList 0x50C298) | |
0x004A8BA0 |
T_CloudProc |
sms | label-only; the global ambient proc (set in T_Init2) | |
0x004A8C30 |
T_QueueDecor |
re | append an entry (short id + 0x17-B record) to the per-frame decor/comment list at _decorListEnd (DAT_0057336c); dedupes by id | |
0x004A8CC0 |
T_ObjIsVisible |
re | per-object render-flag/type visibility test (DAT_00573396 flags vs type table at 0x50A6B8); used by T_AddVisibleObjs | |
0x004A8D30 |
T_Normal |
sms | ||
0x004A8E50 |
T_LeafOp |
sms | emit one terrain leaf's geometry; 14-B entry + split body FUN_004a8e5e | |
0x004A9660 |
T_Make |
sms | build terrain scene: pick LOD tile size (_lodTileSize 0x20-0x100) + detail (_lodDetail 1/2/4/8) by view distance/DAT_00573394; quadtree tessellate into _cellArray; run ambient/object passes; emit leaf list | |
0x004A9BB0 |
T_RunAmbientProcs |
re | iterate _ambientProcs[0..0x10] (17 decoration procs) calling each with its index; gated by DAT_00573396&0x8000 and _ambientSuppress | |
0x004A9C20 |
T_AddVisibleObjs |
re | iterate _objPtrs[1.._nextObjId]; for visible in-bounds objects run ImmediateService and T_QueueDecor (via GetCurObj/PutCurObj) | |
0x004A9D00 |
T_BuildQuadCell |
re | build one quad cell in _cellArray (sample T_GetLeaf at 4 corners, T_QuadAltitude/SetFlags) | |
0x004A9E20 |
T_QuadAltitude |
re | representative altitude of a cell (clamped min/max blend of corner heights scaled by DAT_00573392) | |
0x004A9EA0 |
T_QuadSetFlags |
re | set leaf flat-bit (+0xF|1) vs sloped-bit (+0xE|0x80) from corner-delta bytes | |
0x004A9ED0 |
T_ViewBounds |
re | rotate view box by heading (_Rotate2) to compute cell-space AABB DAT_0057334a..DAT_00573350 | |
0x004AA070 |
T_SubdivideCells |
re | LOD pass: walk _cellArray, subdivide cells finer than _lodDetail via 4x T_BuildQuadCell; T_CellScreenBounds cull; caps at 500 cells | |
0x004AA260 |
T_CellScreenBounds |
re | cell world extent (<<5) AND'd with view bounds via _BoundsAnd (cull test) | |
0x004AA2B0 |
T_CompactCells |
re | compact _cellArray dropping dead (0xffff) cells; rewrites _cellCount and optional carried index | |
0x004AA380 |
T_SortCells |
re | in-place quicksort of _cellArray (stride 0x16) via comparator (*_cellCompare 0x580B9C) and _Swapmem | |
0x004AA440 |
T_CountCells |
re | tally cells into front/back counts (returns front count); drives T_Make's subdivide-again loop | |
0x004AA4A0 |
T_EmitCells |
re | per live cell: T_CellTmapLookup + optional T_Normal, then T_LeafOp to emit geometry | |
0x004AA620 |
T_InitDictionary |
sms | init the tmap dictionary (_tdic) | |
0x004AA680 |
T_InitDictionaryEntry |
sms | ||
0x004AA790 |
T_NamedTmaps |
sms | build named-tmap list (_tlist) from dictionary | |
0x004AA7E0 |
T_CompareTlist |
sms | qsort comparator for _tlist (label-only; xref-only in globals.csv) | |
0x004AA820 |
T_SortTmapList |
sms | sort _tlist for binary search | |
0x004AA840 |
T_CellTmapLookup |
re | binary-search _tlist by packed cell coord ((y&~3)<<16|(x&~3)) to pick the tile texture; gated by _lowMemory/_tlistSize/DAT_00573396&2 | |
0x004AACA0 |
T_InitHorizonProc |
sms | RMAccess stars.SH/moon.SH/sun.SH into _starsH/_moonH/_sunH | |
0x004AACE0 |
T_HorizonProc |
sms | 1-B stub in current Ghidra (single RET / proc-ptr slot); SMS underscore differs from current label | |
0x004AACFE |
T_DrawHorizon |
re | horizon/sky band render: _T_Info horizon sample + _WRMakeHazeList + _currentTintTable sky colors + _hackSky/_clip* extents; static, no SMS symbol | |
0x004AB860 |
BrushFromIndex |
sms | ||
0x004ABAB0 |
T_Info |
sms | ||
0x004C5D30 |
T_InitDatabase |
sms | set _dbDynamicLow/High to _dbaseLow bounds (dynamic terrain-object DB) | |
0x004C5D50 |
T_ShutdownDatabase |
sms | zero _dbDynamicLow/High | |
0x004C5D60 |
T_Init |
sms | ||
0x004C5D70 |
T_Load |
sms | load |
|
0x004C5F40 |
T_StripTildes |
re | remove '~' chars from a filename in place; used twice by T_Load | |
0x004C5F60 |
T_Init2 |
sms | ||
0x004C5FA0 |
T_Shutdown |
sms | mirror of T_Init: free handle + clear dictionary/list (used by T_Load on map change) | |
0x004C6020 |
T_StopAdding |
sms | OBJStopAdding (unless _curScreen==3) + OBJFindHumans | |
0x004C6040 |
T_GetLeaf |
sms | return 3-byte leaf ptr: fine grid (th+0x91,stride th+0x89) if size | _borderLeaf (0x50CE4C) |
0x004C9624 |
T_InterpAltNW |
sms | barycentric altitude interpolation in the NW triangle of a leaf quad (64-bit divide of corner heights) | |
0x004C9770 |
T_InterpAltSE |
sms | label-only in FA.SMS import; SE-triangle counterpart (folded into InterpAltNW tail today) |
Weapons — projectiles / seekers / ECM (PROJ)¶
weapons.csv · page — 55 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004C06A0 |
PROJInit |
sms | |
0x004C0710 |
PROJGetTargetPos |
sms | |
0x004C0820 |
PROJAccurateHardPos |
sms | |
0x004C0870 |
PROJSetTarget |
sms | |
0x004C0960 |
PROJLockUpdate |
sms | |
0x004C0A90 |
PROJAdd |
sms | |
0x004C1120 |
PROJSpeed |
sms | |
0x004C1170 |
PROJEngineState |
sms | |
0x004C11B0 |
PROJMoveProc |
sms | |
0x004C1630 |
PROJGuideToTarget |
re | CreateMove toward lock target (proportional guidance) |
0x004C1660 |
PROJGuideLoft |
re | loft/high-trajectory guidance when range>=type+0x10D |
0x004C1720 |
PROJHoldCourse |
re | hold current commanded angles (fly straight) |
0x004C1760 |
PROJGuideToSun |
re | steer IR seeker toward the sun when decoyed |
0x004C17A0 |
PROJUpdateWeave |
re | random weave/jink aim offsets from type weave amp |
0x004C17F0 |
PROJSunInSeeker |
re | IR seeker sun/sky FOV check (decoy source) |
0x004C1870 |
PROJDamageProc |
sms | |
0x004C1C10 |
PROJBuildName |
re | compose ' |
0x004C1CC0 |
PROJEventProc |
sms | label-only in FA.SMS; ApplySymbols materializes the function |
0x004C1F10 |
PROJIsLockableTarget |
re | predicate: valid non-player lock candidate |
0x004C1F50 |
PROJProc |
sms | |
0x004C20C0 |
PROJHit |
sms | |
0x004C2170 |
PROJFire |
sms | |
0x004C24B0 |
PROJAimAngles |
re | compute launch/boresight angles in the launcher frame (gimbal-limited) |
0x004C26F0 |
PROJFireSound |
sms | |
0x004C2860 |
PROJInFOV |
sms | |
0x004C2B50 |
PROJTargetSignal |
re | seeker signal/lock quality (contrast, IR notch, look-down, clutter) |
0x004C2E40 |
PROJInNotch |
re | Doppler-notch/beaming detection (defeats pulse-doppler) |
0x004C2EB0 |
PROJRadarIsOn |
sms | |
0x004C2F20 |
PROJLock |
sms | |
0x004C31F0 |
PROJIRSensorOn |
re | IR-seeker detectability duty gate |
0x004C3250 |
PROJProximityFuze |
re | closest-approach detonation decision + hit roll |
0x004C3360 |
PROJTargetIsFastAir |
re | predicate: fast maneuvering aircraft (class4, speed>0x3A00) |
0x004C3380 |
PROJHitChance |
sms | |
0x004C3830 |
PROJApplyPkCurve |
re | interpolate and clamp the running Pk penalty |
0x004C3890 |
PROJRangePk |
re | range->Pk envelope lookup from the type range table |
0x004C3960 |
PROJSizePk |
re | target-size/RCS vs weapon-sensitivity Pk scalar |
0x004C39A0 |
PROJLaunchDevice |
sms | |
0x004C3AF0 |
PROJRetargetMissilesOnDevice |
sms | |
0x004C3C40 |
PROJGuideToDevice |
re | steer a seduced missile toward the decoy device |
0x004C3CA0 |
PROJRemove |
sms | |
0x004C3DD0 |
PROJRetargetMissiles |
sms | |
0x004C3EB0 |
PROJMakeBombEq |
sms | |
0x004C4030 |
PROJChangeBombEq |
sms | |
0x004C4050 |
PROJBombPos |
sms | |
0x004C4100 |
PROJSelectTarget |
sms | |
0x004C4390 |
PROJScoreTarget |
re | per-candidate seeker scoring callback (dist+penalties) |
0x004C4700 |
PROJServiceWeapon |
sms | |
0x004C5000 |
PROJSetReattackTimer |
re | set a random AI re-attack delay from aggression |
0x004C5050 |
PROJAimGunSolution |
re | gun/dumb-weapon aim point with dispersion + terrain clamp |
0x004C5270 |
PROJHasMissileOnTarget |
re | predicate: already have a guided missile locked on target |
0x004C52D0 |
PROJSelectStore |
re | AI: pick the best weapon store for a target |
0x004C5570 |
PROJMissileAttacking |
sms | |
0x004C5670 |
PROJSendCollateralDamages |
sms | |
0x004C58A0 |
PROJAreaWeaponHit |
re | special-warhead detonation (submunition scatter + collateral) |
0x004C5D00 |
PROJMinScatterAngle |
re | clamp a scatter angle away from zero for submunition dispersion |
3D render core / SH interpreter (GR)¶
render-core.csv · page — 163 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004CD588 |
sincos |
re | sin/cos via sin_table lookup+lerp; AL=frac AH=index; core of GRSinCos/angles_2_matrix/all rotates |
0x004CD5DF |
isqrt16 |
re | tail-call wrapper of FUN_004cd5ee (16-bit integer sqrt) |
0x004CD5EE |
isqrt16_body |
re | Newton-Raphson 16-bit integer square root (magnitude-classed); used by distance/normalize |
0x004CD7A0 |
GRACos |
sms | public arccos wrapper over acos |
0x004CD7B4 |
acos |
re | arccos via DAT_00515dcc table lookup+lerp |
0x004CD834 |
GRSetLightSource |
sms | store world light dir into _worldLightSource/515ED2/515ED4 |
0x004CD854 |
SetShading |
re | thin wrapper -> SetShadingTable (renderer span); rebuilds shade LUT |
0x004CD8B0 |
Sun |
re | lighting dot product of _worldLightSource against current matrix rows (518384/38A/390) |
0x004CD8F0 |
clip_edge_right |
re | Sutherland-Hodgman clip vs screen edge (outcode bit4); vbuf/vbuf2 ping-pong; calls ecode_pnt |
0x004CD9DA |
clip_edge_left |
re | S-H clip vs screen edge (outcode bit8); vbuf ping-pong |
0x004CDAC6 |
clip_edge_top |
re | S-H clip vs screen edge (outcode bit2); vbuf ping-pong |
0x004CDBB3 |
clip_edge_bottom |
re | S-H clip vs screen edge (outcode bit1); vbuf ping-pong |
0x004CDCA8 |
do_nop |
sms | SH opcode 0x34 no-op handler |
0x004CDCB8 |
render_3d |
re | scene traversal: sets viewport from _cb clip box; inits sort list; T_AddYourObjs; dispatches dddEntry via vector_table; painter-sorts cur_sort_list then draws via PTR_FUN_0051839c |
0x004CDEB4 |
setup_view_projection |
re | per-frame view/projection setup (aspect/head vectors/frustum) from zoom + screen dims |
0x004CE4A8 |
set_render_mode |
re | tail into check_flat body; installs perspective-path rasterizer fn-ptr table (5184AC-518674/5185F0-51861C) |
0x004CE4B4 |
check_flat |
re | pick flat vs perspective render path from matrix (m4=m2=m8=m6=0 && m5~0x7FFF); swaps the rasterizer fn-ptr dispatch tables; sets DAT_00518679/515F84 |
0x004CE784 |
load_normal_table |
re | copy lighting normal_table (or DAT_00518620 variant) into working DAT_00518484 by in_AL selector |
0x004CE7BC |
load_xlate_rotate_pnt |
re | hand-asm vertex load+translate+rotate helper (axis-select on DAT_00515f84) |
0x004CE7F7 |
mxmul |
re | hand-asm matrix*scalar accumulate helper (axis-select) |
0x004CE89C |
compute_axis_check |
re | derive axis_check_type from dominant matrix axis (m3/m6/m9 vs aspect); selects transform/cull variant |
0x004CE968 |
sort_objs_wrapper |
re | painter depth sort wrapper over _SortObjs_8 |
0x004CEB00 |
rotate_vec_roll |
re | rotate _xv/_zv by cached sin/cos (roll axis) helper of rotate_matrix_roll |
0x004CEB70 |
rotate_matrix_roll |
re | object-instancing: concat roll rotation into current matrix (m1..m9) via sincos |
0x004CED44 |
rotate_vec_pitch |
re | rotate _yv/_zv (pitch axis) helper of rotate_matrix_pitch |
0x004CEDB8 |
rotate_matrix_pitch |
re | object-instancing: concat pitch rotation into current matrix via sincos |
0x004CEF8C |
rotate_vec_yaw |
re | rotate _xv/_yv (yaw axis) helper of rotate_matrix_yaw |
0x004CF000 |
rotate_matrix_yaw |
re | object-instancing: concat yaw rotation into current matrix via sincos |
0x004CF270 |
code_pnt |
re | compute 5-bit frustum outcode of a projected point (BP/BX bounds) |
0x004CF2A4 |
ecode_pnt |
re | extended outcode (near-plane aware) returned in AL; used by clippers |
0x004CF2D0 |
matrix_from_angle3 |
re | build 3x3 rotation matrix (3x FUN_004cf410) for MakeObj/ViewRotationMatrix |
0x004CF328 |
mult_point_by_matrix_asm |
re | hand-asm point*matrix (register-only; empty decompile) — MultPointByMatrix core |
0x004CF410 |
matrix_row_asm |
re | hand-asm matrix row build (register-only; empty decompile) |
0x004D028C |
cull_bbox_viewspace |
re | view-space bounding-box/near-plane visibility test; returns clip code; called by GRAddBrentObj & do_drawobj000 |
0x004D0494 |
get_sort_dist |
re | painter sort key = max|Δ|+quarters of others + per-obj bias (from _xv32/_yv32/_zv32) |
0x004D057C |
GRAddBrentObj |
sms | |
0x004D0798 |
draw_brent_obj |
re | per-object shape-draw callback (PTR_FUN_0051839c target): restore saved matrix/viewer; ShapeSetup(brentObjId); object rotate (roll/pitch/yaw); lighting matrix; check_flat; SetShadingTable; interpret shape stream with _bdrawObj=1 |
0x004D0C2F |
sh_op_BA |
re | SH opcode 0xBA handler |
0x004D0C50 |
do_drawobj000 |
sms | |
0x004D0C8A |
sh_op_6A |
re | SH opcode 0x6A handler (render-state/geometry; 1409 bytes) |
0x004D120B |
sh_op_28 |
re | SH opcode 0x28/0x30 handler |
0x004D1421 |
sh_op_D6_pre |
re | 3-byte pre-adjust falling into sh_op_5A (op 0xD6) |
0x004D1424 |
sh_op_5A |
re | SH opcode 0x5A handler (epic #52 placeholder name; 623 bytes) |
0x004D1694 |
sh_op_20 |
re | SH opcode 0x20 handler |
0x004D17BC |
do_shape_name |
sms | SH opcode 0x42 (SourceName): consume null-terminated shape name into _shapeName |
0x004D17E0 |
sh_op_stub |
re | shared no-op stub for 10 unassigned SH opcodes |
0x004D17F0 |
sh_op_00 |
re | SH opcode 0x00 (EndObject) handler entry (1 byte; falls into do_short_eof) |
0x004D17F4 |
do_short_eof |
sms | SH opcode 0x1E (ShortEOF): plain ret — returns from the current interpreter call frame (ends an Unmask/selector-called fragment; at top level ends the object). Trailing 0x1E runs are alignment after the return |
0x004D17F8 |
sh_op_3A |
re | SH opcode 0x3A handler |
0x004D18F4 |
sh_op_08 |
re | SH opcode 0x08 handler |
0x004D1974 |
sh_op_72 |
re | SH opcode 0x72 handler (epic #52 placeholder name) |
0x004D1984 |
sh_op_96 |
re | SH opcode 0x96 handler (856 bytes) |
0x004D1E5C |
do_vertexbuffer |
re | SH opcode 0x82 (VertexBuffer) + 0x02/0x04/0x0A: push vertex batch into global pool at push_at/8 |
0x004D1ECC |
sh_op_A2 |
re | SH opcode 0xA2/0xAE handler |
0x004D1EDC |
sh_op_7A |
re | SH opcode 0x7A handler (epic #52 placeholder name) |
0x004D1F24 |
sh_op_74 |
re | SH opcode 0x74/0x7C/0x8E/0x9C handler (6-byte) |
0x004D1F2C |
sh_op_76 |
re | SH opcode 0x76 handler |
0x004D1F34 |
sh_op_22 |
re | SH opcode 0x22/0x7E handler |
0x004D1FC0 |
sh_op_80 |
re | SH opcode 0x80 handler (epic #52 placeholder name; 796 bytes) |
0x004D225E |
sh_op_1A |
re | SH opcode 0x1A handler |
0x004D2278 |
do_unmask |
re | SH opcode 0x12 (Unmask): dispatches the target sub-stream via the vector_table call-form; the callee chain runs until its ShortEOF (0x1E) rets; control resumes after the opcode |
0x004D22A8 |
do_sfcal_long |
sms | SH opcode 0x6E (UnmaskLong) |
0x004D22D4 |
do_ifdestroyed |
sms | SH opcode 0xAC (JumpToDamage): esi+=rel16 if _destroyed (0x50C39C) |
0x004D22FC |
do_no_overlap |
sms | SH opcode 0xB8: clears overlap/collision flag via FUN_004d426c |
0x004D2318 |
do_jumptodetail |
re | SH opcode 0xA6 (JumpToDetail): skip rel16 when _detail(0x515EEE) < threshold |
0x004D2344 |
do_use_terrain_detail |
sms | SH opcode 0xB2 |
0x004D2360 |
sh_op_B0 |
re | SH opcode 0xB0 handler |
0x004D2380 |
do_if_not_effect |
sms | SH opcodes 0x14/0x16/0x3C/0xA8/0xAA/0xC0: conditional skip keyed on effects setting |
0x004D23AC |
sh_op_6C |
re | SH opcode 0x6C (draw-order selector): compares object-record field [w0] to w1; ALWAYS renders both sub-chains — calls one (returns at its ShortEOF) and tail-continues the other; the condition only swaps the order (painter's sorting). Targets: call=opd+w3+8 / continue=opd+6+w2; 13/14/16-byte sizes are the trailing embedded 38/48/50 jump |
0x004D2450 |
sh_op_06 |
re | SH opcode 0x06 (plane-test draw-order selector): sign of nx(x+_xv)+ny(y+_yv)+nz*(z+_zv) picks the order; both sub-chains always render (call one / continue other). Operand: 3×(coeff i16 + coord i16) + size u16 + call-rel16 + embedded jump; call=opd+16+rel / continue=next instruction (opd+14+size) |
0x004D24F8 |
sh_op_0C |
re | SH opcode 0x0C: two-axis (y/z) variant of the 0x06 plane-test draw-order selector; operand 2×(coeff+coord) + size u16 + call-rel16 + embedded jump; call=opd+12+rel / continue=next instruction |
0x004D2580 |
sh_op_0E |
re | SH opcode 0x0E: two-axis (x/z) variant of the 0x06 plane-test draw-order selector (same layout as 0x0C) |
0x004D2608 |
sh_op_10 |
re | SH opcode 0x10: two-axis (x/y) variant of the 0x06 plane-test draw-order selector (same layout as 0x0C) |
0x004D2690 |
sh_op_18 |
re | SH opcode 0x18 handler |
0x004D2740 |
sh_op_84 |
re | SH opcode 0x84 handler (epic #52 placeholder name) |
0x004D2798 |
load_dest |
re | interpreter helper: load destination operand |
0x004D2880 |
sh_op_1C |
re | SH opcode 0x1C/0x88 handler (perspective-path variant swapped by check_flat) |
0x004D2910 |
sh_op_26 |
re | SH opcode 0x26 handler |
0x004D29EC |
sh_op_2A |
re | SH opcode 0x2A/0x86 handler |
0x004D2B20 |
sh_op_2C |
re | SH opcode 0x2C/0x8A handler |
0x004D2BB0 |
sh_op_92 |
re | SH opcode 0x92 handler |
0x004D2C70 |
sh_op_90 |
re | SH opcode 0x90 handler |
0x004D2D30 |
sh_op_94 |
re | SH opcode 0x94 handler |
0x004D2FC0 |
do_setcolor2 |
sms | SH opcode 0x5C |
0x004D2FC8 |
sh_op_BC |
re | SH opcode 0xBC handler (UnkBC) |
0x004D2FD0 |
sh_op_2E |
re | SH opcode 0x2E handler; contains do_setcolor_continue (0x4D2FD6) |
0x004D300A |
sh_op_24_pre |
re | 2-byte entry for op 0x24 (falls into do_fullpntg16) |
0x004D300C |
do_fullpntg16 |
sms | SH opcode 0xFA |
0x004D3064 |
sh_op_A0 |
re | SH opcode 0xA0 handler |
0x004D30C8 |
sh_op_4E |
re | SH opcode 0x4E handler |
0x004D30E4 |
do_short_ijmp |
sms | SH opcode 0x38 (ShortJump): DEC ESI then shares 0x48 body |
0x004D30E5 |
do_jump |
sms | SH opcode 0x48 (Jump): esi+=rel16 |
0x004D3100 |
do_ijmp_long |
sms | SH opcode 0x50 (LongJump): esi+=rel32 |
0x004D3118 |
sh_op_32 |
re | SH opcode 0x32 handler |
0x004D3134 |
do_anim_jmp |
sms | SH opcode 0x40 (JumpToFrame): idx=_frameCounter mod nframes; relative frame-table jump |
0x004D315C |
sh_op_4A |
re | SH opcode 0x4A handler |
0x004D3193 |
sh_op_4C_pre |
re | 1-byte entry for op 0x4C/0x8C (falls into sh_op_C4) |
0x004D3194 |
do_xformunmask |
re | SH opcode 0xC4 XformUnmask: render sub-stream at a relative transform |
0x004D33D8 |
do_icall_long |
sms | SH opcode 0xC6 (XformUnmaskLong) |
0x004D3618 |
sh_op_52 |
re | SH opcode 0x52/0x54 handler |
0x004D3644 |
sh_op_56 |
re | SH opcode 0x56 handler |
0x004D3670 |
sh_op_5E |
re | SH opcode 0x5E handler |
0x004D36CC |
sh_op_60 |
re | SH opcode 0x60 handler |
0x004D3728 |
sh_op_62 |
re | SH opcode 0x62 handler |
0x004D3784 |
sh_op_64 |
re | SH opcode 0x64 handler |
0x004D37BC |
sh_op_66 |
re | SH opcode 0x66 handler |
0x004D37F4 |
sh_op_68 |
re | SH opcode 0x68 handler |
0x004D382C |
sh_op_58 |
re | SH opcode 0x58 handler |
0x004D3938 |
sh_op_78 |
re | SH opcode 0x78: oriented bounding-box visibility cull — transforms a center+-extent box by the view matrix and trivially-rejects the guarded geometry from its 8 corners (code_pnt Cohen-Sutherland outcodes); emits no geometry (largest handler; 2085 bytes) |
0x004D415D |
thunk_FUN_004d416b |
sms | 2-byte thunk for ops 0xA4/0xBE -> FUN_004d416b |
0x004D416B |
sh_op_A4_body |
re | body reached via thunk (op 0xA4/0xBE) |
0x004D416C |
do_jumpfar4 |
sms | SH opcode 0xC8 (JumpToLOD): distance/size LOD test; skips 6-byte operand when _effects&0x20000 |
0x004D4240 |
do_start_interp |
sms | bytecode re-entry target for x86-embedded regions (all 208 x86 shapes jump here); esi=selected sub-stream |
0x004D4254 |
do_start_asm |
sms | SH opcode 0xF0 (X86Code): push esi; ret -> execute embedded x86 payload |
0x004D4258 |
do_collision_info |
sms | SH opcode 0xF2 (PtrToObjEnd): records obj_end_off |
0x004D426C |
set_overlap_flag |
re | helper: AND/OR update of collision/overlap flag word 0x515EF0 (used by do_no_overlap) |
0x004D4288 |
sh_op_CA |
re | SH opcode 0xCA handler (UnkCA) |
0x004D42C8 |
do_setlight |
sms | SH opcode 0xDA |
0x004D42EC |
do_setcoarse |
sms | SH opcode 0x44 (sets _coarse detail flag) |
0x004D4308 |
do_set_point_color |
sms | SH opcode 0xF6 (VertexInfo): per-vertex color+normal |
0x004D4364 |
do_set_gouraud |
sms | SH opcode 0xF4 |
0x004D43CC |
SetFlatColor |
re | helper: set flat-shade color for do_new_poly |
0x004D43DC |
do_new_poly |
sms | SH opcode 0xFC (Face): parse face flags/indices(<<3=*8 pool)/texcoords; effect-gate; synthesize+dispatch sub-program of setcolor/gouraud/texture/brush opcodes -> rasterizer |
0x004D478C |
do_force_no_pmap |
sms | SH opcode 0x46 (sets _force_no_pmap) |
0x004D47A4 |
do_streamer_def |
sms | SH opcode 0xCE (streamer/contrail define) |
0x004D47B8 |
do_streamer_draw |
sms | SH opcode 0xD0 (streamer/contrail draw) |
0x004D4874 |
NeedClip |
re | helper: mark clip needed |
0x004D4888 |
RestoreClip |
re | helper: restore prior clip state |
0x004D4894 |
do_screen_coords |
sms | SH opcode 0xD2 (project to screen coords) |
0x004D4988 |
do_texture_index |
re | SH opcode 0xE0 (TextureIndex): select current texture by index |
0x004D49C0 |
do_texture_file |
re | SH opcode 0xE2 (TextureFile): set current texture by 14-byte name |
0x004D4A19 |
do_brush_solid |
sms | SH opcode 0xEC |
0x004D4A30 |
do_brush_trans |
sms | SH opcode 0xEE |
0x004D4A47 |
do_brush_area |
sms | SH opcode 0xE4 |
0x004D4A6D |
do_brush_area_full |
sms | SH opcode 0xE6 |
0x004D4ACA |
sh_op_DC |
re | SH opcode 0xDC handler (UnkDC; 610 bytes; textured-fill path) |
0x004D4D2C |
sh_op_DE |
re | SH opcode 0xDE handler (perspective textured-fill; 723 bytes) |
0x004D4FFF |
shade_span_a |
re | do_new_smap/rmap shade-span builder variant A |
0x004D511B |
shade_span_b |
re | do_new_smap/rmap shade-span builder variant B |
0x004D523B |
shade_span_c |
re | do_new_smap/rmap shade-span builder variant C |
0x004D5356 |
shade_span_d |
re | do_new_smap/rmap shade-span builder variant D |
0x004D5475 |
do_new_smap |
sms | SH opcode 0xE8 (shade map) |
0x004D5644 |
do_new_rmap |
sms | SH opcode 0xEA (remap; 872 bytes) |
0x004D59AC |
do_new_pmap_or_tmap |
sms | SH opcodes 0x36/0x3E (perspective/texture map) |
0x004D5A2C |
angles_2_matrix |
re | build view rotation matrix from _am_h/_am_p/_am_b (heading/pitch/bank) via sincos |
0x004D5BA8 |
GRInit3d |
sms | init: store detail flag; install _overflow_ptr divide trap |
0x004D5BCC |
GRRender |
sms | top-level 3D render: set viewer xyz + view angles + zoom + obj/ter detail/effects; angles_2_matrix; render_3d; export _unscaled_matrix |
0x004D5C98 |
GRSinCos |
sms | public sin/cos wrapper over sincos |
0x004D5CC0 |
GRTo2d |
sms | |
0x004D5E58 |
MakeObjRotationMatrix |
sms | build object rotation matrix (roll/pitch/yaw) into caller buffer |
0x004D60D8 |
MakeViewRotationMatrix |
sms | build view rotation matrix (yaw/pitch/roll order) into caller buffer |
0x004D631C |
MultPointByMatrix |
sms | transform a point by the current matrix (wraps mult_point_by_matrix_asm) |
0x004D6348 |
GRSaveContext |
sms | save viewer/xv/scr/bias/wtop + scaled+unscaled matrices to shadow block 0x51D5E3+ |
0x004D63F0 |
GRRestoreContext |
sms | restore the GRSaveContext shadow block |
0x004D6498 |
GRExec |
sms | execute one SH command stream: dispatch vector_table[*param]; preserve _xv/_yv/_zv (used by scene dispatch for sky/sun list) |
0x004D64D8 |
MultF24PointByMatrix |
sms | transform a 24.8 fixed point by matrix with saturation |
0x004D65C4 |
Sqrt |
sms | |
0x004D6640 |
do_nt |
sms | SH opcode 0xFE (terrain node/tile): read tile verts; compute bbox+sort key; insert into sort list; draw_quad or draw_tri_nw+se by diagonal |
0x004D69EC |
__compute_viewer_dot_product |
re | backface cull: face-normal dot (vertex - viewer) for do_nt tiles |
0x004D6A38 |
draw_quad |
re | assemble 4-vertex tile working set -> draw_nt |
0x004D6A90 |
draw_tri_nw |
re | assemble NW-triangle working set -> draw_nt |
0x004D6B24 |
draw_tri_se |
re | assemble SE-triangle working set -> draw_nt |
0x004D6BB8 |
draw_nt |
re | build terrain-tile polygon (flat/gouraud/textured) from working set and submit to the 2D rasterizer |
Startup / Phar Lap DOS extender / config¶
startup.csv · page — 222 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004D715A |
_DirectDrawCreate@12 |
sms | IAT jump thunk -> DDRAW.DLL import (used by InitVideo); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7220 |
_ser_rs232_getpacket@12 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7226 |
_ser_rs232_block@12 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D722C |
_ser_rs232_flush@8 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7232 |
_ser_rs232_getbyte@8 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7238 |
_ser_rs232_putbyte@8 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D723E |
_ser_rs232_getstatus@8 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7244 |
_ser_rs232_putpacket@12 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D724A |
_FlushTransmitBuffer@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7250 |
_FlushReceiveBuffer@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7256 |
_SetFlowControlThreshold@12 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D725C |
_SetPaceTime@8 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7262 |
_SetTimeout@8 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7268 |
_UnInitializePort@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D726E |
_SetPortCharacteristics@24 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7274 |
_InitializePort@36 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D727A |
_IsPortAvailable@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7280 |
_BytesInReceiveBuffer@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7286 |
_BytesInTransmitBuffer@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D728C |
_GetByte@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7292 |
_PeekChar@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7298 |
_CdrvCrc16@8 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D729E |
_IsCarrierDetect@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72A4 |
_ModemInit@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72AA |
_ModemAttention@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72B0 |
_ModemWaitForCall@16 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72B6 |
_IsRing@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72BC |
_GetString@12 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72C2 |
_ModemAnswerMode@8 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72C8 |
_ModemGetCarrierSpeed@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72CE |
_ModemConnect@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72D4 |
_Dial@12 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72DA |
_ModemModifyValue@12 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72E0 |
_ModemHangup@4 |
sms | IAT jump thunk -> CDRVxF32/COMMSC32 serial+modem driver import (network/input transport); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D72FE |
_closeMS |
sms | IAT jump thunk -> msapi.dll matchmaking-service import (network subsystem); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7304 |
_getMSdatafile |
sms | IAT jump thunk -> msapi.dll matchmaking-service import (network subsystem); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D730A |
_getMSdatafilesize |
sms | IAT jump thunk -> msapi.dll matchmaking-service import (network subsystem); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7310 |
_initializeMS |
sms | IAT jump thunk -> msapi.dll matchmaking-service import (network subsystem); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7316 |
_connectMS |
sms | IAT jump thunk -> msapi.dll matchmaking-service import (network subsystem); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D731C |
_sendMSresults |
sms | IAT jump thunk -> msapi.dll matchmaking-service import (network subsystem); FA.SMS-named linker stub, conceptual owner is another subsystem |
0x004D7330 |
_strncpy |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7430 |
__cinit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7460 |
_exit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7480 |
__exit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7580 |
__lockexit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7590 |
__unlockexit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D75C0 |
_strrchr |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D75F0 |
_atol |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D76A0 |
_atoi |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D76B0 |
__atoi64 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7790 |
_sprintf |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7810 |
_strchr |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D78D0 |
_memmove |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7A30 |
_tolower |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7AA0 |
__tolower_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7B90 |
_fclose |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7BD0 |
__fclose_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7C70 |
_fopen |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7C90 |
_strstr |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7DB0 |
_isdigit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7E20 |
_isspace |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7E80 |
_isalnum |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D7EC0 |
_isprint |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8050 |
_toupper |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D80C0 |
__toupper_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D81B0 |
__chkstk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D81E0 |
?_JumpToContinuation@@YGXPAXPAUEHRegistrationNode@@@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8220 |
?_CallMemberFunction0@@YGXPAX0@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8230 |
?_CallMemberFunction1@@YGXPAX00@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8240 |
?_CallMemberFunction2@@YGXPAX00H@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8250 |
?_UnwindNestedFrames@@YGXPAUEHRegistrationNode@@PAUEHExceptionRecord@@@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8310 |
?_CallCatchBlock2@@YAPAXPAUEHRegistrationNode@@PBU_s_FuncInfo@@PAXHK@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D83B0 |
?_CallSETranslator@@YAHPAUEHExceptionRecord@@PAUEHRegistrationNode@@PAX2PBU_s_FuncInfo@@H1@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8510 |
__global_unwind2 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8552 |
__local_unwind2 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D85BA |
__abnormal_termination |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D85DD |
__NLG_Notify1 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D85E6 |
__NLG_Notify |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8610 |
_strncmp |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8648 |
__ftol |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D86A0 |
__cfltcvt_init |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D87B5 |
__seh_longjmp_unwind@4 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D87D0 |
_labs |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D87E0 |
_strncat |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8910 |
__alldiv |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D89C0 |
__allmul |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8A00 |
_qsort |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8C00 |
__chdir |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8CB0 |
__fullpath |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8D90 |
__splitpath |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8EF0 |
__getcwd |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D8F60 |
__getdcwd_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D90A0 |
__validdrive |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D90F0 |
_strupr |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9250 |
_bsearch |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9300 |
_fread |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9340 |
__fread_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9490 |
_fwrite |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D94D0 |
__fwrite_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9660 |
_stricmp |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9730 |
_strlen |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D97B0 |
_strcpy |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D97C0 |
_strcat |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D98B0 |
_rand |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9A10 |
_sscanf |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9A60 |
_getenv |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9A90 |
__getenv_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9B70 |
__fflush_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9D00 |
WinMainCRTStartup |
sms | PE entry point / MSVC CRT startup: GetVersion, __heap_init, __mtinit, __ioinit, initmbctable, _setargv/setenvp/__cinit, then _WinMain@16 (0x476120, outside range), _exit. THE one true startup element in range |
0x004D9EB0 |
__amsg_exit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004D9EE0 |
__strlwr |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA040 |
_strnicmp |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA140 |
__mtinitlocks |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA170 |
__mtdeletelocks |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA1E0 |
__lock |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA250 |
__unlock |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA270 |
__lock_file |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA2B0 |
__lock_file2 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA2E0 |
__unlock_file |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA320 |
__unlock_file2 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DA370 |
__isctype |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DBDB0 |
_free |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DBE20 |
__close |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DBE90 |
__close_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DCCD0 |
__CallSettingFrame@12 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DCD20 |
__mtinit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DCDB0 |
__initptd |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DCDD0 |
__getptd |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DCF10 |
_malloc |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DCF30 |
__nh_malloc |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DCF80 |
__heap_alloc |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DCFE0 |
__setdefaultprecision |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD000 |
__ms_p5_test_fdiv |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD050 |
__ms_p5_mp_test_fdiv |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD1C0 |
__cftoe |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD330 |
__cftof |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD460 |
__cftog |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD5B0 |
__dosmaperr |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD630 |
__errno |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD650 |
__mbctoupper |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD6E0 |
__mbsnbcpy |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DD790 |
__setmbcp |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DDAC0 |
__filbuf |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DDBC0 |
__read |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DDC40 |
__read_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DDE70 |
__write |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DDEF0 |
__write_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DE1D0 |
__input |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DEF30 |
__mbsnbicoll |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DF000 |
__commit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DF1A0 |
__XcptFilter |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DF420 |
__ismbblead |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DF4D0 |
__setenvp |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DF5C0 |
__setargv |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DFBC0 |
__ioinit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DFDA0 |
__ioterm |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DFE00 |
__heap_init |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DFE80 |
__FF_MSGBANNER |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004DFEC0 |
__NMSG_WRITE |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E0460 |
__lseek_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E0540 |
__isatty |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E0570 |
_wctomb |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E05D0 |
__wctomb_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E0670 |
__aulldiv |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E06E0 |
__aullrem |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E1700 |
_strcspn |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E1740 |
_strpbrk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E2680 |
__alloc_osfhnd |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E27C0 |
__set_osfhnd |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E2870 |
__free_osfhnd |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E2910 |
__get_osfhandle |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E2A20 |
__lock_fhandle |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E2A90 |
__unlock_fhandle |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E2AE0 |
__sopen |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E2F70 |
?__CxxUnhandledExceptionFilter@@YGJPAU_EXCEPTION_POINTERS@@@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E2FE0 |
?terminate@@YAXXZ |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3080 |
?_inconsistency@@YAXXZ |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3100 |
?_ValidateRead@@YAHPBXI@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3120 |
?_ValidateWrite@@YAHPAXI@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3140 |
?_ValidateExecute@@YAHP6GHXZ@Z |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3160 |
_calloc |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3250 |
__callnewh |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3290 |
__statusfp |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E32B0 |
__clearfp |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E32D0 |
__control87 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3310 |
__controlfp |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3500 |
__ZeroTail |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3570 |
__IncMan |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E35E0 |
__RoundMan |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3690 |
__CopyMan |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E36B0 |
__FillZeroMan |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E36C0 |
__IsZeroMan |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E36E0 |
__ShrMan |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3790 |
__ld12cvt |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3960 |
__ld12tod |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3980 |
__ld12tof |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3A30 |
__atodbl |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3AB0 |
__atoflt |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3B80 |
__fltout2 |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3CD0 |
_mbtowc |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3D30 |
__mbtowc_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E3E70 |
__ungetc_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E47C0 |
__fcloseall |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E4860 |
_wcslen |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E4920 |
__Getdays |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E4A10 |
__Getmonths |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E6270 |
__chsize_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E63B0 |
__onexit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E6440 |
_atexit |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E64A0 |
_abort |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E6730 |
_raise |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E7320 |
_$I10_OUTPUT |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E76C0 |
_realloc |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E7890 |
__mbschr |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E7950 |
__strdup |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E8160 |
__towupper_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E8200 |
_iswctype |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E8310 |
__setmode_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E8380 |
__msize |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E8720 |
_wcstombs |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E8780 |
__wcstombs_lk |
sms | MSVC C runtime (statically linked); FA.SMS public symbol |
0x004E8A2E |
_RtlUnwind@16 |
sms | IAT jump thunk -> ntdll RtlUnwind import (used by CRT C++ EH); FA.SMS-named linker stub, conceptual owner is another subsystem |
Binary: WAIL32.DLL
WAIL32.DLL — Miles Sound System (AIL) audio driver¶
wail32.csv · page — 130 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x20001660 |
DllMain |
re | Miles/AIL public export |
0x200019C0 |
AIL_startup |
re | Miles/AIL public export |
0x20001CF0 |
AIL_shutdown |
re | Miles/AIL public export |
0x20001E00 |
MEM_alloc_lock |
re | Miles/AIL public export |
0x20001F10 |
MEM_free_lock |
re | Miles/AIL public export |
0x20001FA0 |
AIL_set_preference |
re | Miles/AIL public export |
0x200020C0 |
AIL_get_preference |
re | Miles/AIL public export |
0x200021D0 |
AIL_last_error |
re | Miles/AIL public export |
0x200022D0 |
AIL_set_error |
re | Miles/AIL public export |
0x20002360 |
AIL_lock |
re | Miles/AIL public export |
0x20002380 |
AIL_unlock |
re | Miles/AIL public export |
0x200023A0 |
AIL_delay |
re | Miles/AIL public export |
0x20002420 |
AIL_background |
re | Miles/AIL public export |
0x20002430 |
AIL_register_timer |
re | Miles/AIL public export |
0x20002540 |
AIL_set_timer_user |
re | Miles/AIL public export |
0x20002660 |
AIL_set_timer_period |
re | Miles/AIL public export |
0x20002700 |
AIL_set_timer_frequency |
re | Miles/AIL public export |
0x200027A0 |
AIL_set_timer_divisor |
re | Miles/AIL public export |
0x20002840 |
AIL_start_timer |
re | Miles/AIL public export |
0x200028D0 |
AIL_start_all_timers |
re | Miles/AIL public export |
0x20002950 |
AIL_stop_timer |
re | Miles/AIL public export |
0x200029E0 |
AIL_stop_all_timers |
re | Miles/AIL public export |
0x20002A60 |
AIL_release_timer_handle |
re | Miles/AIL public export |
0x20002AF0 |
AIL_release_all_timers |
re | Miles/AIL public export |
0x20002B70 |
AIL_waveOutOpen |
re | Miles/AIL public export |
0x20002CB0 |
AIL_waveOutClose |
re | Miles/AIL public export |
0x20002D60 |
AIL_allocate_sample_handle |
re | Miles/AIL public export |
0x20002E80 |
AIL_allocate_file_sample |
re | Miles/AIL public export |
0x20002FB0 |
AIL_release_sample_handle |
re | Miles/AIL public export |
0x20003060 |
AIL_init_sample |
re | Miles/AIL public export |
0x20003110 |
AIL_set_sample_file |
re | Miles/AIL public export |
0x20003240 |
AIL_set_sample_address |
re | Miles/AIL public export |
0x200032F0 |
AIL_set_sample_type |
re | Miles/AIL public export |
0x200033A0 |
AIL_start_sample |
re | Miles/AIL public export |
0x20003450 |
AIL_stop_sample |
re | Miles/AIL public export |
0x20003500 |
AIL_resume_sample |
re | Miles/AIL public export |
0x200035B0 |
AIL_end_sample |
re | Miles/AIL public export |
0x20003660 |
AIL_set_sample_playback_rate |
re | Miles/AIL public export |
0x20003710 |
AIL_set_sample_volume |
re | Miles/AIL public export |
0x200037C0 |
AIL_set_sample_pan |
re | Miles/AIL public export |
0x20003870 |
AIL_set_sample_loop_count |
re | Miles/AIL public export |
0x20003920 |
AIL_set_sample_loop_block |
re | Miles/AIL public export |
0x200039D0 |
AIL_sample_status |
re | Miles/AIL public export |
0x20003AF0 |
AIL_sample_playback_rate |
re | Miles/AIL public export |
0x20003C10 |
AIL_sample_volume |
re | Miles/AIL public export |
0x20003D30 |
AIL_sample_pan |
re | Miles/AIL public export |
0x20003E50 |
AIL_sample_loop_count |
re | Miles/AIL public export |
0x20003F70 |
AIL_set_digital_master_volume |
re | Miles/AIL public export |
0x20004020 |
AIL_digital_master_volume |
re | Miles/AIL public export |
0x20004140 |
AIL_minimum_sample_buffer_size |
re | Miles/AIL public export |
0x20004280 |
AIL_sample_buffer_ready |
re | Miles/AIL public export |
0x200043A0 |
AIL_load_sample_buffer |
re | Miles/AIL public export |
0x20004460 |
AIL_sample_buffer_info |
re | Miles/AIL public export |
0x200045B0 |
AIL_set_sample_position |
re | Miles/AIL public export |
0x20004660 |
AIL_sample_position |
re | Miles/AIL public export |
0x20004780 |
AIL_register_SOB_callback |
re | Miles/AIL public export |
0x200048B0 |
AIL_register_EOB_callback |
re | Miles/AIL public export |
0x200049E0 |
AIL_register_EOS_callback |
re | Miles/AIL public export |
0x20004B10 |
AIL_register_EOF_callback |
re | Miles/AIL public export |
0x20004C30 |
AIL_set_sample_user_data |
re | Miles/AIL public export |
0x20004CE0 |
AIL_sample_user_data |
re | Miles/AIL public export |
0x20004E10 |
AIL_active_sample_count |
re | Miles/AIL public export |
0x20004F30 |
AIL_digital_configuration |
re | Miles/AIL public export |
0x20004FF0 |
AIL_set_direct_buffer_control |
re | Miles/AIL public export |
0x20005110 |
AIL_get_DirectSound_info |
re | Miles/AIL public export |
0x200051A0 |
AIL_midiOutOpen |
re | Miles/AIL public export |
0x200052D0 |
AIL_midiOutClose |
re | Miles/AIL public export |
0x20005360 |
AIL_allocate_sequence_handle |
re | Miles/AIL public export |
0x20005470 |
AIL_release_sequence_handle |
re | Miles/AIL public export |
0x20005500 |
AIL_init_sequence |
re | Miles/AIL public export |
0x20005630 |
AIL_start_sequence |
re | Miles/AIL public export |
0x200056C0 |
AIL_stop_sequence |
re | Miles/AIL public export |
0x20005750 |
AIL_resume_sequence |
re | Miles/AIL public export |
0x200057E0 |
AIL_end_sequence |
re | Miles/AIL public export |
0x20005870 |
AIL_set_sequence_tempo |
re | Miles/AIL public export |
0x20005910 |
AIL_set_sequence_volume |
re | Miles/AIL public export |
0x200059B0 |
AIL_set_sequence_loop_count |
re | Miles/AIL public export |
0x20005A50 |
AIL_sequence_status |
re | Miles/AIL public export |
0x20005B60 |
AIL_sequence_tempo |
re | Miles/AIL public export |
0x20005C70 |
AIL_sequence_volume |
re | Miles/AIL public export |
0x20005D80 |
AIL_sequence_loop_count |
re | Miles/AIL public export |
0x20005E90 |
AIL_set_XMIDI_master_volume |
re | Miles/AIL public export |
0x20005F30 |
AIL_XMIDI_master_volume |
re | Miles/AIL public export |
0x20006040 |
AIL_active_sequence_count |
re | Miles/AIL public export |
0x20006150 |
AIL_controller_value |
re | Miles/AIL public export |
0x20006280 |
AIL_channel_notes |
re | Miles/AIL public export |
0x200063A0 |
AIL_sequence_position |
re | Miles/AIL public export |
0x200064C0 |
AIL_branch_index |
re | Miles/AIL public export |
0x20006560 |
AIL_register_prefix_callback |
re | Miles/AIL public export |
0x20006680 |
AIL_register_trigger_callback |
re | Miles/AIL public export |
0x200067A0 |
AIL_register_sequence_callback |
re | Miles/AIL public export |
0x200068C0 |
AIL_register_beat_callback |
re | Miles/AIL public export |
0x200069E0 |
AIL_register_event_callback |
re | Miles/AIL public export |
0x20006B00 |
AIL_register_timbre_callback |
re | Miles/AIL public export |
0x20006C20 |
AIL_set_sequence_user_data |
re | Miles/AIL public export |
0x20006CC0 |
AIL_sequence_user_data |
re | Miles/AIL public export |
0x20006DE0 |
AIL_register_ICA_array |
re | Miles/AIL public export |
0x20006E80 |
AIL_lock_channel |
re | Miles/AIL public export |
0x20006F90 |
AIL_release_channel |
re | Miles/AIL public export |
0x20007030 |
AIL_map_sequence_channel |
re | Miles/AIL public export |
0x200070D0 |
AIL_true_sequence_channel |
re | Miles/AIL public export |
0x200071F0 |
AIL_send_channel_voice_message |
re | Miles/AIL public export |
0x200072B0 |
AIL_send_sysex_message |
re | Miles/AIL public export |
0x20007350 |
AIL_create_wave_synthesizer |
re | Miles/AIL public export |
0x20007480 |
AIL_destroy_wave_synthesizer |
re | Miles/AIL public export |
0x20007510 |
FILE_error |
re | Miles/AIL public export |
0x20007610 |
FILE_size |
re | Miles/AIL public export |
0x20007720 |
FILE_read |
re | Miles/AIL public export |
0x20007840 |
FILE_write |
re | Miles/AIL public export |
0x20007970 |
AIL_serve |
re | Miles/AIL public export |
0x200079F0 |
AIL_redbook_open |
re | Miles/AIL public export |
0x20007B00 |
AIL_redbook_close |
re | Miles/AIL public export |
0x20007B90 |
AIL_redbook_eject |
re | Miles/AIL public export |
0x20007C20 |
AIL_redbook_status |
re | Miles/AIL public export |
0x20007D30 |
AIL_redbook_tracks |
re | Miles/AIL public export |
0x20007E40 |
AIL_redbook_track_info |
re | Miles/AIL public export |
0x20007EF0 |
AIL_redbook_id |
re | Miles/AIL public export |
0x20008000 |
AIL_redbook_position |
re | Miles/AIL public export |
0x20008110 |
AIL_redbook_play |
re | Miles/AIL public export |
0x20008240 |
AIL_redbook_stop |
re | Miles/AIL public export |
0x20008350 |
AIL_redbook_pause |
re | Miles/AIL public export |
0x20008460 |
AIL_redbook_resume |
re | Miles/AIL public export |
0x20008570 |
AIL_quick_startup |
re | Miles/AIL public export |
0x200086B0 |
AIL_quick_shutdown |
re | Miles/AIL public export |
0x20008730 |
AIL_quick_load |
re | Miles/AIL public export |
0x20008840 |
AIL_quick_unload |
re | Miles/AIL public export |
0x200088D0 |
AIL_quick_play |
re | Miles/AIL public export |
0x200089F0 |
AIL_quick_halt |
re | Miles/AIL public export |
0x20008A80 |
AIL_quick_status |
re | Miles/AIL public export |
0x20008B90 |
AIL_quick_load_and_play |
re | Miles/AIL public export |
Binary: IP.EXE
IP.EXE — EA system-info & tech-support tool (MFC)¶
ip.csv · page — 5 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004019B0 |
CDROMBenchmark |
re | CD-ROM speed/transfer benchmark ("Benchmarking CD-ROM Drive", Single/Double/Quad-Speed, KB/s) |
0x00403FE0 |
LaunchSystemProperties |
re | ShellExecute sysdm.cpl (Windows System control panel) |
0x00404061 |
LoadDetectionDlls |
re | LoadLibrary hdd.dll + cd.dll (hardware-detection helper libraries) |
0x0040DC60 |
BuildSupportReport |
re | builds the [CPU]/[VIDEO]/[SOUND]/[MODEM] system-config report and faxes/e-mails it to EA support (support@ea.com) |
0x00436EF0 |
WinMain |
re | MFC AfxWinMain wrapper (Ghidra FID) |
Binary: CDRVDL32.DLL
CDRVDL32.DLL — Cdrv RS-232 serial comms driver¶
comms-dl.csv · page — 26 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x100019D0 |
ser_rs232_block |
re | Cdrv comms driver (RS-232 serial) public export |
0x100019E0 |
ser_rs232_cleanup |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001A90 |
ser_rs232_dtr_off |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001AE0 |
ser_rs232_dtr_on |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001B30 |
ser_rs232_flush |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001C50 |
ser_rs232_getbyte |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001D20 |
ser_rs232_getpacket |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001DF0 |
ser_rs232_getport |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001E50 |
ser_rs232_getregister |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001EB0 |
ser_rs232_getstatus |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001F00 |
ser_rs232_maxport |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001F10 |
ser_rs232_putbyte |
re | Cdrv comms driver (RS-232 serial) public export |
0x10001FE0 |
ser_rs232_putpacket |
re | Cdrv comms driver (RS-232 serial) public export |
0x100020B0 |
ser_rs232_putregister |
re | Cdrv comms driver (RS-232 serial) public export |
0x100020C0 |
ser_rs232_rts_off |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002110 |
ser_rs232_rts_on |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002160 |
ser_rs232_set_intfunc |
re | Cdrv comms driver (RS-232 serial) public export |
0x100021D0 |
ser_rs232_misc_func |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002420 |
ser_rs232_setbauddiv |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002450 |
ser_rs232_setup |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002B10 |
ser_rs232_viewpacket |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002B80 |
ser_rs232_get_sdata |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002BC0 |
bio_set_timer |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002BE0 |
bio_get_elapsedtime |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002C10 |
bio_get_timer |
re | Cdrv comms driver (RS-232 serial) public export |
0x10002C40 |
bio_set_timerresolution |
re | Cdrv comms driver (RS-232 serial) public export |
Binary: CDRVHF32.DLL
CDRVHF32.DLL — Cdrv Hayes-modem comms driver¶
comms-hf.csv · page — 75 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x10001000 |
InitializePort |
re | Cdrv comms driver (Hayes modem) public export |
0x100013D0 |
SetBaud |
re | Cdrv comms driver (Hayes modem) public export |
0x10001430 |
SetFlowControlCharacters |
re | Cdrv comms driver (Hayes modem) public export |
0x100014B0 |
SetFlowControlThreshold |
re | Cdrv comms driver (Hayes modem) public export |
0x10001510 |
SetPortCharacteristics |
re | Cdrv comms driver (Hayes modem) public export |
0x100015A0 |
UnInitializePort |
re | Cdrv comms driver (Hayes modem) public export |
0x10001640 |
SetSpecialBehavior |
re | Cdrv comms driver (Hayes modem) public export |
0x10001720 |
Dial |
re | Cdrv comms driver (Hayes modem) public export |
0x10001880 |
ModemAnswerMode |
re | Cdrv comms driver (Hayes modem) public export |
0x10001950 |
ModemAttention |
re | Cdrv comms driver (Hayes modem) public export |
0x10001A70 |
ModemConnect |
re | Cdrv comms driver (Hayes modem) public export |
0x10001C20 |
ModemGetCarrierSpeed |
re | Cdrv comms driver (Hayes modem) public export |
0x10001C50 |
ModemGetConnectSpeed |
re | Cdrv comms driver (Hayes modem) public export |
0x10001C80 |
ModemHangup |
re | Cdrv comms driver (Hayes modem) public export |
0x10001D30 |
ModemInit |
re | Cdrv comms driver (Hayes modem) public export |
0x10001DE0 |
ModemModifyString |
re | Cdrv comms driver (Hayes modem) public export |
0x10001F40 |
ModemModifyValue |
re | Cdrv comms driver (Hayes modem) public export |
0x10002010 |
SendBreak |
re | Cdrv comms driver (Hayes modem) public export |
0x10002090 |
ModemWaitForCall |
re | Cdrv comms driver (Hayes modem) public export |
0x10002270 |
GetByte |
re | Cdrv comms driver (Hayes modem) public export |
0x100022F0 |
GetPacket |
re | Cdrv comms driver (Hayes modem) public export |
0x100023A0 |
GetString |
re | Cdrv comms driver (Hayes modem) public export |
0x100024C0 |
PeekChar |
re | Cdrv comms driver (Hayes modem) public export |
0x10002540 |
PutByte |
re | Cdrv comms driver (Hayes modem) public export |
0x100025C0 |
PutPacket |
re | Cdrv comms driver (Hayes modem) public export |
0x100026B0 |
PutString |
re | Cdrv comms driver (Hayes modem) public export |
0x100026E0 |
BytesInReceiveBuffer |
re | Cdrv comms driver (Hayes modem) public export |
0x10002700 |
BytesInTransmitBuffer |
re | Cdrv comms driver (Hayes modem) public export |
0x10002720 |
FlushReceiveBuffer |
re | Cdrv comms driver (Hayes modem) public export |
0x10002740 |
FlushTransmitBuffer |
re | Cdrv comms driver (Hayes modem) public export |
0x10002760 |
ReceiveBufferSize |
re | Cdrv comms driver (Hayes modem) public export |
0x10002780 |
SpaceInReceiveBuffer |
re | Cdrv comms driver (Hayes modem) public export |
0x100027B0 |
SpaceInTransmitBuffer |
re | Cdrv comms driver (Hayes modem) public export |
0x100027E0 |
SpaceTransmitBuffer |
re | Cdrv comms driver (Hayes modem) public export |
0x100027F0 |
TransmitBufferSize |
re | Cdrv comms driver (Hayes modem) public export |
0x10002810 |
WaitForPeekTableFixed |
re | Cdrv comms driver (Hayes modem) public export |
0x10002970 |
WaitForPeekFixed |
re | Cdrv comms driver (Hayes modem) public export |
0x100029C0 |
WaitForTableFixed |
re | Cdrv comms driver (Hayes modem) public export |
0x10002C10 |
WaitForFixed |
re | Cdrv comms driver (Hayes modem) public export |
0x10002C60 |
WaitForPeekTable |
re | Cdrv comms driver (Hayes modem) public export |
0x10002DB0 |
WaitForPeek |
re | Cdrv comms driver (Hayes modem) public export |
0x10002DF0 |
WaitForTable |
re | Cdrv comms driver (Hayes modem) public export |
0x10003010 |
WaitFor |
re | Cdrv comms driver (Hayes modem) public export |
0x10003050 |
CdrvCrc16 |
re | Cdrv comms driver (Hayes modem) public export |
0x100030A0 |
CdrvCrc32 |
re | Cdrv comms driver (Hayes modem) public export |
0x100030E0 |
DtrOff |
re | Cdrv comms driver (Hayes modem) public export |
0x10003100 |
DtrOn |
re | Cdrv comms driver (Hayes modem) public export |
0x10003120 |
RtsOff |
re | Cdrv comms driver (Hayes modem) public export |
0x10003140 |
RtsOn |
re | Cdrv comms driver (Hayes modem) public export |
0x10003160 |
CdrvGetPcb |
re | Cdrv comms driver (Hayes modem) public export |
0x10003180 |
CdrvCheckTime |
re | Cdrv comms driver (Hayes modem) public export |
0x10003240 |
CdrvDelay |
re | Cdrv comms driver (Hayes modem) public export |
0x10003270 |
CdrvReturnStringAddress |
re | Cdrv comms driver (Hayes modem) public export |
0x10003280 |
CdrvSetTime |
re | Cdrv comms driver (Hayes modem) public export |
0x100032D0 |
CdrvSetTimeoutFunction |
re | Cdrv comms driver (Hayes modem) public export |
0x10003300 |
CdrvSetTimerResolution |
re | Cdrv comms driver (Hayes modem) public export |
0x10003320 |
GetPaceTime |
re | Cdrv comms driver (Hayes modem) public export |
0x10003350 |
GetTimeout |
re | Cdrv comms driver (Hayes modem) public export |
0x10003380 |
SetPaceTime |
re | Cdrv comms driver (Hayes modem) public export |
0x100033B0 |
SetTimeout |
re | Cdrv comms driver (Hayes modem) public export |
0x100033E0 |
DataStreamGetPacket |
re | Cdrv comms driver (Hayes modem) public export |
0x10003430 |
DataStreamGetByte |
re | Cdrv comms driver (Hayes modem) public export |
0x10003480 |
SetDataStreamFunction |
re | Cdrv comms driver (Hayes modem) public export |
0x100034B0 |
IsBreak |
re | Cdrv comms driver (Hayes modem) public export |
0x100034F0 |
IsCarrierDetect |
re | Cdrv comms driver (Hayes modem) public export |
0x10003520 |
IsCts |
re | Cdrv comms driver (Hayes modem) public export |
0x10003550 |
IsDsr |
re | Cdrv comms driver (Hayes modem) public export |
0x10003580 |
IsFramingError |
re | Cdrv comms driver (Hayes modem) public export |
0x100035C0 |
IsInputOverrun |
re | Cdrv comms driver (Hayes modem) public export |
0x10003600 |
IsOverrunError |
re | Cdrv comms driver (Hayes modem) public export |
0x10003640 |
IsParityError |
re | Cdrv comms driver (Hayes modem) public export |
0x10003680 |
IsPortAvailable |
re | Cdrv comms driver (Hayes modem) public export |
0x100036A0 |
IsReceiveBufferEmpty |
re | Cdrv comms driver (Hayes modem) public export |
0x100036C0 |
IsRing |
re | Cdrv comms driver (Hayes modem) public export |
0x10003700 |
IsTransmitBufferEmpty |
re | Cdrv comms driver (Hayes modem) public export |
Binary: CDRVXF32.DLL
CDRVXF32.DLL — Cdrv file-transfer comms driver¶
comms-xf.csv · page — 33 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x10002410 |
CdrvXferCreateDialog |
re | Cdrv comms driver (file transfer) public export |
0x10002580 |
CdrvXferUpdateDialog |
re | Cdrv comms driver (file transfer) public export |
0x100026A0 |
CdrvXferDestroyDialog |
re | Cdrv comms driver (file transfer) public export |
0x10002A90 |
cdrvxfer_files |
re | Cdrv comms driver (file transfer) public export |
0x10002AC0 |
cdrvxfer_sfiles |
re | Cdrv comms driver (file transfer) public export |
0x10002AF0 |
FileTransferDialog |
re | Cdrv comms driver (file transfer) public export |
0x10003080 |
cdrvxfer_gclose |
re | Cdrv comms driver (file transfer) public export |
0x100033E0 |
cdrvxfer_getfiles |
re | Cdrv comms driver (file transfer) public export |
0x10003410 |
cdrvxfer_sendfiles |
re | Cdrv comms driver (file transfer) public export |
0x10003440 |
SetXferParameters |
re | Cdrv comms driver (file transfer) public export |
0x10003500 |
TransferFiles |
re | Cdrv comms driver (file transfer) public export |
0x100058B0 |
dos_close |
re | Cdrv comms driver (file transfer) public export |
0x100058D0 |
dos_commit |
re | Cdrv comms driver (file transfer) public export |
0x100058F0 |
dos_create |
re | Cdrv comms driver (file transfer) public export |
0x10005960 |
dos_createdir |
re | Cdrv comms driver (file transfer) public export |
0x10005980 |
dos_delete |
re | Cdrv comms driver (file transfer) public export |
0x100059A0 |
dos_deletedir |
re | Cdrv comms driver (file transfer) public export |
0x100059C0 |
dos_getdate |
re | Cdrv comms driver (file transfer) public export |
0x10005A10 |
dos_getdir |
re | Cdrv comms driver (file transfer) public export |
0x10005A80 |
dos_getfdate |
re | Cdrv comms driver (file transfer) public export |
0x10005AD0 |
dos_getfileattribute |
re | Cdrv comms driver (file transfer) public export |
0x10005B20 |
dos_getfiledatetime |
re | Cdrv comms driver (file transfer) public export |
0x10005BC0 |
dos_getfirstfile |
re | Cdrv comms driver (file transfer) public export |
0x10005C70 |
dos_getnextfile |
re | Cdrv comms driver (file transfer) public export |
0x10005DA0 |
dos_gettime |
re | Cdrv comms driver (file transfer) public export |
0x10005E00 |
dos_open |
re | Cdrv comms driver (file transfer) public export |
0x10005EA0 |
dos_read |
re | Cdrv comms driver (file transfer) public export |
0x10005EE0 |
dos_rename |
re | Cdrv comms driver (file transfer) public export |
0x10005F00 |
dos_seek |
re | Cdrv comms driver (file transfer) public export |
0x10005F40 |
dos_setfdate |
re | Cdrv comms driver (file transfer) public export |
0x10005F90 |
dos_setfileattribute |
re | Cdrv comms driver (file transfer) public export |
0x10005FE0 |
dos_setfiledatetime |
re | Cdrv comms driver (file transfer) public export |
0x10006070 |
dos_write |
re | Cdrv comms driver (file transfer) public export |
Binary: COMMSC32.DLL
COMMSC32.DLL — Cdrv comms terminal-screen service¶
comms-sc.csv · page — 8 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x100011F0 |
commdrvw_char_screen |
re | Cdrv comms service (terminal screen) public export |
0x10001990 |
CdrvScrDestroy |
re | Cdrv comms service (terminal screen) public export |
0x100019B0 |
CdrvScrCreate |
re | Cdrv comms service (terminal screen) public export |
0x100019D0 |
CdrvScrResize |
re | Cdrv comms service (terminal screen) public export |
0x100019F0 |
CdrvScrWrite |
re | Cdrv comms service (terminal screen) public export |
0x10001A10 |
CdrvScrKillFocus |
re | Cdrv comms service (terminal screen) public export |
0x10001A30 |
CdrvScrSetFocus |
re | Cdrv comms service (terminal screen) public export |
0x10001A50 |
CdrvScrPaint |
re | Cdrv comms service (terminal screen) public export |
Binary: MSAPI.DLL
Matchmaking / internet-play client (MSAPI)¶
msapi.csv · page — 25 named functions
| VA | Symbol | Src | Role |
|---|---|---|---|
0x100011E0 |
connectMS |
re | Export ord2: read Server IP/Server Port from the registry (SOFTWARE...\Matchmaker via ms_reg_open/select/read) or a default source; socket(AF_INET,SOCK_STREAM)+connect() into ms_socket; then send 'WAKEUP' (6B) and expect 'OK'. Codes: 1=OK, 0x3E8=socket() failed, 0x3E9=handshake rejected, 0x3EA=connect error, 0x3EB=proto. |
0x10001670 |
initializeMS |
re | Export ord7: registration handshake — send record size (u32) + 'u' + player record, call ms_upload_init_arrays ('i'), then register the volume serial (GetVolumeInformation, '%d'-formatted); expect 'OK'. Creates the receive worker (ms_recv_thread, CREATE_SUSPENDED). |
0x10001A20 |
ms_upload_init_arrays |
re | Opcode 'i' (@0x1001D0FC): upload the init record as two groups of three u32 arrays, each u32-length-prefixed (ms_send_u32) and htonl-byteswapped. Part of the initializeMS handshake. |
0x10001D20 |
loginMShost |
re | Export ord9: opcode 'h' — login as game host; stores host cookie (ms_host_cookie) and ResumeThread on ms_recv_thread. |
0x10001D80 |
loginMSPlayer |
re | Export ord8: opcode 'p' — login as player; SuspendThread on ms_recv_thread and clear ms_host_cookie. |
0x10001DE0 |
requestMSgame |
re | Export ord10: opcode 'r' — request game list; reads 'P'-prefixed records (ms_recv_u32 length, then payload) into the 0x24-byte linked-list nodes at ms_game_list_head (+0x10 len, +0x14 payload, +0x1C next, +0x20 head); returns 5-dword header + payload to caller. |
0x10002030 |
selectMSgame |
re | Export ord12: opcode 's' — select game by id (rec+0x10, ms_send_u32); expect 'O'. |
0x100020C0 |
deselectMSgame |
re | Export ord3: opcode 'd' — deselect game by id (rec+0x10). |
0x10002120 |
resetMSfilter |
re | Export ord11: opcode 't' — reset game-list filter/cursor; clears ms_game_count/ms_game_selected, sets ms_list_dirty. |
0x10002170 |
updateMSgame |
re | Export ord14: opcode 'u' — upload/update the player-or-game record (ms_record_size bytes). |
0x100021D0 |
fetchMSgame |
re | Export ord4: opcode 'f' — fetch one game record by id; reads a 'P' payload of ms_record_size bytes. |
0x10002280 |
sendMSresults |
re | Export ord13: opcode 'v' — send mission-results blob (ms_send_u32 length + data); expect 'O'. |
0x10002330 |
getMSdatafilesize |
re | Export ord6: opcode 'z' — query server data-file size by name; reads a u32 size; 0xFFFFFFFF => not found (0x3F3); expect 'O'. |
0x10002440 |
getMSdatafile |
re | Export ord5: opcode 'x' — download server data-file by name into a buffer; client acks 'O', expects 'K'. |
0x10002570 |
closeMS |
re | Export ord1: opcode 'l' — quit/logout; closesocket(ms_socket); free the game list + DeleteCriticalSection; clear ms_running. |
0x10002630 |
ms_recv_all |
re | recv() exactly N bytes in a loop ('Read Packet Error - Correcting...' on short read). |
0x10002680 |
ms_send_all |
re | send() exactly N bytes in a loop ('Send Packet Error - Correcting...' on short write). |
0x100026D0 |
ms_recv_u32 |
re | recv 4 bytes then ntohl -> host u32 (network-order length prefix). |
0x10002700 |
ms_send_u32 |
re | htonl then send 4 bytes (network-order length prefix). |
0x10002730 |
ms_disconnect |
re | Receive-worker teardown: send 'l' quit opcode, closesocket, free game list + critical section. |
0x10002800 |
ms_reg_open |
re | Registry-cache ctor: RegCreateKeyExA the HKLM SOFTWARE... base keys (this+0x308/0x30C/0x310) for the Server IP/Port + data-file cache. |
0x10002950 |
ms_reg_close |
re | Registry-cache dtor: RegFlushKey + RegCloseKey the open keys. |
0x100029A0 |
ms_reg_select_subkey |
re | Registry-cache: RegCreateKeyExA a named subkey (e.g. 'Matchmaker') into this+0x314. |
0x10002A00 |
ms_reg_read_value |
re | Registry-cache: RegQueryValueExA a named value into a buffer; if missing and a default is given, RegSetValueExA writes it (get-or-create). Used for 'Server IP'/'Server Port'. |
0x100034D0 |
ms_atoi |
re | Decimal-string-to-int (thin wrapper over FUN_10003430); parses the registry port string in connectMS. |
Format Loaders and Parsers¶
Cross-reference of symbols that directly load, initialize, or parse named file formats.
LIB Archive (EALIB)¶
| Address | Symbol | Role |
|---|---|---|
0x47A090 |
LibSeek(…) |
Seek within open LIB entry |
0x47A130 |
LibFileExists(…) |
Test for named entry |
0x479BD0 |
LibOpen(…) |
Open a named LIB entry |
0x479C80 |
LibRead |
Read bytes from open entry |
0x479D20 |
LibClose |
Close entry handle |
0x479D40 |
LibFileSize |
Query entry size |
0x479630 |
DoLoadLibFile |
Internal LIB decompression dispatch |
0x47A5A0 |
InitGraphicsMode |
Sets up graphics mode post-LIB init |
0x47BC40 |
LibStartUp |
Initialize LIB subsystem |
0x4792D0 |
LibShutDown |
Shutdown LIB subsystem |
0x479350 |
LibUpdate |
Periodic LIB maintenance |
Overlay DLL (.LAY, .HUD, .FNT, .CAM, .MUS, .BI, .MC)¶
| Address | Symbol | Role |
|---|---|---|
0x41E8F0 |
IsBrentDLL(void*) |
Detect Phar Lap PL\0\0 signature |
0x41E910 |
IsDLL(…) |
Generic DLL validity check |
0x41EB60 |
LoadDLL(…) |
Load and IAT-patch an overlay DLL |
0x41F240 |
LoadBrentDLL(…) |
Load Phar Lap PE overlay (CAM/BI/MC) |
0x4B4370 |
WRInit(…) |
Load .LAY file via LoadLibrary + IAT patch |
0x4A6E50 |
LoadPIC |
Load .PIC bitmap (also via LIB) |
0x4A7220 |
SetupPT |
Init .PT (playable aircraft BRF type) |
0x4A6EB0 |
SetupOT |
Init .OT (static object BRF type) |
0x4A7200 |
SetupNT |
Init .NT (NPC/vehicle BRF type) |
0x4A7230 |
SetupJT |
Init .JT (projectile BRF type) |
Config / Save (.CFG, .PLT, NET.DAT)¶
| Address | Symbol | Role |
|---|---|---|
0x47F6D0 |
CN_SetFactoryDefaults(CN_INFO*) |
Initialize config struct to defaults |
0x47F7A0 |
CN_ReadConfig(CN_INFO*, unsigned char*) |
Read EA.CFG into CN_INFO |
0x47F930 |
CN_WriteConfig(CN_INFO*, unsigned char*) |
Write EA.CFG from CN_INFO |
0x47F740 |
CfigChecksum(CN_INFO*) |
Verify config checksum |
0x4B2930 |
UCONFIG_load_EA_CFG() |
High-level EA.CFG load |
0x4B2980 |
UCONFIG_save_EA_CFG() |
High-level EA.CFG save |
0x4B2BD0 |
UCONFIG_Initialize() |
Full config system init |
0x467180 |
PilotSave(PILOT*, short) |
Write .PLT pilot save file |
BRF / Object Types (.OT, .NT, .PT, .JT, .GAS, .ECM)¶
| Address | Symbol | Role |
|---|---|---|
0x41E8F0 |
IsBrentDLL(void*) |
Detect BRF magic header |
0x4A6EB0 |
SetupOT |
Load/init .OT static object |
0x4A7200 |
SetupNT |
Load/init .NT NPC/vehicle |
0x4A7220 |
SetupPT |
Load/init .PT playable aircraft |
0x4A7230 |
SetupJT |
Load/init .JT projectile |
Video (.VDO / Cobra codec)¶
| Address | Symbol | Role |
|---|---|---|
0x4AE440 |
PlayVDOString(char*, …) |
Play FMV by filename |
0x4AE406 |
PlayVDOFile(char*, …) |
Play FMV from open file |
0x4AF070 |
StartVDOAudio(char*) |
Start audio stream for VDO |
0x4AF1B0 |
OpenVDOFile(char*) |
Open a .VDO file |
0x4AF200 |
ReadVDOHeader(…) |
Parse .VDO file header |
0x4AF2D0 |
ReadFrameSizesFile(char*) |
Read .FBC companion sizes |
0x4AF320 |
ReadVDOPalette(…) |
Extract palette from VDO header |
0x4AF3A0 |
AllocVDO(VDO*) |
Allocate VDO playback context |
0x4AE4E0 |
BuildVDOList(char*) |
Build linked list of VDO files |
0x4AED50 |
VDOSetMode(VDO*) |
Set video decode mode |
0x442360 |
InitMovieContext(MovieContext*, …) |
Init Cobra codec context |
0x442370 |
DecodeFrame(MovieContext*, …) |
Decode one Cobra video frame |
Terrain (.T2)¶
| Address | Symbol | Role |
|---|---|---|
0x4C5D60 |
T_Init() |
Initialize terrain database |
0x4C5D70 |
T_Load(…) |
Load .T2 terrain file |
0x4C5D50 |
T_ShutdownDatabase() / T_Init2() / T_Shutdown() |
Lifecycle |
0x4AA620 |
T_InitDictionary() |
Set up terrain tile dictionary |
0x4AA680 |
T_InitDictionaryEntry(…) |
Add .T2 tile entry |
0x4AA7E0 |
T_CompareTlist(…) / T_SortTmapList() |
Sort terrain tmap list |
0x4C6040 |
T_GetLeaf(…) |
Get terrain leaf node at position |
Music / Sequencer (.MUS, .XMI)¶
| Address | Symbol | Role |
|---|---|---|
0x432920 |
InitMusic() / ShutDownMidi() |
Miles Sound System MIDI init/shutdown |
0x4329A0 |
DMusicOn(char*, float) |
Load and start .MUS playlist |
0x432A90 |
MusicOn(char*, float) |
Load and start music by name |
0x432B40 — 0x432C00 |
MusicVolume(…) / DMusicOff() / MusicOff() |
Volume / stop |
0x432C30 |
ScoreOn(void*, char) |
Start .XMI sequence via AIL |
0x446B70 |
SEQmusic |
SEQ script music command dispatcher |
Sequence Scripts (.SEQ)¶
| Address | Symbol | Role |
|---|---|---|
0x44F70 |
SeqInit |
Initialize sequencer |
0x445060 |
SeqStart |
Begin SEQ playback |
0x445D30 |
SeqStop |
Stop SEQ |
0x445700 |
SeqContinue |
Resume/step SEQ |
0x446C70 |
SEQsound / SEQsndoff |
SEQ audio commands |
0x446A50 |
SEQfont |
SEQ font command |
0x446BE0 |
SEQpalette |
SEQ palette command |
0x447090 |
SEQvideo |
SEQ video command |
0x4454D0 |
SeqSubstitute(…) |
Variable substitution in SEQ text |
Mission Map (.MM)¶
| Address | Symbol | Role |
|---|---|---|
0x47A130 |
LibFileExists(…) |
Asset-existence test used during MM load (LIB membership, then loose files by extension — see memory-resource.md) |
0x4B4370 |
WRInit(…) |
Dispatcher for .LAY lines in .MM |
0x4A7D70 |
T_ImmediateVisibility(…) |
Terrain visibility update from MM |
Modem DB / Serial config¶
| Address | Symbol | Role |
|---|---|---|
0x4B9BA0 |
ReadModemDB() |
Read modem database file |
0x4B9BD6 |
WriteModemEntry(…) |
Write modem entry to file |
0x4B9DC0 |
SelectModemFromDB(CN_INFO*) |
Select modem from parsed DB |
0x4B9BF0 |
WriteModemFile(CN_INFO*) |
Write modem config file |
Generated from FA.SMS (3,829 symbols). Addresses are virtual addresses in the game executable's address space (ImageBase 0x00400000).
Global Variable Reference¶
Inventory of all named global variables recovered from the game executable.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; data sourced from
DumpGlobals.csv(DumpGlobals.javaheadless run). Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.Referenced-globals rule: the game-executable reconstruction program treats a data symbol as in-scope only when it is referenced by code (≥1 xref): the 10,313 such globals in
db/inventory/FA.EXE/globals.csv(the local-only Ghidra inventory export — see db/README.md) are the mechanical universe a completed subsystem must name or explicitly waive. The ~48k zero-xref entries are mostly struct/array interiors named at their base and are not individually tracked.
Summary¶
| Metric | Value |
|---|---|
| Total data symbols scanned | 58,742 |
| Named globals (from FA.SMS) | 4,212 |
| Named globals with size > 0 | 1,944 |
| Unnamed data items | ~54,530 |
By size category (named with size > 0):
| Size | Count | Notes |
|---|---|---|
| byte (1) | 1,048 | Flags, boolean state, small counters |
| dword (4) | 659 | Pointers, 32-bit counts, tick counters |
| word (2) | 196 | Screen coords, IDs, 16-bit counters |
| small struct/array (8–32) | 26 | Matrices, fixed-size records |
| large (>32) | 8 | Buffers, large tables |
By address range:
| Range | Count | Segment |
|---|---|---|
0x400000–0x4FFFFF |
833 | Code + embedded constants |
0x500000–0x53FFFF |
452 | Initialized data |
0x540000–0x55FFFF |
284 | Initialized data |
0x560000–0x59FFFF |
375 | Initialized/BSS data |
Top 30 Most-Referenced Globals¶
These are the most-accessed runtime state variables across the entire binary.
| Address | Name | Size | Type | Xrefs | First Writer | Notes |
|---|---|---|---|---|---|---|
0x553848 |
_objPtrs |
4 | ptr | 408 | _OBJShutdown@0 |
Object pool pointer array — the entity list |
0x50CE80 |
_cg |
1 | byte | 340 | _T_AddObj@12 |
Current game object context / active object slot |
0x4EB604 |
_numComputers |
4 | dword | 321 | ?MPConnect@@YGXXZ |
Number of players/computers in session |
0x4F6FBC |
_curId |
2 | word | 309 | @GetCurObj@4 |
Current object ID being processed |
0x4EB608 |
_thisComputer |
4 | dword | 299 | ?MPConnect@@YGXXZ |
This player's computer index in session |
0x520A50 |
_curScreen |
2 | word | 249 | ?usnfmain@@YAXXZ |
Current UI screen ID |
0x520A1C |
_playerId |
2 | word | 237 | ?_MISSIONInit1@@YGXXZ |
Local player entity ID |
0x4EB6F8 |
_gamePrefs |
4 | ptr | 215 | ?MPMissionShutdown@@YGXXZ |
Pointer to single-player preferences struct |
0x4EB6FC |
_gameMultiPrefs |
4 | ptr | 209 | FUN_00429dde |
Pointer to multiplayer preferences struct |
0x552ED4 |
?curDialog@@3PAUDIALOG@@A |
4 | ptr | 175 | _DialogSetup@12 |
Active dialog box pointer |
0x501504 |
_cb |
4 | ptr | 141 | @G_SetBitmap@4 |
Current bitmap / render target pointer |
0x5183A0 |
vector_table |
4 | ptr* | 141 | — | Dispatch vector table (function pointer array) |
0x515F90 |
_resbuf |
4 | ptr | 123 | _GRTo2d@8 |
Rasterizer result buffer pointer |
0x553838 |
_nextObjId |
2 | word | 119 | _OBJInit@4 |
Next available object ID counter |
0x5528E0 |
_currentTime |
2 | word | 111 | _TIMEInit@12 |
Game time (mission elapsed, ticks) |
0x552928 |
_currentTicks |
4 | dword | 108 | _TIMEInit@12 |
Absolute tick counter |
0x5528C8 |
_currentT |
2 | word | 102 | _TIMEInit@12 |
Current time (aliased view of _currentTime) |
0x583DC0 |
_curPalette |
4 | ptr | 86 | FUN_004afb40 |
Active 256-color palette pointer |
0x4FB1A8 |
_missionName |
4 | ptr | 84 | FUN_00428412 |
Pointer to current mission name string |
0x5528EC |
_timerTicks |
4 | dword | 84 | _InstallTimerInt |
Raw timer interrupt tick count |
0x521DE8 |
_shellMousePos |
2 | word | 75 | ?ShellMousePos@@YGXXZ |
Shell UI mouse position (packed x/y) |
0x5528BC |
_fortMission |
1 | byte | 66 | ?_MISSIONInit1@@YGXXZ |
Non-zero when mission is a fortress/fort mode |
0x58F0E0 |
_overflow_ptr |
4 | ptr | 62 | render_3d |
3D renderer overflow scratch pointer |
0x546BA0 |
_serviceTicks |
2 | word | 62 | @GetCurObj@4 |
Per-object service tick timestamp |
0x515F90 |
_xv/_yv/_zv |
2 | word | 62 | _GRTo2d@8 |
Rasterizer projected vertex coordinates |
0x510288 |
_lineStats |
4 | dword | 61 | FUN_0045dedf |
Line-draw statistics counter |
0x5528F8 |
_timeCompression |
1 | byte | 61 | _TIMEInit@12 |
Time compression multiplier (fast-forward) |
0x515F44 |
_scaled_matrix |
2 | word | 57 | FUN_004cdeb4 |
Scaled rotation matrix element |
0x517F34 |
vbuf |
2 | word | 56 | FUN_004d5356 |
Vertex buffer pointer |
0x556868 |
?appIO@@3P6AJJPAD@ZA |
4 | ptr | 54 | ?NET_Initialize@@YAJPAUCN_INFO@@J@Z |
Network I/O function pointer |
Globals by subsystem¶
Generated from the symbol database — the named referenced globals of each completed subsystem (waived struct/array interiors live in the CSVs, not here). Detail per subsystem is on its own page.
Generated from db/symbols/; each subsystem's detailed prose lives on its own page.
Binary: FA.EXE
HUD / cockpit¶
hud.csv · page — 16 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004EBD08 |
_hudTargetViewEnable |
re | enable flag guarding HUDDrawTargetView |
0x004EBD30 |
_hudPitchBarTable |
re | pitch-ladder bar table, 37 records x 3 int16 {pitch,dY,dX} |
0x0052107C |
_hudWarnExpireTick |
re | warning-message expiry tick vs _currentTicks |
0x005213AD |
_hudLineHeight |
re | text row pitch between HUD print lines |
0x005213D2 |
_hudColor |
re | current HUD draw colour (G_SetColor) |
0x005213D4 |
_hudBitmap |
re | HUD offscreen bitmap handle (G_SetBitmap target / G_Blit source) |
0x005213D8 |
_hudShape |
re | 3D shape record rendered into the HUD by HUDDrawTargetView |
0x00521498 |
_hudTargetViewCount |
re | element count for the HUDDrawTargetView blit loop |
0x00521614 |
_hudBlink1 |
re | ~1Hz blink flag from _timerTicks |
0x00521620 |
_hudWarnText2 |
re | secondary warning line buffer |
0x00521694 |
_hudMasterMode |
re | HUD weapon/master submode; ==2 selects landing (HUDDrawApproach) |
0x005216A0 |
_hudBlink2 |
re | second blink-phase flag |
0x005216A4 |
_hudWarnText |
re | warning-message string pointer set by HUDSetWarning |
0x00521980 |
_hudFpmXCached |
re | cached _hudFpmX restored when not in a view transition |
0x00521D94 |
_hudFpmX |
re | flight-path-marker screen X; anchor for nearly all HUD symbology |
0x00521D96 |
_hudFpmY |
re | flight-path-marker screen Y |
View / camera & replay (VIEW)¶
view.csv · page — 5 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004EC420 |
viewModeTable |
re | view-mode dispatch table scanned by VIEWModeLookup |
0x005223F0 |
replayWindowStart |
re | replay capture window start tick |
0x005223F4 |
replayWindowEnd |
re | replay capture window end tick |
0x00522400 |
replaySaveBuf |
re | saved-view replay buffer base (0x30 dwords copied in/out of the view) |
0x005224C0 |
replayActive |
re | replay-active flag (set by VIEWReplayRecordGate, read by VIEWReplayPlayback) |
Collision (COL)¶
collision.csv · page — 38 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00536728 |
_colSelfObj |
re | querying entity ptr (predicted-pos cache follows) |
0x00536730 |
_colRayEndX |
re | query ray end X |
0x00536734 |
_colRayEndY |
re | query ray end Y |
0x00536738 |
_colRayEndZ |
re | query ray end Z |
0x0053673C |
_colObjPart |
re | hit child-box/part id |
0x00536740 |
_colObjCount |
re | registered collidable count this frame |
0x00536748 |
_colBoundMaxX |
re | swept AABB max X |
0x0053674C |
_colBoundMaxY |
re | swept AABB max Y |
0x00536750 |
_colBoundMaxZ |
re | swept AABB max Z |
0x00536758 |
_colBoundMinX |
re | swept AABB min X |
0x0053675C |
_colBoundMinY |
re | swept AABB min Y |
0x00536760 |
_colBoundMinZ |
re | swept AABB min Z |
0x00536764 |
_colBlockType |
re | blocking-hit terrain-leaf/structure type |
0x00536768 |
_colStructCount |
re | registered structure count |
0x0053676C |
_colBlockDist |
re | nearest blocking-hit distance (sentinel 0x7FFFFFFF) |
0x00536770 |
_colStructList |
re | structure id list (word[0x1c2]) |
0x00536AF8 |
_colObjAngleH |
re | object-hit angle H |
0x00536AFC |
_colObjAngleP |
re | object-hit angle P |
0x00536B00 |
_colClosureRadius |
re | closure radius (query param) |
0x00536B04 |
_colGearHeight |
re | gear/ground clearance (COLInfo+8 <<8) |
0x00536B0C |
_colSelfSide |
re | IFF side bit of the querying object |
0x00536B10 |
_colObjList |
re | collidable id list (word[900]) |
0x00537218 |
_colBlockAngleH |
re | blocking-hit angle H |
0x0053721C |
_colBlockAngleP |
re | blocking-hit angle P |
0x00537220 |
_colRayStartX |
re | query ray start X |
0x00537224 |
_colRayStartY |
re | query ray start Y |
0x00537228 |
_colRayStartZ |
re | query ray start Z |
0x0053722C |
_colObjId |
re | nearest hit object id |
0x00537230 |
_colBlockPosX |
re | blocking-hit pos X |
0x00537234 |
_colBlockPosY |
re | blocking-hit pos Y |
0x00537238 |
_colBlockPosZ |
re | blocking-hit pos Z |
0x0053723C |
_colSelfId |
re | querying entity id |
0x00537240 |
_colObjPosX |
re | object-hit pos X |
0x00537244 |
_colObjPosY |
re | object-hit pos Y |
0x00537248 |
_colObjPosZ |
re | object-hit pos Z |
0x0053724C |
_colTargetId |
re | single-target id (excluded from broad-phase) |
0x00537250 |
_colObjDist |
re | nearest object-hit distance (sentinel 0x7FFFFFFF) |
0x00537254 |
_colObjBox |
re | hit box pointer |
Sound / music (incl. WAIL32)¶
sound.csv · page — 5 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004EB5F0 |
_musicOn |
re | music/MIDI subsystem-available master flag |
0x004F3C10 |
_warnSndVol |
re | initial-volume table for _warnSnd |
0x004F3C20 |
_warnSndName |
re | filename table for _warnSnd (IR1.11K, ...) |
0x004F3CCC |
_curShellMusic |
re | current shell-music category |
0x005380B8 |
_warnSnd |
re | RWR/IR threat-warning tone channel table (8 entries) |
Memory & resource managers (MM/RM)¶
memory-resource.csv · page — 12 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004F3DBC |
mm_initialized |
sms | MM one-time init guard (bool); set by MMInit cleared by MMShutdown |
0x004F3DC0 |
mm_handles |
sms | base of T_HANDLE[count] pool (operator_new'd in MMInit) |
0x004F3DC4 |
mm_used_list |
sms | head of allocated-handle intrusive list (next@+0 prev@+4) |
0x004F3DC8 |
mm_unused_list |
sms | head of free-handle intrusive list |
0x004F3DCC |
mmAllocIdSp |
re | depth/index of the alloc-id save-stack (@0x538250) for MMPushAllocId/MMPopAllocId [was DAT_004f3dcc] |
0x00503E30 |
resList |
sms | RES_LIST[1400] resource registry spanning 0x503E30..0x50A618 (1400*0x13=0x67E8) |
0x0050A618 |
rmInitialized |
re | RM init flag; its ADDRESS doubles as the resList end-marker (scans stop at 0x50A617) [was DAT_0050a618] |
0x0050A61C |
rmNotifyEnabled |
re | guards RMNotify; cleared during RMFree/RMFreeAllId bulk operations to prevent reentrancy [was DAT_0050a61c] |
0x00538248 |
mm_page_size |
sms | GetSystemInfo dwPageSize; VirtualAlloc-vs-GlobalAlloc threshold (shared reader: sound) |
0x0053824C |
mmAllocId |
sms | current alloc-id tag stamped on new handles/RES_LIST rows; widely read (campaign/network/shell-ui) but MM-owned |
0x00573208 |
brsColorProcess |
sms | bitmap color-process mode set by SetupBitmapAccess |
0x00573248 |
resCache |
re | 20-slot x 8-byte LRU find-cache {RES_LIST* ; timerTicks} spanning 0x573248..0x5732E8 [was DAT_00573248] |
.SEQ scripted-cutscene / sequence player (SEQ)¶
seq.csv · page — 25 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00540318 |
seqFontArray |
sms | SEQFNT font pool base - init by SeqInit |
0x0054039C |
seqFonts |
sms | active font list head |
0x005403A0 |
seqFadeLen |
sms | palette fade duration in fixed ticks |
0x005403A8 |
seqGrArray |
sms | SEQGR graphic-node pool base |
0x00540768 |
seqSynch |
sms | synch flag - current command is a wait/synch point |
0x00540770 |
curSeq |
sms | index of the sequence slot being ticked |
0x00540778 |
seqTextArray |
sms | SEQTXT text-node pool base |
0x005407D8 |
videoState |
sms | video/AVI playback state gating SEQvideo (shared with video path) |
0x005407DC |
seqIgnoreTicks |
sms | accumulated load time subtracted from the sequence clock |
0x005407E0 |
seqGrList |
sms | SEQGR free-list head |
0x005407E4 |
seqFading |
sms | fade direction 0 none 1 in -1 out |
0x005407E8 |
seqFlipState |
sms | page-flip/dirty state for the render pass |
0x005407EC |
seqSndPriority |
sms | current sound priority |
0x005407F0 |
seqLoadPtr |
sms | write cursor into the compiled command buffer |
0x005407F4 |
seqFadeStart |
sms | tick at which the current fade started |
0x00541278 |
seqSndLink |
sms | linked sound handle |
0x0054127C |
seqGraphics |
sms | active SEQGR display-list ring head |
0x00541280 |
seqFontList |
sms | SEQFNT free-list head |
0x00541284 |
seqLabelList |
sms | SEQLBL free-list head |
0x00541288 |
lineChar |
sms | interpreter cursor into the current command line |
0x00541290 |
seqLine |
sms | expanded current-line scratch buffer |
0x00541394 |
seqLabelPtr |
sms | label node just defined by SeqParseLabel |
0x00541398 |
seqTextList |
sms | SEQTXT free-list head |
0x005413A0 |
seqList |
sms | SEQUENCE slot array base - stride 0x38 - up to 3-4 slots |
0x00541450 |
seqLabels |
sms | SEQLBL node pool base |
Renderer & rasterizer (GG/G_)¶
renderer.csv · page — 3 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004F6CA4 |
_blitDestX |
re | destination X of the present blit rect (GG_FlushShaken/FlushDirtyLines) |
0x004F6CA8 |
_blitDestY |
re | destination Y of the present blit rect; offset by screen shake |
0x004F6CAC |
_shakeParity |
re | current screen-shake parity toggle (GG_FlushShaken) |
Wingman / group AI (WNG/GRP)¶
wingman.csv · page — 1 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x00546AB8 |
_wmNameBuf |
re | scratch buffer for WNGName/GRPName output |
Object / entity system & shape selection¶
objects.csv · page — 15 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004F6FDC |
_curObjStackTop |
re | PushCurObj/PopCurObj nesting stack for GetCurObj |
0x004F6FE0 |
_cmdBufWritePtr |
re | command-buffer write cursor (WriteCmdBuf*/AllocCmdBuf) |
0x004FFE34 |
_objArena |
re | entity arena base (MMAllocPtr in OBJInit; shrunk by OBJStopAdding) |
0x00546B94 |
_curObjSize |
re | byte size of the current entity mirror; = type +0x03 + 0xDE for classes 0/2/4/6 |
0x00546B9C |
_curTypeSize |
re | byte size of the current type-record mirror (from type +0x01) |
0x00546BA4 |
_lastPadlockId |
re | previous padlock/tracked id, compared by CheckForEvents1/2 |
0x00546BA8 |
_requeueChain |
re | objects serviced this frame; ChainMergeSorted folds it back into chainStart |
0x00546BB8 |
_lastCurZ |
re | previous frame Z of current object (CheckForEvents1) |
0x00553120 |
_objSizes |
re | word[900] per-id entity size table (OBJAdd/OBJSubtract); ends at _objArenaNext |
0x00553828 |
_objArenaNext |
re | bump cursor into the entity arena (OBJAdd memcpy target) |
0x0055382C |
_tempAliasBase |
re | lowest temp-alias id: (-0x14 - thisComputer)*1000 - 999 |
0x00553830 |
_tempAliasMax |
re | highest temp-alias id: (-0x14 - thisComputer)*1000; OBJTempAlias wraps to base past it |
0x00553834 |
_multiAliasCursor |
re | OBJAliasForMulti/OBJNextAliasForMulti iteration cursor |
0x0055383C |
_tempAliasNext |
re | next temp alias returned by OBJTempAlias (negative per-computer id band) |
0x00553840 |
_objArenaSize |
re | arena byte capacity (OBJInit parameter), bounds OBJAdd |
AI interpreter (CT)¶
ai.csv · page — 11 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x0050CF83 |
_ctOverrideName |
re | alternate/override AI script-name buffer; selected by _ctState+0x1d |
0x0050CF90 |
_ctStateCheckpoint |
re | heap copy of _ctState for preemptible save/restore |
0x0050D312 |
_ctProgramNamePtr |
re | pointer to the current AI script name; NULL gates the interpreter off |
0x00546BC0 |
_ctCompareActor |
re | actor slot used by CTVarDiff to evaluate attributes under the other object |
0x00546BC8 |
_ctState |
re | 0x80-byte CT execution state block copied by CTSaveState/CTRestoreState (vars+stack+IP+base+name+line+priority) |
0x00546C48 |
_ctPrintBuf |
re | HUD message text staged by CTDo_print/printnum, flushed on loop exit |
0x00546C8C |
_ctCheckPass |
re | validate/dry-run flag: skips branches and side effects, enables the stack-imbalance check |
0x00546C90 |
_ctActionTaken |
re | set 1 when a CTDo action fires; returned by CTExecProgram |
0x00546C94 |
_ctActorObj |
re | pointer to the current actor entity record |
0x00546C98 |
_ctHalt |
re | halt flag; the CTExecProgram loop runs while *IP!='%' and !_ctHalt |
0x00546CA4 |
_ctExecuting |
re | re-entry guard set across CTExecProgram |
Terrain (T_)¶
terrain.csv · page — 1 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004AACF0 |
T_DefaultHorizon |
sms | default horizon descriptor (14 B) embedded after T_HorizonProc; pushed into leaf list by T_Make |
Weapons — projectiles / seekers / ECM (PROJ)¶
weapons.csv · page — 8 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x0050D0F8 |
_projGunSoundHandle |
re | active looping gun-sound handle |
0x0050D0FA |
_projGunSoundExpiry |
re | gun-sound expiry tick |
0x0058F0F8 |
_projBestTargetCost |
re | seeker-search best-cost accumulator (init 0x7FFFFFFF) |
0x0058F10C |
_projBestTargetId |
re | seeker-search result id |
0x0058F110 |
_projPkPenalty |
re | running hit-chance (Pk) multiplier |
0x0058F118 |
_projNameBuf |
re | weapon display-name buffer (PROJBuildName) |
0x0058F180 |
_projSeekerList |
re | seeker-parameter array from HARDBestSeekers |
0x0058F1D4 |
_projInboundWarnT |
re | inbound-missile warning throttle |
Startup / Phar Lap DOS extender / config¶
startup.csv · page — 47 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x004D85AA |
__NLG_Return2 |
sms | MSVC CRT global (xrefs=1) |
0x004D86F8 |
__except_handler3 |
sms | MSVC CRT global (xrefs=7) |
0x004DD080 |
__forcdecpt |
sms | MSVC CRT global (xrefs=1) |
0x004DD0F0 |
__cropzeros |
sms | MSVC CRT global (xrefs=1) |
0x004DD150 |
__positive |
sms | MSVC CRT global (xrefs=1) |
0x004DD170 |
__fassign |
sms | MSVC CRT global (xrefs=1) |
0x004DD510 |
__cfltcvt |
sms | MSVC CRT global (xrefs=1) |
0x0051E750 |
__umaskval |
sms | MSVC CRT global (xrefs=1) |
0x0051E758 |
__winver |
sms | MSVC CRT global (xrefs=1) |
0x0051E75C |
__winmajor |
sms | MSVC CRT global (xrefs=1) |
0x0051E760 |
__winminor |
sms | MSVC CRT global (xrefs=2) |
0x0051E770 |
__environ |
sms | MSVC CRT global (xrefs=15) |
0x0051E778 |
__wenviron |
sms | MSVC CRT global (xrefs=6) |
0x0051E788 |
__exitflag |
sms | MSVC CRT global (xrefs=1) |
0x0051E78C |
__C_Termination_Done |
sms | MSVC CRT global (xrefs=1) |
0x0051E790 |
__C_Exit_Done |
sms | MSVC CRT global (xrefs=2) |
0x0051E7A0 |
__NLG_Destination |
sms | MSVC CRT global (xrefs=2) |
0x0051E7BC |
__adjust_fdiv |
sms | MSVC CRT global (xrefs=1) |
0x0051E7C0 |
__FPinit |
sms | MSVC CRT global (xrefs=1) |
0x0051E7D0 |
__aenvptr |
sms | MSVC CRT global (xrefs=5) |
0x0051E7D8 |
__aexit_rtn |
sms | MSVC CRT global (xrefs=1) |
0x0051E7F0 |
__locktable |
sms | MSVC CRT global (xrefs=9) |
0x0051E8B0 |
__pctype |
sms | MSVC CRT global (xrefs=38) |
0x0051E8B4 |
__pwctype |
sms | MSVC CRT global (xrefs=3) |
0x0051EC78 |
__cfltcvt_tab |
sms | MSVC CRT global (xrefs=2) |
0x0051EDF8 |
__mbctype |
sms | MSVC CRT global (xrefs=7) |
0x0051F010 |
__stdbuf |
sms | MSVC CRT global (xrefs=1) |
0x0051F2A8 |
__cflush |
sms | MSVC CRT global (xrefs=2) |
0x0051F2B0 |
__XcptActTab |
sms | MSVC CRT global (xrefs=1) |
0x0051F328 |
__First_FPE_Indx |
sms | MSVC CRT global (xrefs=4) |
0x0051F32C |
__Num_FPE |
sms | MSVC CRT global (xrefs=4) |
0x0051F334 |
__XcptActTabCount |
sms | MSVC CRT global (xrefs=2) |
0x0051F3F8 |
__adbgmsg |
sms | MSVC CRT global (xrefs=2) |
0x00520470 |
__commode |
sms | MSVC CRT global (xrefs=2) |
0x00520474 |
?__pInconsistency@@3P6AXXZA |
sms | MSVC CRT global (xrefs=1) |
0x00520478 |
__newmode |
sms | MSVC CRT global (xrefs=3) |
0x00520578 |
__alternate_form |
sms | MSVC CRT global (xrefs=17) |
0x0052057C |
__no_lead_zeros |
sms | MSVC CRT global (xrefs=20) |
0x005205E0 |
__timezone |
sms | MSVC CRT global (xrefs=7) |
0x005205E4 |
__daylight |
sms | MSVC CRT global (xrefs=3) |
0x005205E8 |
__dstbias |
sms | MSVC CRT global (xrefs=2) |
0x00520670 |
__tzname |
sms | MSVC CRT global (xrefs=4) |
0x00591A60 |
?_pnhHeap@@3P6AHI@ZA |
sms | MSVC CRT global (xrefs=1) |
0x00591B24 |
__crtheap |
sms | MSVC CRT global (xrefs=11) |
0x00591C30 |
__nhandle |
sms | MSVC CRT global (xrefs=18) |
0x00592C40 |
__nstream |
sms | MSVC CRT global (xrefs=6) |
0x00592C4C |
__acmdln |
sms | MSVC CRT global (xrefs=5) |
Binary: WAIL32.DLL
Binary: IP.EXE
Binary: CDRVDL32.DLL
Binary: CDRVHF32.DLL
Binary: CDRVXF32.DLL
Binary: COMMSC32.DLL
Binary: MSAPI.DLL
Matchmaking / internet-play client (MSAPI)¶
msapi.csv · page — 12 named referenced globals
| VA | Symbol | Src | Role |
|---|---|---|---|
0x1001D098 |
ms_running |
re | Running flag: set in initializeMS, cleared by closeMS / ms_disconnect. |
0x1001D09C |
ms_conn_state |
re | Connection-state/idle flag toggled around operations; set when the link is torn down. |
0x1001D0A0 |
ms_list_dirty |
re | Game-list-dirty flag: resetMSfilter sets it; requestMSgame refetches when set and clears it when the list is exhausted. |
0x10020BD0 |
ms_recv_thread |
re | MFC CWinThread* background receive pump (AfxBeginThread, CREATE_SUSPENDED); m_hThread at +0x28, Resume/Suspended by the login exports. |
0x10020BD4 |
ms_game_list_tail |
re | Game-list append cursor (last node); advanced as 'P' records arrive. |
0x10020BD8 |
ms_game_list_head |
re | Game-list head sentinel node (0x24 bytes; allocated in initializeMS). |
0x10020BDC |
ms_game_selected |
re | Current game-list selection/iteration node (walked by requestMSgame, reset by resetMSfilter). |
0x10020BFC |
ms_socket |
re | Connected TCP socket handle (AF_INET/SOCK_STREAM); first arg to every send/recv; 0 when not connected. |
0x10020C08 |
ms_host_cookie |
re | Host-mode cookie/handle set by loginMShost, cleared by loginMSPlayer. |
0x10020C10 |
ms_record_size |
re | Negotiated record size in bytes for game/player records (send/recv payload length). |
0x10020C14 |
ms_list_inited |
re | Game-list-initialised flag (set in initializeMS; guards teardown in closeMS). |
0x10020C18 |
ms_game_count |
re | Number of games accumulated in the list (incremented per 'P' record, reset by resetMSfilter). |
Notable Globals for C Reimplementation¶
These globals represent the minimum set of extern declarations needed for a C reimplementation to link correctly:
// Object system
extern void **_objPtrs; // 0x553848 — entity list
extern uint8_t _cg; // 0x50CE80 — current object context
extern uint16_t _curId; // 0x4F6FBC — current object ID
extern uint16_t _nextObjId; // 0x553838 — next free ID
// Timing
extern uint16_t _currentTime; // 0x5528E0 — mission elapsed ticks
extern uint32_t _currentTicks; // 0x552928 — absolute tick counter
extern uint32_t _timerTicks; // 0x5528EC — hardware timer count
extern uint8_t _timeCompression; // 0x5528F8 — time-compression factor
// Multiplayer
extern uint32_t _numComputers; // 0x4EB604 — player count
extern uint32_t _thisComputer; // 0x4EB608 — this player index
extern void *_gamePrefs; // 0x4EB6F8 — SP prefs struct
extern void *_gameMultiPrefs; // 0x4EB6FC — MP prefs struct
// Mission
extern uint16_t _playerId; // 0x520A1C — local player entity ID
extern void *_missionName; // 0x4FB1A8 — mission name pointer
extern uint8_t _fortMission; // 0x5528BC — fortress mission flag
// UI
extern uint16_t _curScreen; // 0x520A50 — current screen ID
extern void *curDialog; // 0x552ED4 — active dialog pointer
// Graphics
extern void *_cb; // 0x501504 — current bitmap
extern void *_curPalette; // 0x583DC0 — active palette
extern int16_t _xv, _yv, _zv; // 0x51CDAA/AC/AE — projected vertex
extern int32_t _clipLeft, _clipRight, _clipTop, _clipBottom; // clip rect
Full Named Global Listing¶
The complete list of the 1,955 named globals — those carrying an assigned name
(USER_DEFINED, applied from db/, or IMPORTED, from FA.SMS), with Ghidra's
auto-analysis labels (switchD_/caseD_/s_/DAT_) excluded — is regenerated
into $FA_PROJECT/output/DumpGlobals_named.csv by DumpGlobals.java, with columns:
address, name, size_bytes, data_type, xref_count, first_writer
The raw full export (all 58,517 data symbols, including switch tables and unnamed
data) is the sibling $FA_PROJECT/output/DumpGlobals.csv. Both are local-only
Ghidra output (never committed, #342);
the counts track the current canonical fa-re project.
FA Struct Reference¶
Master struct reference for Jane's Fighters Anthology. All offsets were derived by
RecoverStructs.java scanning the executable for field-access patterns against known
struct pointer arguments.
Provenance: Ghidra static analysis of the game executable with FA.SMS symbols applied; recovered by
RecoverStructs.java. Confidence markers follow spec-authoring.md: confirmed · inferred · unknown.
In this document, confirmed means the accessor function name clearly indicates the
field meaning (e.g., MPSetFuel, HUDDrawAlt, DAMAGEDoHit); inferred means only
generic FUN_* accessors exist and the field name comes from access context, or is unknown.
All sizes are in bytes. Multi-byte integer types are little-endian. Fixed-point values use FA's standard F24.8 format (24-bit integer part, 8-bit fraction) unless noted. The entity struct scan covered offsets 0x00–0x11E; the raw scan logged 286 distinct offsets. The tables below present the most useful subset; unlisted offsets within a range are unaccessed or aliased to adjacent fields.
Confidence and the low-confidence pass¶
Every inferred (Low-confidence) field was reviewed against its accessor set (#130). A field is
promoted to confirmed only when a dedicated named accessor whose name identifies this
specific offset exists (the bar stated above). Where a field remains inferred, the reason is one
of three — this is the "why it can't be promoted from static analysis alone":
- Generic
FUN_*accessors only. The functions that touch the offset are still unnamed in the applied FA.SMS symbol set, so the field's role cannot be established statically. These are the bulk of the remaining Low tags and are deferred to the dynamic / Ghidra re-tracing pass under #54 — reading what eachFUN_*does with the offset is what earns the promotion, and that cannot be done from the committed docs/DB. - Named but generic accessors. The offset is touched only by broad multi-field routines
(
_explode,_MoveObj@0,_HUDInit@0,_DAMAGEInit@0,_ServicePlayer@0, theMP*sync functions, high-fan-out helpers likeFUN_00406a5e/FUN_0042d050, …). The accessor name does not isolate this offset's meaning, so the field name stays a context-derived guess. (Contrast0x32 speed/0x34 heading,confirmedbecauseHUDDrawSpeed/HUDDrawHeadingmap to exactly that field, with0x36 unk_36/0x3A unk_3A,inferredalthough the same routine reads them.) - Probable cross-struct contamination. A few offsets are reached by methods of an unrelated
class and are almost certainly not fields of the struct they were scanned under — see the
flagged
GV_TYPErows0x5C/0x78/0x7C(CDirDrawSurface::*). The scan cannot separate two structs that share a pointer-argument convention (see Methodology).
Promotions made in this pass: entity 0xE8 and GV_TYPE 0x3F (both gained a dedicated named
accessor); the three contamination rows above are annotated in place. All other Low tags fall under
categories 1–2 and carry the standing rationale here rather than a repeated per-row note.
1. entity — Game Object Base¶
Every live game object (aircraft, missile, vehicle, static object, NPC, etc.) shares this common header. Allocated and managed by the object system (_MoveObj@0, _explode, _T_AddObj@12). At runtime a pointer to the current object is held in a global (_currentT or equivalent); subsystems such as HUD, damage, flight model, and AI all index off this pointer.
The struct begins at the object's base address. Extension structs (PROJ_TYPE, GV_TYPE, etc.) overlay the upper region of the same allocation.
Scan range: 0x400000–0x540000, offsets 0x00–0x11E
| Offset | Size | Field name (inferred) | Accessor functions | Notes |
|---|---|---|---|---|
0x00 |
1 | obj_type |
_explode, NET_SlaveInit, NET_SlaveShutdown |
Object type/class byte. Highest access count (6434×). confirmed |
0x01 |
1 | obj_flags |
_explode, FUN_00401180, FUN_00401280 |
Primary flags byte; 8288× accesses. inferred |
0x02 |
1 | obj_flags2 |
_explode, FUN_00401280, FUN_00401430 |
Secondary flags. inferred |
0x03 |
1 | unk_03 |
_explode, NET_SlaveInit, player_list_process_pkt |
inferred |
0x04 |
4 | obj_id or obj_ptr |
_explode, FUN_00401180, FUN_00401280 |
Most-accessed dword (10188×); likely object index/handle. inferred |
0x05 |
1 | unk_05 |
FUN_00401a60, handle_slave_connection_failed |
inferred |
0x06 |
1 | unk_06 |
_explode, player_list_process_pkt |
inferred |
0x07 |
1 | unk_07 |
slave_process_pkt, HUDDrawHeading |
inferred |
0x08 |
4 | unk_08 |
FUN_00401180, FUN_00401280, FUN_004014b0 |
5673× accesses. inferred |
0x09 |
1 | squawk_code |
FlyingLoop, HUDDrawHeading, HUDSquawk@0 |
Read by @HUDSquawk@0; IFF/squawk identifier. confirmed |
0x0A |
1 | warning_flags |
slave_process_pkt, HUDSetWarning@8, _HUDInit@0 |
Written by @HUDSetWarning@8. confirmed |
0x0B |
1 | unk_0B |
usnfmain, HUDSetWarning@8, _DAMAGEInit@0 |
inferred |
0x0C |
4 | unk_0C |
_explode, FUN_00401180, FUN_00401280 |
2938× accesses. inferred |
0x0D |
1 | unk_0D |
usnfmain, _HUDInit@0, FUN_0040ebc0 |
inferred |
0x0E |
1 | unk_0E |
player_list_process_pkt, usnfmain, _HUDInit@0 |
inferred |
0x0F |
1 | unk_0F |
usnfmain, HUDDrawRangeInfo, FUN_00409760 |
inferred |
0x10 |
4 | unk_10 |
_explode, FUN_00401180, NET_SlaveInit |
4911× accesses. inferred |
0x14 |
4 | unk_14 |
_explode, FUN_00401180, FUN_00401280 |
3399× accesses. inferred |
0x16 |
2 | altitude |
usnfmain, HUDDrawAlt, FUN_004089a0 |
Read by ?HUDDrawAlt@@YIXXZ. confirmed |
0x17 |
1 | speed_high |
HUDDrawSpeed, _ServicePlayer@0, _MSGSend@32 |
Read by ?HUDDrawSpeed@@YIXXZ. Part of speed value. confirmed |
0x18 |
4 | unk_18 |
_explode, FUN_00401180, NET_SlaveInit |
2555× accesses. inferred |
0x1C |
4 | unk_1C |
_explode, FUN_00401180, NET_RequestPlayerList |
1952× accesses. inferred |
0x1F |
1 | hvel_component |
usnfmain, HUDDrawHVel, HUDDrawRangeInfo |
Read by ?HUDDrawHVel@@YIXXZ. confirmed |
0x20 |
4 | unk_20 |
_explode, FUN_004014b0, handle_slave_connection_failed |
2028× accesses. inferred |
0x21 |
1 | damage_zone |
_DAMAGEDoHit@12, _RotatedOffsetF24@20, MessagesToPlayer |
Written by _DAMAGEDoHit@12. confirmed |
0x22 |
2 | hud_init_flags |
_HUDInit@0, _HUDShutdown@0, FUN_004089a0 |
Read/written by HUD init/shutdown. confirmed |
0x24 |
4 | unk_24 |
_explode, FUN_00401180, NET_SlaveInit |
1289× accesses. inferred |
0x27 |
1 | zone_flags |
_HUDInit@0, @ArmPlane@4, _Collision@56 |
Read by zone/collision init. inferred |
0x28 |
4 | unk_28 |
_explode, NET_SlaveInit, NET_RequestPlayerList |
1190× accesses. inferred |
0x29 |
1 | net_slave_id |
NET_SlaveInit, slave_process_pkt, NET_MasterInit |
Accessed in all slave/master NET functions. confirmed |
0x2C |
4 | unk_2C |
_explode, FUN_00401180, NET_SlaveInit |
885× accesses. inferred |
0x30 |
4 | unk_30 |
FUN_00401180, handle_slave_connection_failed, _HUDInit@0 |
844× accesses. inferred |
0x32 |
2 | speed |
HUDDrawSpeed, FUN_00409f30, FUN_0040b320 |
Read repeatedly by ?HUDDrawSpeed@@YIXXZ. confirmed |
0x34 |
4 | heading |
slave_process_pkt, _HUDInit@0, HUDDrawHeading |
Read by ?HUDDrawHeading@@YIXXZ. confirmed |
0x36 |
2 | unk_36 |
HUDDrawSpeed, FUN_004089a0 |
inferred |
0x38 |
4 | unk_38 |
player_list_process_pkt, slave_process_pkt, FUN_004024d0 |
538× accesses. inferred |
0x3A |
2 | unk_3A |
HUDDrawSpeed, FUN_004089a0 |
inferred |
0x3C |
4 | unk_3C |
FUN_004024d0, _HUDInit@0, HUDDrawSpeed |
532× accesses. inferred |
0x3F |
1 | chat_line |
_CHATGetEditLine@0, _MSGSend@32 |
Read by _CHATGetEditLine@0. confirmed |
0x40 |
4 | unk_40 |
_explode, usnfmain, _HUDInit@0 |
644× accesses. inferred |
0x44 |
4 | unk_44 |
FlyingLoop, FUN_00406a5e, FUN_00409760 |
430× accesses. inferred |
0x48 |
4 | unk_48 |
FUN_00402640, FUN_00406a5e, FUN_0040991b |
233× accesses. inferred |
0x49 |
1 | explosion_type |
FUN_0043a5c0, _GRAPHICAddExp@28, _GRAPHICAddSmoke@28 |
Written when creating explosion graphic. confirmed |
0x4B |
1 | tv_key_state |
FUN_0040f2d0, _DAMAGEDoHit@12, TVKey@@YIGG@Z |
Read by ?TVKey@@YIGG@Z. confirmed |
0x4C |
4 | unk_4C |
FUN_00406a5e, FUN_00409760, FUN_00409bfb |
252× accesses. inferred |
0x50 |
4 | unk_50 |
FUN_00406a5e, FUN_00409760, FUN_00409bfb |
336× accesses. inferred |
0x52 |
1 | net_conn_id |
NET_SlaveInit, FUN_00402640, NET_MasterInit |
Accessed in NET_SlaveInit at offset 0x52. confirmed |
0x54 |
4 | unk_54 |
FUN_00406a5e, FUN_0040991b, HUDFindNearest@8 |
231× accesses. inferred |
0x58 |
4 | unk_58 |
FUN_00406a5e, FUN_00409910, FUN_0040991b |
156× accesses. inferred |
0x5A |
2 | damage_state |
FUN_004089a0, _DAMAGEDoHit@12, _DAMAGEUpdate@0 |
Read/written by damage system. confirmed |
0x5C |
4 | unk_5C |
usnfmain, FUN_00406a5e, FUN_00409bfb |
143× accesses. inferred |
0x60 |
4 | unk_60 |
FUN_0040991b, FUN_00409bfb, MenuCreateRemaps |
166× accesses. inferred |
0x64 |
4 | altitude_f24 |
slave_process_pkt, HUDDrawAlt, FUN_00409bfb |
Read by ?HUDDrawAlt@@YIXXZ; F24.8 format. confirmed |
0x65 |
1 | net_state |
NET_SlaveInit, handle_slave_connection_failed, slave_process_pkt |
Network connection state byte. confirmed |
0x68 |
4 | unk_68 |
FUN_00406a5e, FUN_00409bfb, FUN_0040e3a0 |
123× accesses. inferred |
0x6A |
2 | damage_init_data |
_DAMAGEInit@0, FUN_00444ca0, FUN_0044aa20 |
Read during damage system init. confirmed |
0x6B |
1 | max_speed_ref |
FUN_0040e470, @ArmPlane@4, _MaxSpeed@8 |
Read by _MaxSpeed@8. confirmed |
0x6C |
4 | unk_6C |
FUN_00406a5e, FUN_00409bfb, FUN_0040b320 |
99× accesses. inferred |
0x6E |
2 | fm_plane_fields |
_ScreenDump@0, _FMUpdatePlaneFields@0 |
Written by _FMUpdatePlaneFields@0. confirmed |
0x70 |
4 | unk_70 |
slave_process_pkt, FUN_00402640, FUN_00406a5e |
121× accesses. inferred |
0x74 |
4 | unk_74 |
slave_process_pkt, FUN_00402640, FUN_00406a5e |
219× accesses. inferred |
0x78 |
4 | hud_message_ptr |
slave_process_pkt, _HUDMessage@4, FUN_00406a5e |
Read by _HUDMessage@4. confirmed |
0x7C |
4 | unk_7C |
FlyingLoop, FUN_00406a5e, FUN_00409bfb |
88× accesses. inferred |
0x7D |
1 | call_damage_proc |
@CallDamageProc@4, FUN_00463f30, GToTurn@8 |
Read by @CallDamageProc@4. confirmed |
0x7F |
1 | map_side |
FUN_00406a5e, _ServicePlayer@0, @MAPSetSide@4 |
Written by @MAPSetSide@4. confirmed |
0x80 |
4 | unk_80 |
FUN_00401580, FUN_00402640, usnfmain |
618× accesses. inferred |
0x84 |
4 | unk_84 |
FUN_00406a5e, _DAMAGEInit@0, _ServicePlayer@0 |
75× accesses. inferred |
0x86 |
2 | damage_init2 |
_DAMAGEInit@0, FUN_0041769c, FUN_0043db40 |
Written during damage init. confirmed |
0x88 |
4 | unk_88 |
FUN_00406a5e, _DAMAGEInit@0, @ArmPlane@4 |
66× accesses. inferred |
0x8C |
4 | move_obj_ptr |
FUN_0042d050, FUN_00430a90, _MoveObj@0 |
Read by _MoveObj@0; likely next-object link or move data. confirmed |
0x8D |
1 | crc_data |
FUN_00428340, ComputeCRC@@YGJPAUOBJ_TYPE@@ |
Read by ?ComputeCRC@@YGJPAUOBJ_TYPE@@@Z. confirmed |
0x8E |
1 | graphic_flags |
FUN_0043d69b, _GRAPHICAddYourObjs@4 |
Read by graphic object add functions. confirmed |
0x90 |
4 | unk_90 |
ShellSetup, FUN_0042d050, _MoveObj@0 |
32× accesses. inferred |
0x94 |
4 | unk_94 |
FUN_00418a50, @GetNames@4, FUN_0042d050 |
49× accesses. inferred |
0x96 |
2 | altitude_display |
HUDDrawAlt, FUN_0043faf0, FUN_004449f0 |
Read by ?HUDDrawAlt@@YIXXZ. confirmed |
0x98 |
4 | unk_98 |
FUN_0041769c, FUN_0042d050, _MoveObj@0 |
38× accesses. inferred |
0x9C |
4 | unk_9C |
FUN_0040d810, FUN_0042d050, FUN_0042e1d0 |
41× accesses. inferred |
0x9E |
2 | eject_data |
@PilotScreen@4, @EJECTAdd@4 |
Written by @EJECTAdd@4. confirmed |
0xA0 |
4 | hud_draw_data |
_HUDDraw@4, FUN_00406a5e, _DAMAGEInit@0 |
Read by _HUDDraw@4. confirmed |
0xA4 |
4 | unk_A4 |
FUN_0040d390, FUN_00425358, FUN_0042d050 |
93× accesses. inferred |
0xA6 |
2 | unk_A6 |
FUN_00406a5e, FUN_0040903b, FUN_00409f30 |
93× accesses. inferred |
0xA8 |
4 | unk_A8 |
FUN_00420bb0, FUN_0042d050, _MoveObj@0 |
90× accesses. inferred |
0xAA |
2 | damage_hit_data |
_DAMAGEInit@0, _DAMAGEDoHit@12, FUN_00417530 |
Written by _DAMAGEDoHit@12. confirmed |
0xAC |
4 | unk_AC |
_HUDInit@0, FUN_0040d810, FUN_0040e3a0 |
41× accesses. inferred |
0xAD |
1 | plane_type_change |
_DAMAGEInit@0, _ChangePlaneType@12, _FMGetAcc@12 |
Read by _ChangePlaneType@12. confirmed |
0xAE |
2 | unk_AE |
FUN_0040e470, FUN_00428a3b, _SelectRepairPlane@16 |
45× accesses. inferred |
0xB0 |
4 | unk_B0 |
FUN_0040e3a0, _DAMAGEInit@0, FUN_0042d050 |
51× accesses. inferred |
0xB3 |
1 | reaction_data |
@GetNames@4, FUN_00426696, @Reaction@12 |
Read by @Reaction@12. confirmed |
0xB4 |
4 | unk_B4 |
FUN_00406a5e, HUDDrawRangeInfo, FUN_0040903b |
84× accesses. inferred |
0xB6 |
2 | stability |
HUDDrawHeading, HUDDrawRangeInfo, HUDSetStability |
Written by ?HUDSetStability@@YIJXZ. confirmed |
0xB8 |
4 | unk_B8 |
FUN_0041c4f0, FUN_0042d050, _MoveObj@0 |
39× accesses. inferred |
0xBA |
2 | flight_key_state |
FUN_00402640, @FlightKey@4, FUN_00418a50 |
Read by @FlightKey@4. confirmed |
0xBC |
4 | unk_BC |
FUN_00417150, @ArmPlane@4, FUN_004242a0 |
44× accesses. inferred |
0xC0 |
4 | ifm_time |
usnfmain, FlyingLoop, IFMSetTime@@YAXD@Z |
Written by ?IFMSetTime@@YAXD@Z. confirmed |
0xC8 |
4 | slave_conn_fail |
handle_slave_connection_failed, FUN_00406a5e |
Read on slave connection failure. confirmed |
0xCC |
4 | msg_chatter_ptr |
FUN_00409bfb, _MSGSendChatter@24, FUN_004267e4 |
Read by _MSGSendChatter@24. confirmed |
0xD0 |
4 | unk_D0 |
FUN_00401e30, FUN_00415e30, _MoveObj@0 |
33× accesses. inferred |
0xD5 |
1 | msg_state |
_MSGInit@0, _MSGSend@32, _MSGReceive@8 |
Read/written by message system. confirmed |
0xD8 |
4 | unk_D8 |
handle_slave_connection_failed, FUN_004225d4 |
20× accesses. inferred |
0xDC |
4 | unk_DC |
FUN_00406a5e, FUN_0040e470, FUN_0042d050 |
34× accesses. inferred |
0xDE |
2 | cpanel_draw |
FUN_0040d810, FUN_0042c9b0, _CPDraw@8 |
Read by _CPDraw@8. confirmed |
0xE2 |
4 | cp_skill |
FUN_004242a0, FUN_0043a5c0, CPSetSkill@@YAXF@Z |
Written by ?CPSetSkill@@YAXF@Z. confirmed |
0xE3 |
4 | nearest_obj |
_NearestObj@16, FUN_00424de0, FUN_0043a5c0 |
Read by _NearestObj@16. confirmed |
0xE4 |
4 | waypoint_ptr |
FUN_0040d810, _MSGSendChatter@24, MAPUpdateWPPtrs@8 |
Read by @MAPUpdateWPPtrs@8. confirmed |
0xE6 |
2 | service_player |
_ServicePlayer@0, FUN_00417530, _SelectRepairPlane@16 |
Read by _ServicePlayer@0. confirmed |
0xE8 |
4 | msg_chatter2 |
FUN_0040e470, _MSGSendChatter@24, FUN_004226cb |
Read by _MSGSendChatter@24, the same dedicated accessor that confirms 0xCC msg_chatter_ptr. confirmed |
0xE9 |
1 | ap_takeoff_type |
@FlightKey@4, @APTakeoffType@8 |
Written by @APTakeoffType@8. confirmed |
0xEA |
2 | ap_landing_type |
InitCobra, MainLoop, @APLandingType@8 |
Written by @APLandingType@8. confirmed |
0xEB |
1 | ap_type2 |
@ArmPlane@4, WRFogLayerUpdate, @APTakeoffType@8 |
confirmed |
0xF0 |
4 | unk_F0 |
FUN_004242a0, FUN_00426696, FUN_0042d050 |
62× accesses. inferred |
0xF1 |
1 | proj_fire_ref |
FUN_004aacfe, _PROJFire@16, _PROJServiceWeapon@24 |
Read by _PROJFire@16. confirmed |
0xF4 |
4 | unk_F4 |
FUN_0040e470, _MSGSendChatter@24, FUN_004242a0 |
34× accesses. inferred |
0xF5 |
1 | bay_state |
@FMBay@4, FUN_004b298e |
Read by @FMBay@4; weapon bay open/close. confirmed |
0xF8 |
4 | arm_plane_data |
@ArmPlane@4, @GetNames@4, FUN_004242a0 |
Read by @ArmPlane@4. confirmed |
0xFF |
1 | hud_disrupt_flag |
FUN_00405cd0, FUN_00406a5e |
See HUDSetDisrupt@4 at 0x01 of HUD state. inferred |
0x100 |
4 | unk_100 |
_explode, FUN_00401180, FUN_00401280 |
328× accesses; mirrors low-offset pattern. inferred |
0x104 |
4 | menu_bar_data |
usnfmain, MenuDrawBar@@YIXD@Z, FUN_0040cb80 |
Read by ?MenuDrawBar@@YIXD@Z. confirmed |
0x10C |
4 | music_on_ptr |
_MSGSend@32, FUN_0042d050, MusicOn@@YIXPADF@Z |
Read by ?MusicOn@@YIXPADF@Z. confirmed |
0x10D |
1 | streamer_ptr |
FUN_00409bfb, _DrawStreamer@12 |
Read by _DrawStreamer@12. confirmed |
0x110 |
4 | unk_110 |
FUN_0040d810, _DAMAGEDoHit@12, FUN_0041c4f0 |
28× accesses. inferred |
0x111 |
1 | ap_park |
_SelectRepairPlane@16, _APClearParks@0, _APGetPark@0 |
Read by _APGetPark@0. confirmed |
0x113 |
1 | plane_crash |
FUN_0043faf0, _PLANECrash@4 |
Read by _PLANECrash@4. confirmed |
0x115 |
1 | proj_speed_ref |
MenuCreateRemaps, _PROJSpeed@8 |
Read by _PROJSpeed@8. confirmed |
0x118 |
4 | unk_118 |
MenuCreateRemaps, FUN_0040d810, FUN_0042d050 |
38× accesses. inferred |
0x11A |
2 | menu_state |
MenuStartUp@@YGXPADJJDJ@Z, FUN_0040cb80, _CPDraw@8 |
Read by menu startup. confirmed |
0x11C |
4 | unk_11C |
MenuStartUp, FUN_0040c990, MenuSteelRect |
29× accesses. inferred |
2. PROJ_TYPE — Projectile / Missile Extension¶
Overlays the upper region of an entity allocation for missile and projectile objects. Scanned in the range 0x4c0000–0x4c3000 (the PROJ subsystem code block). Only offsets 0x50–0x7F are documented; the lower range is shared with entity.
Scan range: 0x4c0000–0x4c3000, offsets 0x50–0x7F
| Offset | Size | Field name (inferred) | Accessor functions | Notes |
|---|---|---|---|---|
0x50 |
4 | proj_target_ptr |
_PROJFire@16, _PROJInFOV@40, FUN_004c2b5a |
Read at fire and seeker FOV check. confirmed |
0x54 |
4 | unk_54 |
(1× only) | inferred |
0x55 |
1 | proj_hit_flag |
_PROJHit@8 |
Written on impact. confirmed |
0x57 |
1 | unk_57 |
FUN_004c0a9d |
inferred |
0x58 |
4 | proj_fov_data |
_PROJInFOV@40, FUN_004c2b5a |
Read by seeker FOV test. confirmed |
0x5A |
2 | unk_5A |
FUN_004c0a9d |
inferred |
0x5C |
4 | proj_guidance |
FUN_004c24b0, _PROJInFOV@40 |
Read by guidance update. inferred |
0x60 |
4 | proj_guidance2 |
FUN_004c24b0, _PROJInFOV@40 |
Read by guidance update (5×). inferred |
0x64 |
4 | proj_speed |
FUN_004c0a9d, _PROJSpeed@8, _PROJHit@8, _PROJFire@16 |
Read by _PROJSpeed@8; 25× accesses. confirmed |
0x67 |
1 | proj_speed_byte |
_PROJSpeed@8 |
Byte component of speed. confirmed |
0x68 |
1 | proj_fov_byte |
_PROJInFOV@40 |
Seeker cone angle. confirmed |
0x6B |
1 | unk_6B |
_PROJSpeed@8 |
confirmed (same accessor) |
0x6C |
1 | proj_fov2 |
_PROJInFOV@40 |
Second FOV parameter. confirmed |
0x6E |
1 | unk_6E |
FUN_004c2b5a |
inferred |
0x70 |
4 | proj_guidance3 |
FUN_004c24b0, _PROJInFOV@40 |
inferred |
0x72 |
2 | unk_72 |
(1× only) | inferred |
0x74 |
4 | proj_fire_data |
FUN_004c0a9d, _PROJInFOV@40 |
inferred |
0x7F |
1 | proj_fire_sound |
FUN_004c1c10, _PROJFireSound@4 |
Written when firing sound is triggered. confirmed |
3. PT_TYPE — Aircraft Performance Type¶
Performance type data loaded from .JT files on disk (see architecture.md BRF type byte 5). At runtime the struct pointer is obtained from @T_Load@4 / @T_GetLeaf@12 and passed to the flight model. Key physics globals (DAT_0050d3xx range) in AnalyzePhysics.txt map to fields in this struct.
Disk source: .JT files (BRF struct_type 5)
Scan range: 0x400000–0x540000, offsets 0x00–0xC0
Offsets 0x00–0x4F are shared with the entity header (same access patterns); the PT-specific physics fields begin around 0x50. The table below lists the complete distinct set found in the PT_TYPE scan; offsets that exactly match the entity scan are marked as shared.
| Offset | Size | Field name (inferred) | Accessor functions | Notes |
|---|---|---|---|---|
0x00–0x4F |
— | (entity header fields) | — | Shared with entity; see §1 above |
0x50 |
4 | pt_perf_base |
FUN_00406a5e, FUN_00409760, FUN_0040991b |
First PT-specific dword after header. inferred |
0x5A |
2 | pt_damage |
FUN_004089a0, _DAMAGEDoHit@12, _DAMAGEUpdate@0 |
Written by damage system. confirmed |
0x5C |
4 | pt_usnf_data |
usnfmain, FUN_00406a5e, FUN_0040991b |
inferred |
0x60 |
4 | pt_unk_60 |
FUN_0040991b, FUN_00409bfb, MenuCreateRemaps |
inferred |
0x64 |
4 | pt_alt_f24 |
slave_process_pkt, HUDDrawAlt, FUN_0040991b |
Altitude in F24.8. confirmed |
0x68 |
4 | pt_unk_68 |
FUN_00406a5e, FUN_00409bfb, FUN_0040e3a0 |
inferred |
0x6B |
1 | pt_max_speed |
FUN_0040e470, _MaxSpeed@8 |
Read by _MaxSpeed@8. confirmed |
0x6E |
2 | pt_fm_fields |
_FMUpdatePlaneFields@0 |
Written by flight model update. confirmed |
0x70 |
4 | pt_unk_70 |
slave_process_pkt, FUN_00402640 |
inferred |
0x74 |
4 | pt_unk_74 |
slave_process_pkt, FUN_00402640 |
inferred |
0x78 |
4 | pt_hud_msg |
_HUDMessage@4, FUN_00406a5e |
confirmed |
0x7C |
4 | pt_unk_7C |
FlyingLoop, FUN_00406a5e |
inferred |
0x80 |
4 | pt_unk_80 |
FUN_00401580, FUN_00402640, usnfmain |
618× accesses. inferred |
0x84 |
4 | pt_damage_init |
_DAMAGEInit@0, _ServicePlayer@0 |
confirmed |
0x88 |
4 | pt_arm_plane |
@ArmPlane@4, FUN_004267e4 |
Read by @ArmPlane@4. confirmed |
0x8C |
4 | pt_move_ptr |
FUN_0042d050, _MoveObj@0 |
confirmed |
0x90 |
4 | pt_unk_90 |
ShellSetup, FUN_0042d050, _MoveObj@0 |
inferred |
0x94 |
4 | pt_names |
@GetNames@4, FUN_0042d050 |
Read by @GetNames@4; aircraft name string ptr. confirmed |
0x96 |
2 | pt_alt_display |
HUDDrawAlt, FUN_0043faf0 |
confirmed |
0xA0 |
4 | pt_hud_draw |
_HUDDraw@4, FUN_0040d810 |
confirmed |
0xA4 |
4 | pt_unk_A4 |
FUN_0040d390, FUN_0042d050 |
inferred |
0xA8 |
4 | pt_unk_A8 |
_MoveObj@0, FUN_0043faf0 |
inferred |
0xAD |
1 | pt_change_type |
_ChangePlaneType@12, _FMGetAcc@12 |
Read by _ChangePlaneType@12. confirmed |
0xB0 |
4 | pt_unk_B0 |
_DAMAGEInit@0, FUN_0044a910 |
inferred |
0xB3 |
1 | pt_reaction |
@Reaction@12, @GetNames@4 |
confirmed |
0xB6 |
2 | pt_stability |
HUDDrawHeading, HUDSetStability |
confirmed |
0xBC |
4 | pt_unk_BC |
FUN_00417150, @ArmPlane@4 |
inferred |
0xC0 |
4 | pt_time |
usnfmain, FlyingLoop, IFMSetTime@@YAXD@Z |
Written by ?IFMSetTime@@YAXD@Z. confirmed |
4. CN_INFO — Network Configuration¶
Persisted to and loaded from EA.CFG / NET.DAT on disk. CN_ReadConfig reads 0xDDC bytes after a 4-byte checksum header; CN_WriteConfig writes the same. The struct is version-stamped at [0x00] (version byte: 1, 2, or 3). Version 3 is the current live format.
Disk source: EA.CFG or NET.DAT
Total size: 0xDDC bytes (body only; 4-byte checksum prepended on disk)
| Offset | Size | Field name (inferred) | Accessor functions | Notes |
|---|---|---|---|---|
0x00 |
4 | cn_version |
CN_ReadConfig, NET_SlaveShutdown |
Config version. 3 = current format. confirmed |
0x01 |
1 | cn_flags |
NET_SlaveInit, NET_MasterStartGame |
General net flags byte. confirmed |
0x04 |
4 | cn_session_id |
NET_CancelPlayerList, netDialogAppIO, ip2long |
Session or IP identifier. confirmed |
0x06 |
2 | cn_hud_msg_flags |
_HUDMessage@4, HUDReprintMessages |
HUD message display flags. confirmed |
0x07 |
1 | cn_master_reject |
NET_MasterRejectPlayer |
Set when master rejects a player. confirmed |
0x08 |
4 | cn_pkt_state |
player_list_process_pkt, slave_process_pkt |
Packet processing state. confirmed |
0x0A |
1 | cn_warning |
HUDSetWarning@8 |
HUD warning flags from net. confirmed |
0x0B |
1 | cn_unk_0B |
usnfmain, HUDSetWarning@8 |
inferred |
0x0C |
4 | cn_unk_0C |
NET_SlaveInit, NET_RequestPlayerList |
inferred |
0x10 |
4 | cn_master_ptr |
NET_MasterInit, NET_MasterShutdown |
Master session handle. confirmed |
0x14 |
4 | cn_player_list |
NET_RequestPlayerList, NET_MasterRejectPlayer |
Player list pointer or state. confirmed |
0x20 |
4 | cn_hud_range |
HUDDrawRangeInfo |
Range info for HUD display. confirmed |
0x24 |
4 | cn_hud_heading |
HUDDrawHeading |
Heading value for HUD. confirmed |
0x28 |
4 | cn_hud_speed |
HUDDrawSpeed |
Airspeed for HUD. confirmed |
0x30 |
4 | cn_master_shutdown |
NET_MasterShutdown |
Shutdown state flag. confirmed |
0x34 |
4 | cn_hud_alt |
HUDDrawAlt |
Altitude for HUD. confirmed |
0x38 |
4 | cn_hud_data |
HUDDrawWeaponInfo |
Weapon info HUD data. confirmed |
0x40 |
4 | cn_hud_draw_data |
_HUDDraw@4 |
HUD draw buffer reference. confirmed |
0x44 |
4 | cn_hud_init_ext |
_HUDInit@0, FUN_004089a0 |
inferred |
0x52 |
1 | cn_slave_id |
NET_SlaveInit |
Slave player index. confirmed |
0x54 |
4 | cn_hud_nearest |
HUDFindNearest@8 |
Nearest contact for HUD lock. confirmed |
0x60 |
4 | cn_hud_data2 |
HUDDrawRangeInfo, FUN_00409bfb |
inferred |
0x64 |
4 | cn_net_slave_state |
NET_SlaveInit, slave_process_pkt |
Full slave network state. confirmed |
0x68 |
4 | cn_flight_key |
FlightKey@4 |
Flight key binding data. confirmed |
0x7C |
4 | cn_flying_loop |
FlyingLoop |
Game loop counter or tick. confirmed |
0x80 |
4 | cn_unk_80 |
usnfmain, FlyingLoop |
inferred |
0xA0 |
4 | cn_hud_disrupt |
_HUDDraw@4 |
HUD disruption state. confirmed |
0xB4 |
1 | cn_hud_extra |
HUDSetStability |
HUD stability display. confirmed |
0xB6 |
2 | cn_stability |
HUDSetStability |
Stability value sent over net. confirmed |
0xC0 |
4 | cn_ifm_time |
IFMSetTime@@YAXD@Z |
IFM timer. confirmed |
0xC8 |
4 | cn_damage |
_DAMAGEDoHit@12 |
Damage data for net sync. confirmed |
0xD8 |
4 | cn_slave_fail |
handle_slave_connection_failed |
Failure state for disconnection. confirmed |
0xDB0 |
— | cn_tcp_block |
_NetSetFactoryTCP |
TCP factory defaults block; set by _NetSetFactoryTCP__YAXPAUCN_INFO_TCP___Z. confirmed |
0x8E4 |
1 | cn_has_mac_str |
CN_ReadConfig |
Non-zero if MAC address string present at this offset. confirmed |
0x8E4 |
8 | cn_mac_str[8] |
CN_ReadConfig, _atohb |
ASCII MAC address string, converted to binary at 0xDD0. confirmed |
0x8EC |
12 | cn_ip_str[12] |
CN_ReadConfig, _atohb |
ASCII IP address string, converted to binary at 0xDD4. confirmed |
0xDD0 |
8 | cn_mac_bin[8] |
CN_ReadConfig |
Binary MAC address bytes (decoded from 0x8E4). confirmed |
0xDD4 |
12 | cn_ip_bin[12] |
CN_ReadConfig |
Binary IP address bytes (decoded from 0x8EC). confirmed |
0xDDA |
1 | cn_addr_valid |
CN_ReadConfig |
Set to 1 when both MAC and IP decoded successfully. confirmed |
0xDDC |
— | (end of struct) | CN_WriteConfig |
fwrite(param_1, 0xDDC, 1, _File) confirms total body size. |
5. GV_TYPE — Ground Vehicle Extension¶
Extension struct overlaid on entity allocations for ground vehicle objects. Scanned exclusively in the MP subsystem code block (0x470000–0x480000), which handles multiplayer synchronization of ground vehicles. The MP message functions (MPMsgSend, MPGraphicAdd*, MPKey) drive nearly all accesses.
Scan range: 0x470000–0x480000, offsets 0x00–0x80
| Offset | Size | Field name (inferred) | Accessor functions | Notes |
|---|---|---|---|---|
0x00 |
1 | gv_type_id |
FUN_0047001c, MPMsgSend, MPGraphicAddExp |
Object type identifier. 443× accesses. inferred |
0x01 |
1 | gv_flags |
MPProjAdd, FUN_0047001c, MPEjectAdd |
Primary flags. 687× accesses. inferred |
0x04 |
4 | gv_id |
MPSetPaused, FUN_0047001c, MPEjectAdd |
Vehicle index or ID. 610× accesses. inferred |
0x08 |
4 | gv_pos |
FUN_0047001c, MPEjectAdd, FUN_004701ac |
Position data. 563× accesses. inferred |
0x0C |
4 | gv_state |
FUN_0047001c, MPPrepareForInterp, FUN_0047025c |
Interpolation/movement state. 168× accesses. inferred |
0x10 |
4 | gv_graphic |
FUN_0047025c, MPGraphicAddCrater, MPGraphicAddSpecialDebris |
Graphic data reference. 262× accesses. inferred |
0x14 |
4 | gv_unk_14 |
FUN_004701ac, FUN_0047025c, MPMsgSend |
203× accesses. inferred |
0x18 |
4 | gv_net_data |
MPEjectAdd, FUN_004701ac, FUN_0047025c |
Network sync data. 160× accesses. inferred |
0x1C |
4 | gv_unk_1C |
FUN_0047001c, FUN_004701ac, MPMsgSend |
179× accesses. inferred |
0x20 |
4 | gv_unk_20 |
FUN_0047025c, MPMsgSend, MPGraphicAddExp |
149× accesses. inferred |
0x21 |
1 | gv_hulk_flag |
MPGraphicAddHulk, _GVEventProc, FUN_0047759b |
Set on vehicle destruction. confirmed |
0x22 |
1 | gv_unk_22 |
MPMsgSend, MPLaunchDevice, MPGraphicAddFire |
inferred |
0x24 |
4 | gv_unk_24 |
FUN_00470560, MPMsgSend, MPGraphicAddSmoke |
99× accesses. inferred |
0x28 |
4 | gv_unk_28 |
FUN_00470560, MPMsgSend, MPGraphicAddExp |
95× accesses. inferred |
0x2C |
4 | gv_proj_add |
MPProjAdd, FUN_0047001c, MPManAdd |
Read by MPProjAdd; projectile launch data. confirmed |
0x2D |
1 | gv_chatter_msg |
MPMsgSend, MPGraphicAddSmokeAdder, MPKillStats |
Kill stats message data. confirmed |
0x30 |
4 | gv_unk_30 |
FUN_00470560, MPGraphicAddExp, MPGraphicAddSmoke |
73× accesses. inferred |
0x32 |
2 | gv_event_proc |
FUN_0047267c, _GVEventProc, _CheckLandingParms@0 |
Read by _GVEventProc. confirmed |
0x34 |
4 | gv_unk_34 |
FUN_00470560, MPGraphicAddSmoke, MPGraphicAddDebris |
60× accesses. inferred |
0x38 |
4 | gv_unk_38 |
FUN_00470560, MPGraphicAddSmoke, MPPlayerChoseSide |
52× accesses. inferred |
0x3C |
4 | gv_unk_3C |
FUN_004701ac, FUN_00470560, MPGraphicAddFire |
47× accesses. inferred |
0x3F |
1 | gv_fort_btn2 |
MPFortButtonText2, FUN_0047759b, @COSig@20 |
Accessed by the dedicated MPFortButtonText2 (fort-menu button-text 2), consistent with the other MP*-named GV fields. confirmed |
0x40 |
4 | gv_unk_40 |
FUN_004701ac, MPGraphicAddFire, MPSendAntiCheat |
70× accesses. inferred |
0x44 |
4 | gv_unk_44 |
FUN_004701ac, FUN_00470560, MPGraphicAddFire |
36× accesses. inferred |
0x48 |
4 | gv_unk_48 |
FUN_004701ac, FUN_00470560, MPGraphicAddSmokeAdder |
24× accesses. inferred |
0x4B |
1 | gv_fuel |
MPSetFuel@@YIXG@Z, FUN_0047759b, _COBrv@0 |
Written by ?MPSetFuel@@YIXG@Z. confirmed |
0x4C |
4 | gv_hardpoints |
FUN_004701ac, FUN_00470560, MPSetHardpoints |
Read by ?MPSetHardpoints@@YIXG@Z. confirmed |
0x4D |
1 | gv_waypoints |
MPSetWaypoints@@YIXGPAUWAYPOINT@@@Z |
Written by ?MPSetWaypoints@@YIXGPAUWAYPOINT@@@Z. confirmed |
0x50 |
4 | gv_unk_50 |
MPGraphicAddSmokeAdder, FUN_00472130, _FMFlight@0 |
16× accesses. inferred |
0x52 |
1 | gv_cn_print |
CN_Print@@YAXPAE@Z |
Read by ?CN_Print@@YAXPAE@Z. confirmed |
0x54 |
4 | gv_unk_54 |
MPGraphicAddSmokeAdder, FUN_004715bc, FUN_0047267c |
inferred |
0x5C |
4 | gv_clear_surface |
CDirDrawSurface::Clear, _LibStartUp |
Reached only by CDirDrawSurface::Clear — a method of the DirectDraw surface wrapper class (0x41Dxxx), not the GV struct; probable cross-struct contamination (see Confidence, category 3). Not a genuine GV_TYPE field. inferred |
0x60 |
4 | gv_cn_factory |
CN_SetFactoryDefaults |
Network config factory defaults. confirmed |
0x64 |
4 | gv_flight_menu |
MPKey@@YIGG@Z, _FlightMenu |
Read by _FlightMenu. confirmed |
0x78 |
4 | gv_ddraw_surface |
FUN_004735d0, CDirDrawSurface::Create, CDirDrawSurface::SetEntries |
Accessed by CDirDrawSurface::Create/SetEntries (DirectDraw wrapper methods), not GV logic; probable cross-struct contamination (category 3). Not a genuine GV_TYPE field. inferred |
0x7C |
4 | gv_ddraw_surface2 |
MPHUDMessage, CDirDrawSurface::Create, CDirDrawSurface::SetEntries |
Same CDirDrawSurface::* contamination as 0x78 (category 3). Not a genuine GV_TYPE field. inferred |
0x7F |
1 | gv_disconnect |
MPMsgSend, FUN_0047267c, MPCheckDisconnect |
Read by ?MPCheckDisconnect@@YGDXZ. confirmed |
0x80 |
4 | gv_mp_key |
FUN_0047025c, FUN_00471bdf, MPKey@@YIGG@Z |
Read by ?MPKey@@YIGG@Z. confirmed |
6. OT_TYPE — Ordnance Type (Static Object)¶
Type data loaded from .OT files (BRF struct_type 1). The scan for this struct covers the same broad address range as entity; offsets below 0x60 are listed since the scan was bounded there. Offsets 0x00–0x4F strongly overlap the entity header.
Disk source: .OT files (BRF struct_type 1)
Scan range: 0x400000–0x540000, offsets 0x00–0x60
| Offset | Size | Field name (inferred) | Accessor functions | Notes |
|---|---|---|---|---|
0x00–0x4F |
— | (entity header fields) | — | See §1 above |
0x50 |
4 | ot_perf_base |
FUN_00406a5e, FUN_00409760, FUN_0040991b |
First OT-specific offset after shared header. inferred |
0x5A |
2 | ot_damage |
FUN_004089a0, _DAMAGEDoHit@12, _DAMAGEUpdate@0 |
Damage hit data. confirmed |
0x5C |
4 | ot_unk_5C |
usnfmain, FUN_00406a5e, FUN_0040991b |
inferred |
0x5D |
1 | ot_flight_key |
@FlightKey@4, @ArmPlane@4, FUN_00422a71 |
Read by @FlightKey@4. confirmed |
0x5E |
2 | ot_unk_5E |
FUN_00406a5e, @StringIsNumber@4, @StringToNumber@4 |
inferred |
0x5F |
1 | ot_map_draw |
FUN_0040903b, FUN_00409f30, MAPDrawGrid |
Read by ?MAPDrawGrid@@YGXXZ. confirmed |
0x60 |
4 | ot_menu_data |
FUN_0040991b, FUN_00409bfb, MenuCreateRemaps |
Read during menu remap. inferred |
7. NT_TYPE — Nav Target¶
Type data loaded from .NT files (BRF struct_type 3). Like OT_TYPE, the scan covers 0x00–0x60. The accessor pattern through the shared range is identical to entity; NT-specific behavior appears at 0x5D–0x60.
Disk source: .NT files (BRF struct_type 3)
Scan range: 0x400000–0x540000, offsets 0x00–0x60
| Offset | Size | Field name (inferred) | Accessor functions | Notes |
|---|---|---|---|---|
0x00–0x4F |
— | (entity header fields) | — | See §1 above |
0x50 |
4 | nt_nav_base |
FUN_00406a5e, FUN_00409760, FUN_0040991b |
inferred |
0x5A |
2 | nt_damage |
FUN_004089a0, _DAMAGEDoHit@12, _DAMAGEUpdate@0 |
confirmed |
0x5C |
4 | nt_unk_5C |
usnfmain, FUN_00406a5e |
inferred |
0x5D |
1 | nt_flight_ref |
@FlightKey@4, @ArmPlane@4, FUN_00422a71 |
confirmed |
0x5E |
2 | nt_unk_5E |
FUN_00406a5e, @StringIsNumber@4, @StringToNumber@4 |
inferred |
0x5F |
1 | nt_map_draw |
FUN_0040903b, FUN_00409f30, MAPDrawGrid |
Read by ?MAPDrawGrid@@YGXXZ. confirmed |
0x60 |
4 | nt_menu_data |
FUN_0040991b, FUN_00409bfb, MenuCreateRemaps |
166× accesses. inferred |
Notes on Methodology¶
The raw data was produced by RecoverStructs.java, a Ghidra headless script that enumerates all instructions in the given VA range and records every memory dereference of the form [reg + constant] where reg holds a known struct pointer argument. The access count column reflects how often each offset was touched across the entire executable scan — higher counts indicate more deeply integrated fields.
Known limitations:
- The scan cannot distinguish between two different struct types that happen to use the same pointer argument convention. The
entity,OT_TYPE,NT_TYPE, andPT_TYPEscans share access patterns in the0x00–0x4Frange because many engine functions are generic and accept any object pointer. The type record's shape-pointer fields (+0x0F/+0x17/+0x1B/+0x25/+0x29) and their runtime use are documented in shape-selection.md. PROJ_TYPEandGV_TYPEwere isolated by restricting the scan to subsystem-specific code blocks, which removes the shared-header noise but may miss cross-subsystem accesses.- Fields accessed only once or twice are often incidental (e.g., CRT helper functions scanning memory). Treat single-access entries as tentative.
- Size column values are inferred from the next observed offset; actual padding may differ.
External imports — MS / third-party DLL boundary¶
The game executable and its companion binaries link a set of Microsoft and third-party redistributable DLLs. This page documents the FA-side boundary — which functions each binary imports from each external DLL — built mechanically from the PE import tables. Per the license rule (MIT repo), the external code itself is not reverse-engineered; only the interface FA calls is recorded. FA's own companion binaries — WAIL32.DLL (Miles/AIL) and the Cdrv comms suite (CDRVDL32 / modem / transfer / terminal) — are documented as their own subsystems, not here.
External DLLs¶
| DLL | Kind | Role | Imported by | Boundary (representative) |
|---|---|---|---|---|
DDRAW.dll |
MS DirectX | DirectDraw 2D surface / blit | the game executable, IP.EXE | DirectDrawCreate (1) — the rest of the surface API is via the returned COM vtable (see render-core.md) |
DSOUND.dll |
MS DirectX | DirectSound | IP.EXE (diagnostics) | DirectSoundCreate |
WINMM.dll |
MS Win32 | multimedia — joystick + timers + wave/MIDI | the game executable · WAIL32 · IP.EXE · comms | the game executable: joyGetPosEx/joyGetPos/joyGetDevCapsA (input), timeGetTime; WAIL32: timeSetEvent/waveOut*; IP.EXE: midi*/wave*/mixer* DevCaps (diagnostics) |
MSAPI.dll |
EA / third-party | multiplayer match / mission-server client | FA.EXE | initializeMS, connectMS, sendMSresults, getMSdatafilesize, getMSdatafile, closeMS |
MAPI32.dll |
MS | Simple MAPI e-mail | IP.EXE | (the support-report e-mail path) |
WSOCK32.dll |
MS | Winsock — IP config query | IP.EXE | (the network-config section of the support report) |
ADVAPI32.dll |
MS Win32 | registry | the game executable · IP.EXE · … | the game executable: RegOpenKeyExA/RegQueryValueExA/RegEnumKeyExA/RegCloseKey |
KERNEL32 / USER32 / GDI32 |
MS Win32 | OS core · windowing · GDI | all binaries | ubiquitous OS surface (files, memory, threads, messages, DC/blit) |
VERSION · comdlg32 · WINSPOOL.DRV · SHELL32 · COMCTL32 |
MS Win32 | version info · common dialogs · printing · shell | IP.EXE | the support tool's file/print/shell UI (GetOpenFileNameA, OpenPrinterA, ShellExecuteExA, …) |
Notes¶
MSAPI.dllis the one genuinely game-specific external — an EA multiplayer service client (connectMS/sendMSresults/getMSdatafile), i.e. the match / mission-server API the game executable's networking calls into. IfMSAPI.dllships in the install it warrants its own boundary or reconstruction pass; the FA-side import surface is the six functions above.- DirectDraw is a 1-function link (
DirectDrawCreate); FA drives the surface through the returned COM interface, traced in render-core.md / renderer.md. - WINMM is the game executable's joystick source (input.md) and a timer source, and WAIL32's wave/timer backend — the same DLL crosses several subsystems.
Related¶
- wail32.md · comms.md — FA's companion binaries (documented as subsystems).
- architecture.md — the overall binary/overlay architecture.
- network.md — the game executable's multiplayer, which uses
MSAPI.dll+ DirectPlay.
Toolkit
CLI Reference¶
All commands follow the pattern fx <subsystem> <subcommand> [args].
Quick Reference¶
fx lib ls / unpack / extract / pack / patch # .LIB archive management
fx pic info / unpack / pack # .PIC images (dense, sparse, JPEG)
fx seq dump / unpack / pack # .SEQ cutscene timelines
fx audio info / unpack / pack # .11K / .5K / .8K raw PCM audio
fx ot info / unpack / pack # object type definitions
fx pt info / unpack / pack # aircraft type definitions
fx nt / jt / see / ecm / gas ... # other type definitions
fx mission info / unpack / pack # .M / .MM mission and map files
fx sh info / unpack # .SH 3D shapes → Wavefront OBJ
fx raw info / unpack / pack # .RAW in-game screenshots ↔ PNG
fx sms dump # FA.SMS symbol map → CSV
fx t2 info / dump / heightmap # .T2 terrain maps (heights, textures)
fx plt info / dump # .P pilot save file
fx pal info / dump # .PAL VGA palettes
fx inf dump # .INF aircraft tech sheets
fx hud dump / set # .HUD layout overlays
fx lay dump / gradient / set # .LAY sky/atmosphere layers
fx fnt info / unpack / pack # .FNT bitmap fonts (x86 glyph recompiler)
fx mus dump # .MUS music sequencer bytecode
fx bi dump / decompile # .BI disassembler + BI→AI decompiler
fx ai compile # .AI → .BI compiler
lib — Archive¶
fx lib ls <file.LIB>
fx lib unpack <file.LIB> [output_dir]
fx lib extract <file.LIB> <NAME> [NAME ...] [-o output_dir]
fx lib pack <dir> <output.LIB>
fx lib repack <src.LIB> <output.LIB>
fx lib patch <src.LIB> <name> <file> <output.LIB>
fx lib ls <file.LIB>¶
List the contents of a .LIB archive.
> fx lib ls FA_2.LIB
Name Flags Size
------------- ----- --------
_AFTB2.11K dcl 26488
BALTIC.TXT dcl 3421
PALETTE.PAL dcl 2310
...
5405 file(s)
Flags: raw = uncompressed, lzss = LZSS, pxpk = PxPk, dcl = PKWare DCL.
fx lib unpack <file.LIB> [output_dir]¶
Extract and decompress all files. Output defaults to the archive stem.
fx lib unpack FA_2.LIB out/FA_2
fx lib unpack FA_3.LIB # extracts to ./FA_3/
Entry names are written through ealib_safe_name: the characters
& * ? " < > | / \ : each become _ (e.g. the looping-audio prefix in
&AFTB2.11K extracts as _AFTB2.11K), so output filenames are identical on
every platform and a crafted archive cannot write outside the output
directory. File paths on the command line follow the operating system's case
rules (exact case required on Linux); entry names inside archives are
case-insensitive everywhere.
fx lib extract <file.LIB> <NAME> [NAME ...] [-o output_dir]¶
Extract one or more named entries. Output defaults to the current directory;
use -o to redirect. Name matching is case-insensitive.
fx lib extract FA_2.LIB BALTIC.TXT
fx lib extract FA_2.LIB F16C_0.PIC F15C_0.PIC -o pics
fx lib pack <dir> <output.LIB>¶
Pack all files in dir into a new .LIB. Files are stored uncompressed (flags=0)
and ordered by name, so the same input directory produces a byte-identical
archive on every platform. The game accepts both raw and compressed entries.
fx lib repack <src.LIB> <output.LIB>¶
Rebuild the container from its own directory: payloads stay raw (still
compressed), entry metadata is copied verbatim, and every offset — including
the directory's terminator entry — is recomputed from scratch. Output is
byte-identical to the input for well-formed archives; the fa_repack_roundtrip
integration test (FX_FA_ROOT mode) proves that against every .LIB in a real
install.
fx lib patch <src.LIB> <name> <file> <output.LIB>¶
Replace one named entry without touching the rest of the archive.
fx lib patch FA_2.LIB BALTIC.TXT edits/BALTIC.TXT FA_2_mod.LIB
fx lib patch FA_3.LIB F16C_0.PIC F16C_mod.PIC FA_3_mod.LIB
See also: fa/formats/LIB.md
pic — Images¶
fx pic info <file.PIC>
fx pic unpack <file.PIC> [-p PALETTE.PAL] [-o output.png]
fx pic pack <file.png> [-p PALETTE.PAL] [-o output.PIC]
fx pic repack <file.PIC> [-o output.PIC]
fx pic info <file.PIC>¶
Print the PIC header: format, dimensions, palette and span offsets.
fx pic unpack <file.PIC> [-p PALETTE.PAL] [-o output.png]¶
Decode to PNG. Handles all three sub-formats: JPEG, dense (format 0), and sparse (format 1).
-p is required for paletted PICs; omit for JPEG.
fx pic pack <file.png> [-p PALETTE.PAL] [-o output.PIC]¶
Encode to a dense PIC (format 0) with a full 256-color inline palette. Pixels with
alpha < 128 map to transparent (index 0xFF). Always provide the same PALETTE.PAL
used during unpack.
fx pic repack <file.PIC> [-o output.PIC]¶
Byte-identical structural repack: re-derives every region from the parsed header
and re-emits the file by construction (whole-file passthrough for JPEG PICs).
Without -o it verifies only — exit 0 means the file re-emits byte-identically;
a byte no documented region accounts for fails the repack instead of being
silently copied.
See also: fa/formats/PIC.md · fa/formats/PAL.md
seq — Cutscene timelines¶
fx seq dump <file.SEQ>
fx seq unpack <file.SEQ> [-o out.txt]
fx seq pack <in.txt> -o <out.SEQ>
fx seq dump <file.SEQ>¶
Pretty-print all events to stdout.
fx seq unpack / pack¶
Round-trip editable text. Output is byte-identical to originals.
See also: fa/formats/SEQ.md
audio — PCM audio¶
fx audio info <file.11K|.5K>
fx audio unpack <file.11K|.5K> [-o out.wav] [-r hz]
fx audio pack <in.wav> -o <out.11K|.5K> [-r hz]
Sample rate is inferred from the file extension (.11K = 11025 Hz, .5K = 5512 Hz).
Override with -r. Input WAV for packing must be mono and 8-bit.
See also: fa/formats/11K.md
ot / nt / pt / jt / see / ecm / gas — Type definitions¶
All seven type definition formats share the same subcommand pattern:
fx <type> info <file>
fx <type> unpack <file> [-o out.txt]
fx <type> pack <in.txt> -o <out>
| Command | Format | Contents |
|---|---|---|
fx ot |
.OT |
Generic object type |
fx nt |
.NT |
NPC / crew type |
fx pt |
.PT |
Plane type (aircraft aerodynamics + avionics) |
fx jt |
.JT |
Jettison / weapon type |
fx see |
.SEE |
Seeker (missile guidance) type |
fx ecm |
.ECM |
ECM pod type |
fx gas |
.GAS |
Gas / smoke type |
info prints every field annotated with the schema tables from
fa/formats/OT.md and its extensions; each extension
section restarts its schema at the section's first field. Version-dependent
fields can shift label alignment mid-section — see the format specs' Open
Questions.
> fx pt info F16C.PT
File: F16C.PT (219 fields, 20 tables)
--- OT/General Section (struct_type=5) ---
[ 0] struct_type = 5 (5) ; 1=OT 3=NT 5=PT 7=JT
[ 3] names = ot_names -> "F-16C" ; ptr -> short, long, filename
[ 6] shape = shape -> "f16.SH" ; ptr -> .SH filename
...
--- NT/Npc Extension (struct_type>=3) ---
[ 60] npc_flags = 0 (0) ; u32 bitfield; bits 18-20/25-26 control AI state
...
--- PT/Plane Extension (struct_type>=5) ---
[ 69] pt_flags = 32767 (32767) ; $1=Jet $2=Hook $4=TwoSeat $8=Helo $10=Eject $20=VTOL $40=Carrier $80=Bay
...
--- Pointer Tables ---
:ot_names
"F-16C"
...
See also: fa/formats/BRF.md · fa/formats/OT.md · fa/formats/NT.md · fa/formats/PT.md · fa/formats/JT.md · fa/formats/SEE.md · fa/formats/ECM.md · fa/formats/GAS.md
mission / mm — Mission and map files¶
fx mission info <file.M|.MM>
fx mission unpack <file.M|.MM> [-o out.txt]
fx mission pack <in.txt> -o <out.M|.MM>
fx mm is an alias for .MM map files. Round-trips byte-identically for all 592
mission files in FA_2.LIB.
See also: fa/formats/M.md · fa/formats/MM.md
sh — 3D shapes¶
fx sh info <file.SH>
fx sh unpack <file.SH> [-o out.obj]
fx sh info <file.SH>¶
Print scale factor, bounding box (feet), vertex count, face count, and texture names.
fx sh unpack <file.SH> [-o out.obj]¶
Export geometry to Wavefront OBJ with usemtl directives for texture references.
Open in Blender, MeshLab, or any 3D viewer.
65 of 1275 FA shape files use x86 machine code for rendering (particle effects, AC130, etc.) and produce no OBJ output. All others extract cleanly.
See also: fa/formats/SH.md
cb8 — FMV video¶
fx cb8 info <file.CB8>
fx cb8 frames <file.CB8> [-o output_dir]
fx cb8 unpack <file.CB8> [-o output_dir]
fx cb8 repack <orig.CB8> <png_dir> [-o out.CB8]
fx cb8 info <file.CB8>¶
Print video dimensions, frame count, frame rate, and total duration.
> fx cb8 info JANELOGO.CB8
video: 320 x 240, 466 frames, 15.0 fps, 31.07 s
audio: 11025 Hz PCM, 400 sync ticks/frame
fx cb8 frames <file.CB8> [-o output_dir]¶
Decode every frame to a PGM image (raw 8-bit palette indices) in output_dir
(default: current directory). Files are named frame0000.pgm, etc. Every
frame is a self-contained key frame; frames decode in any order.
fx cb8 unpack <file.CB8> [-o output_dir]¶
Decode every frame to a colour PNG through its embedded per-frame palette
(no external PAL applies to CB8). Files are named frame0000.png, etc.
fx cb8 repack <orig.CB8> <png_dir> [-o out.CB8]¶
Rebuild a movie around edited frames: the PNGs (one per original frame, same
dimensions, ≤ 256 distinct colours each) are re-encoded as CB8 key frames
with rebuilt per-frame palettes and codebooks, while the DRBC header, every
audio chunk, the stream order, and the VooM timing carry over from
orig.CB8 verbatim. The unpack→repack loop is pixel-exact; byte identity is
a non-goal (the encoder chooses its own codebook packing).
See also: fa/formats/CB8.md
raw — Screenshots¶
fx raw info <file.RAW>
fx raw unpack <file.RAW> [-o out.png]
fx raw pack <file.png> [-o out.RAW]
fx raw info <file.RAW>¶
Print the capture header: magic and dimensions (width and height are u16 big-endian at +8/+10 — confirmed against captures at four resolutions).
fx raw unpack <file.RAW> [-o out.png]¶
Convert an in-game screenshot (Ctrl-Alt-Shift-V, written to the install directory) to PNG using the file's embedded 8-bit palette.
fx raw pack <file.png> [-o out.RAW]¶
Convert a PNG back to a RAW screenshot: the embedded palette is rebuilt from the image's distinct colours in first-seen order (max 256; alpha ignored). The PNG→RAW→PNG loop is pixel-exact.
See also: fa/formats/RAW.md
sms — Symbol map¶
fx sms dump <FA.SMS> [-o out.csv]
fx sms dump <FA.SMS> [-o out.csv]¶
Export all 3,829 MSVC C++ mangled symbols from FA.SMS to a two-column CSV
(va,name), sorted by virtual address with ties broken by name — the output
is byte-identical on every platform. Without -o, prints to stdout. The CSV
uses LF line endings on every platform (previously CRLF on Windows).
> fx sms dump FA.SMS -o symbols.csv
FA.SMS -> symbols.csv (3829 symbols)
The CSV can be imported directly into Ghidra (Script Manager -> ImportSymbolsScript) or IDA Pro to auto-label all known functions and data symbols.
See also: fa/formats/SMS.md
t2 — Terrain map¶
fx t2 info <file.T2>
fx t2 dump <file.T2> [--leaves]
fx t2 heightmap <file.T2> <out.png>
fx t2 info <file.T2>¶
Print the theater name and texture atlas, grid dimensions, leaf grid, the leaf elevation-band range, and the surface class distribution (water vs land, top land classes by count). Grid is width × height per the engine's field map, and the distribution counts the per-tile summary records (the authored far-LOD array — see fa/formats/T2.md § Data Payload).
> fx t2 info UKR.T2
Theater: UKR (Ukraine)
Atlas: ukr.PIC
Grid: 26 x 25 (650 tiles)
Leaves: 208 x 200 (8 per tile side)
Elevation: leaf bands 0..6
Surface: water 195 (30.0%) land 455 (70.0%)
Land classes:
0xD0 21 tiles (3.2%)
0xD2 36 tiles (5.5%)
...
fx t2 dump <file.T2> [--leaves]¶
CSV of the terrain records to stdout — x,y,surface_class,elevation,
texture_variant, row-major. By default dumps the per-tile summary array;
--leaves dumps the full-resolution leaf grid (8×8 leaves per tile).
fx t2 heightmap <file.T2> <out.png>¶
Export the leaf elevation bands as a grayscale PNG, one pixel per leaf (e.g. 256×256 for a 32×32-tile theater). Bands are normalized to the file's own maximum so the relief is visible.
T2 files are stored in FA_2.LIB; unpack the archive first.
See also: fa/formats/T2.md
plt — Pilot save¶
fx plt info <file.P>
fx plt dump <file.P>
fx plt info <file.P>¶
Print pilot identity fields and active campaign state from a .P pilot save file.
> fx plt info PLT441.P
File: PLT441.P (9696 bytes)
Name: Maverick
Callsign: MAVERICK
Rank: Captain
Voice: ^ACID.5K
Nose art: NOSE01
Left decal: LEFT03
Right decal:RIGHT03
Portrait: PILOT02
Campaign: UKRAINE.CAM (Ukraine Crisis)
Aircraft: F16C.PT
Pool: F16C.PT, F15C.PT
Ordnance:
AIM9M.JT x4
AIM120.JT x2
MK82.JT x6
Sensors: F16CSEE.SEE
fx plt dump <file.P>¶
Print the confirmed stats block (kill tallies, mission counters, weapon accuracy
at 0x1F80–0x21F7). Decoding the remaining gap regions is tracked in
#29.
Pilot save files (.P) are stored in the FA install directory alongside FA.EXE.
The stats block (offsets 0xB0–0x0D7E) is not yet decoded; only the identity and
campaign blocks are read.
See also: fa/formats/P.md
pal — VGA palettes¶
fx pal info <file.PAL>
fx pal dump <file.PAL> [-o out.png]
fx pal info <file.PAL>¶
Print the entry count and header details of a 256-color 6-bit VGA palette.
fx pal dump <file.PAL> [-o out.png]¶
Render the palette as a swatch-grid PNG for visual inspection.
See also: fa/formats/PAL.md
inf — Aircraft tech sheets¶
fx inf dump <file.INF>
fx inf dump <file.INF>¶
Print the technical info sheet: aircraft metadata and the briefing-room scene data (RTF text section and scene parameters).
See also: fa/formats/INF.md
hud — HUD layout overlays¶
fx hud dump <file.HUD>
fx hud set <file.HUD> <gauge.field=value ...> [-o out.HUD]
fx hud dump <file.HUD>¶
Print the HUD overlay DLL's gauge parameter table, advisory icon labels, and asset-string references as JSON.
fx hud set <file.HUD> <gauge.field=value ...> [-o out.HUD]¶
Edit gauge parameters (keys as fx hud dump prints them, e.g.
speed_tape.dx=-40) and advisory icon labels (icon_a…icon_d, up to 8
characters). Everything else — asset strings, PE container, unmodelled
bytes — carries over verbatim; with no edits the output is byte-identical
(proven over all 46 install HUDs).
> fx hud set F16C.HUD speed_tape.dx=-40 icon_d=BAY -o F16C_mod.HUD
F16C.HUD -> F16C_mod.HUD (4608 bytes, 2 edit(s))
See also: fa/formats/HUD.md
lay — Sky/atmosphere layers¶
fx lay dump <file.LAY>
fx lay gradient <file.LAY> [-o output.png]
fx lay set <file.LAY> <key=value ...> [-o out.LAY]
fx lay dump <file.LAY>¶
Print the sky and atmosphere lookup-table structure of a .LAY overlay DLL.
fx lay gradient <file.LAY> [-o output.png]¶
Render the atmosphere gradient tables to a PNG.
fx lay set <file.LAY> <key=value ...> [-o out.LAY]¶
Edit header scalars (sky_angle_scale, below_angle_scale) and per-layer
fields addressed as layerN.<field> — the scalar names fx lay dump
prints, plus cloud_pic / sky_pic (up to 22 characters). The gradient
ramps are editable through the library API only. The layer count and end
sentinel cannot change; unmodelled bytes carry over verbatim, so with no
edits the output is byte-identical (proven over all 24 install LAYs).
> fx lay set DAY1.LAY layer0.fog_density=9 sky_angle_scale=123 -o DAY1_mod.LAY
DAY1.LAY -> DAY1_mod.LAY (20992 bytes, 2 edit(s))
See also: fa/formats/LAY.md
fnt — Bitmap fonts¶
fx fnt info <file.FNT>
fx fnt unpack <file.FNT> [-o output_dir]
fx fnt pack <orig.FNT> <dir> [-o out.FNT]
fx fnt info <file.FNT>¶
Print glyph count and font metrics from a font overlay DLL.
fx fnt unpack <file.FNT> [-o output_dir]¶
Extract every glyph as an image into the output directory, plus a metrics.csv
(ascii,char,width,height). The CSV uses LF line endings on every platform
(previously CRLF on Windows).
fx fnt pack <orig.FNT> <dir> [-o out.FNT]¶
Rebuild the font DLL from an unpack directory: printable glyphs re-read from
glyph_sheet.png (white = set), widths and height from metrics.csv, and
each glyph recompiled to x86 with the original compiler's canonical
encoding. Everything else in the container carries over from orig.FNT
verbatim; an unedited unpack→pack loop is byte-identical. Edited glyph code
must fit the original code region.
See also: fa/formats/FNT.md
mus — Music sequencer bytecode¶
fx mus dump <file.MUS>
fx mus dump <file.MUS>¶
Disassemble the in-flight music sequencer bytecode: playlists, transitions, and opcode listing.
See also: fa/formats/MUS.md
bi — Compiled AI bytecode¶
fx bi dump <file.BI>
fx bi decompile <file.BI>
fx bi dump <file.BI>¶
Disassemble compiled .BI AI bytecode to readable mnemonics with
cross-referenced label annotations and resolved CALL_BY_NAME targets.
fx bi decompile <file.BI>¶
Recover recompilable .AI source from .BI bytecode (printed to stdout) — the
inverse of fx ai compile. The reconstructed source recompiles byte-identically
to the input, so fx ai compile and fx bi decompile form a round-trip for any
BI fx produced. Only the fx CALL_BY_NAME bytecode dialect is accepted; the
stock game BIs use the original toolchain's CALL_DIRECT thunks and are
rejected (use fx bi dump for those). Labels are synthesized (L####) and
comments are not recovered.
See also: fa/formats/BI.md
ai — AI script compiler¶
fx ai compile <file.AI> -o <file.BI>
fx ai compile <file.AI> -o <file.BI>¶
Compile a plain-text .AI script to the Phar Lap PE .BI bytecode format the
game's AI interpreter loads. All nine stock flight AIs compile to valid
bytecode.
See also: fa/formats/AI.md, fa/formats/BI.md
fbc — Video frame index¶
fx fbc info <file.FBC>
fx fbc ls <file.FBC>
fx fbc info <file.FBC>¶
Frame count, total frame-data bytes, and the file size the paired .VDO is
expected to have (816-byte header plus the sum of all frame sizes).
fx fbc ls <file.FBC>¶
Per-frame table: frame number, byte size, and the frame's byte offset inside
the paired .VDO.
See also: fa/formats/FBC.md, fa/formats/VDO.md
vdo — Briefing video¶
fx vdo info <file.VDO> [file.FBC]
fx vdo export <file.VDO> <file.FBC> [-o dir]
Read-only decoder for RATVID .VDO mission-briefing movies (the codec is a
per-pixel copy-mask delta scheme; see the format spec). Both subcommands need
the paired .FBC frame-size index to locate frame boundaries.
fx vdo info <file.VDO> [file.FBC]¶
Resolution, frame rate, and paired-audio sample rate from the header. Supply the
.FBC to also report the frame count.
fx vdo export <file.VDO> <file.FBC> [-o dir]¶
Decode every frame to frame%04u.png in the output directory (default .).
Frames are inter-coded, so they are decoded in sequence.
See also: fa/formats/VDO.md, fa/formats/FBC.md
bin — Lookup tables¶
fx bin info <file.BIN>
fx bin info <file.BIN>¶
Identify the table from the filename (the bytes carry no structure — see the spec) and check the size against the documented inventory. Exits nonzero on a size mismatch for a known table.
See also: fa/formats/BIN.md
cam — Campaign DLLs¶
fx cam info <file.CAM>
fx cam strings <file.CAM> [-n MIN]
fx cam info <file.CAM>¶
Validate the MZ + Phar Lap PL container and report the CODE section
geometry plus the embedded-string count.
fx cam strings <file.CAM> [-n MIN]¶
Dump the campaign's embedded string tables (mission list, aircraft types,
weapon pool, state keys) — printable runs of at least MIN characters
(default 3), one per line on stdout.
See also: fa/formats/CAM.md
txt — In-game text¶
fx txt info <file.TXT>
fx txt info <file.TXT>¶
Classify the file (campaign description / UI layout template / plain text), summarize its directive structure (sections, page breaks, buttons, pictures), and confirm the parse round-trips byte-identically.
See also: fa/formats/TXT.md
cfg — Game configuration¶
fx cfg info <EA.CFG>
fx cfg info <EA.CFG>¶
Dump the 347-byte CONFIG struct: input devices, sound and volume settings, preference flag words, pilot/callsign/squadron strings, and the three untraced pass-through fields — then confirm the byte-identical round-trip.
See also: fa/formats/CFG.md
dat — Network configuration¶
fx dat info <NET.DAT|MODEM.DAT|SERIAL.DAT>
fx dat info <file.DAT>¶
Dump the 3,552-byte CN_INFO struct shared by all three transport configs: version, callsign, active transport, serial/modem parameters, phone-book usage, and the TCP/IP address fields — then confirm the byte-identical round-trip (checksum and unmapped regions pass through verbatim).
See also: fa/formats/DAT.md
effect — GRAPHIC effect-spawn data¶
fx effect types # effect type -> class / .SH shape
fx effect dump <table.bin> [-n N] # decode N 0x30-byte parameter records
fx effect spawn <record.bin> # decode a 17-byte MSG 0x8003 spawn record
fx effect types¶
Print the effect type → class / .SH shape classification (types 0x00–0x2A)
recovered from _GRAPHICInit@0. Needs no game data.
fx effect dump <table.bin> [-n N]¶
Decode N consecutive 0x30-byte effect-parameter records from a raw buffer
(the table sliced from the executable, or a fixture) — intensity, frame count,
sub-type / ground-burst flag, debris count/spread, sound-variant count, pitch.
fx effect spawn <record.bin>¶
Decode a 17-byte MSG 0x8003 network spawn record — type, F24.8 position,
owner, flags.
See also: fa/formats/EFFECT.md
mnu — Menu DLLs¶
fx mnu info <file.MNU>
fx mnu strings <file.MNU> [-n MIN]
fx mnu info <file.MNU>¶
Validate the MZ + Phar Lap PL container and report the CODE section
geometry plus the embedded-string count.
fx mnu strings <file.MNU> [-n MIN]¶
Dump the embedded menu label strings — printable runs of at least MIN
characters (default 3), one per line on stdout.
See also: fa/formats/MNU.md
mt — Mission briefing text¶
fx mt info <file.MT>
fx mt info <file.MT>¶
Extract the section-1 header facts (mission id, source name, title, mission type), count the sections (2 = briefing, 3–5 = debrief outcomes), and confirm the parse round-trips byte-identically.
See also: fa/formats/MT.md, fa/formats/TXT.md
pts — Aircraft screen assets¶
fx pts info <file.PTS>
fx pts info <file.PTS>¶
Validate the MZ + Phar Lap PL container and report the CODE section
geometry plus the referenced ICON*.PIC aircraft icon.
See also: fa/formats/PTS.md
rgn — Installer region maps¶
fx rgn info <file.RGN>
fx rgn dump <file.RGN>
fx rgn info <file.RGN>¶
Record count, rectangle count, and the byte-identical round-trip check.
fx rgn dump <file.RGN>¶
Per-record table: name, vertex count, and coordinates.
See also: fa/formats/RGN.md
ssf — Installer scripts¶
fx ssf info <file.SSF>
fx ssf dump <file.SSF>
fx ssf info <file.SSF>¶
Per-keyword statement counts and the byte-identical round-trip check.
fx ssf dump <file.SSF>¶
Every statement with its source line, keyword, and unquoted arguments.
See also: fa/formats/SSF.md
esa — Installer archive¶
fx esa ls <SETUP.ESA>
fx esa info <SETUP.ESA>
fx esa extract <SETUP.ESA> <NAME> [NAME ...] [-o dir]
fx esa unpack <SETUP.ESA> [-o dir]
fx esa repack <SETUP.ESA> <out.ESA>
fx esa ls <SETUP.ESA>¶
The directory: each entry's name, label, flags, method (PKWA/NULL), and
uncompressed/stored sizes.
fx esa info <SETUP.ESA>¶
Entry and method counts, directory size, total uncompressed bytes, and the byte-identical repack check.
fx esa extract <SETUP.ESA> <NAME> [-o dir]¶
Extract named entries — PKWA entries are DCL-decoded, NULL entries copied.
fx esa unpack does the same for every entry.
fx esa repack <SETUP.ESA> <out.ESA>¶
Rebuild the container from its own directory (payloads kept stored); byte-identical for a well-formed archive.
See also: fa/formats/ESA.md
install — Install the game from its discs¶
fx install plan <disc-dir> [disc-dir ...] [-d dir] [options]
fx install run <disc-dir> [disc-dir ...] -d <dir> [options]
fx install verify <disc-dir> [disc-dir ...] -d <dir> [options]
Executes the .SSF installer scripts against SETUP.ESA — what SETUP.EXE
does, portably — and copies the game to a directory of your choosing. It needs
your own discs: nothing is shipped.
A disc is a directory: point it at an ISO mount, an extract of one, or the drive itself, in any order. Which disc is which is decided by content, not by volume label. A full install copies everything, including the CD-resident LIBs the game would otherwise stream off the disc, so no disc is needed to play.
| Option | |
|---|---|
-d <dir> |
the install directory (required for run/verify) |
--full / --minimal |
the full install, or the minimal one (no FA_4B.LIB, the digital-music archive). Default: full |
--verify |
after run, byte-compare every file back against the disc |
--overwrite |
replace files already in the destination. Files SKIP_ON_REMOVE marks as the game's own — pilots, missions, EA.CFG, screen captures — are preserved even so |
--no-cd-resident |
skip the CD-resident LIBs (a ~110 MB install that still needs a disc) |
--patch <fae102.exe> |
after installing, apply the 1.02F updater in place — bring the 1.00F disc install up to the 1.02F build the reconstruction database describes (fa/formats/RTP.md). Runs after --verify, so verification still checks the fresh 1.00F tree against the disc |
--any-media |
proceed on media whose build cannot be fingerprinted |
--json |
machine-readable plan (the shape fxe's first-run reads). Under --json, stdout carries the plan and nothing else; the scan banner, progress, and diagnostics all go to stderr |
fx install plan <disc-dir>…¶
The dry run — it writes nothing. Prints the media's build, which script was
chosen, every file with its action (copy / keep / skip), the byte total,
and every script directive that was not acted on, with the reason. Reading this
before an install is the point of it.
fx install run <disc-dir>… -d <dir>¶
Performs the install. Payloads stream, so a 989 MiB install runs in a few MB of
memory; each file is written to a .part and renamed once complete, so an
interrupted run leaves nothing that looks finished.
$ fx install run /run/media/you/FA_1_00F /run/media/you/FA_1_00F1 -d ~/games/fa --verify
disc 1: /run/media/you/FA_1_00F
disc 2: /run/media/you/FA_1_00F1
media: 1.00F (the 1.02F patch is not applied; the symbol database describes 1.02F)
script: FINSTALL.SSF
...
installed 1036798285 bytes to ~/games/fa (build 1.00F)
verifying against the disc...
verified: every installed byte matches the disc
The discs carry the 1.00F build, while the reconstruction database describes
the patched 1.02F one — fx install always prints which build it wrote, and
--patch <fae102.exe> upgrades the fresh install to 1.02F in place (the four
modified game files; the multiplayer msapi.dll is a follow-up, RTP.md #54). See
fa/formats/ESA.md § File Inventory and
fa/formats/RTP.md.
Everything above is exercised against the retail discs by the fa_disc_install
integration test — see development.md § Real-media install mode.
fx install verify <disc-dir>… -d <dir>¶
Re-derives every file from the disc and byte-compares it against the install.
Unlike run, it plans as though the destination were empty, so it checks
everything a fresh install would write — including the files an install would
otherwise preserve. Editing a mission will therefore be reported: that .MT no
longer matches the disc, which is true.
See also: fa/formats/SSF.md § Engine Notes, fa/formats/ESA.md
patch — Apply the 1.02F updater¶
fx patch inspect <patch.exe>
fx patch apply <patch.exe> --source <dir> --out <dir> [--file NAME] [--no-checksum]
Applies the Pocket Soft .RTPatch payload carried by the FA updater (fae102.exe)
to reconstruct the 1.02F game files from the 1.00F originals — the build
the discs ship versus the build the reconstruction database describes. It needs
your own 1.00F files as the source; nothing is shipped.
fx patch inspect <patch.exe>¶
Locate the RTPatch container overlay and list every record — filename, mode
(modify/new), source and target sizes, and the source rolling checksum — plus
any files the installer relocates to a system directory.
fx patch apply <patch.exe> --source <dir> --out <dir>¶
Reconstruct each patched file from the matching original in --source and write
it to --out. Each source is verified against the record's checksum first, so a
wrong 1.00F version is skipped rather than corrupted; --no-checksum forces the
apply and --file NAME limits it to one target. The reconstruction is
byte-identical to the shipped 1.02F build.
$ fx patch apply fae102.exe --source ~/games/fa --out ~/games/fa-1.02f
[ ok ] FA.EXE 1319424 bytes
[ ok ] FA.SMS 106706 bytes
...
4 patched, 0 skipped, 0 failed
See also: fa/formats/RTP.md
mc — Mission condition DLLs¶
fx mc info <file.MC>
fx mc strings <file.MC> [-n MIN]
fx mc info <file.MC>¶
Validate the MZ + Phar Lap PL container and report the CODE section
geometry plus the embedded-string count.
fx mc strings <file.MC> [-n MIN]¶
Dump the embedded strings — including the imported mission-condition API names — one per line on stdout.
See also: fa/formats/MC.md
hgr — Hangar screen DLLs¶
fx hgr info <file.HGR>
fx hgr strings <file.HGR> [-n MIN]
fx hgr info <file.HGR>¶
Validate the MZ + Phar Lap PL container and list the referenced PIC assets
(hangar background layers and the selection-icon atlas).
fx hgr strings <file.HGR> [-n MIN]¶
Dump the embedded strings, one per line on stdout.
See also: fa/formats/HGR.md
dlg — Menu dialog DLLs¶
fx dlg info <file.DLG>
fx dlg strings <file.DLG> [-n MIN]
fx dlg info <file.DLG>¶
Validate the MZ + Phar Lap PL container and report the CODE section
geometry (the control dispatch table) plus the embedded-string count.
fx dlg strings <file.DLG> [-n MIN]¶
Dump the dialog's embedded control label strings, one per line on stdout.
See also: fa/formats/DLG.md
xmi — Extended MIDI¶
fx xmi info <file.XMI>
fx xmi export <file.XMI> [-s N] -o <out.mid>
fx xmi info <file.XMI>¶
Report the sequence count, and per sequence its timbre count and chunk inventory (TIMB, EVNT, …).
fx xmi export <file.XMI> [-s N] -o <out.mid>¶
Export sequence N (default 0) to a Standard MIDI File (format 0): the AIL
delay encoding becomes SMF variable-length deltas and each note-on's XMI
duration becomes a scheduled note-off. One-way translation, not a round-trip.
See also: fa/formats/XMI.md, fa/formats/MUS.md
fxs — Graphical Editor¶
fxs is the interactive validation layer for the FA asset format research. It
exercises the fx_lib codecs against real game data and makes format behaviour
directly observable — loading a LIB archive, editing a type definition, or previewing
a decoded image confirms that the underlying format understanding is correct.
FATK (DuoSoft 1998), the original FA editor, is a 16-bit/32-bit VB6 application that
does not run on 64-bit Windows. fxs covers the same ground and more, but
replacing FATK is a byproduct rather than the goal.
Platforms¶
fxs runs natively on Linux and Windows from the same code: SDL3 windowing with
an OpenGL 3.3 core renderer through Dear ImGui, and miniaudio for audio preview
(backend rationale in ADR-0001).
- Theming — Auto follows the desktop's dark/light preference (via
SDL_GetSystemTheme) and switches live; Dark/Light can be forced in Preferences. On bare X11 without a desktop portal the system preference is unknown and Auto falls back to dark. - DPI — UI metrics and fonts scale to the window's display scale, and rescale live when the window moves to a display with a different scale.
- Wayland — window position cannot be saved or restored (a Wayland design decision); size and maximized state still persist.
--smoke [LIB ...]— headless self-check: with no arguments, renders three frames without showing a window (falling back to SDL's offscreen driver when no display server exists) and exits 0; CI runs it as thegui_smokectest. Given LIB paths, it opens each archive and cycles every entry through its editor and the preview — one rendered frame per record — then mounts the LIBs' directory as a workspace and cycles every icon-bar view (each category browser and Archives), opening the first object of each category — exercising extraction, every parser, the GPU upload paths, and the category browsers against real game data.--render <LIB> <ENTRY> [--out file.png] [--size WxH] [--software]— headless PNG snapshot: opens the archive, selects the entry (by 8.3 name likeA10.SHor numeric index), settles the preview, and writes a PNG of the whole window through the same render path the interactive app uses. If the first argument is a directory, it is mounted as a workspace instead and<ENTRY>names either the icon-bar view to capture (e.g.aircraft,archives; default Aircraft) — the headless way to review the category browsers — or a workspace entry likeA10.PT, which selects that object so the capture shows its scoped file cluster.--softwarerenders the SH preview through the FA-faithful software rasteriser (fx_render::fa, #290) instead of OpenGL — the headless way to produce software/GL side-by-side captures. Like--smokeit needs no visible window (offscreen fallback when there is no display server).--outdefaults torender.png;--sizedefaults to the standard window size and is clamped to the minimum (the compositor may scale the actual pixel dimensions on HiDPI). Intended for automated visual review of rendering changes — SH 3D orbit, PIC/RAW/CB8 images, and the editors. The environment variableFX_ARTIC="input=value"(e.g._PLgearDown=1) forces one SH articulation state before the capture, for reviewing a moving-part variant.
On Windows, fxs.exe is a WIN32-subsystem (GUI) binary, so shells launch
it detached: a bare --smoke invocation from PowerShell prints nothing,
returns immediately, and never sets $LASTEXITCODE, while the sweep runs
invisibly in the background. Pipe the output so the shell attaches stdout and
waits for the exit code:
$fa = "C:\path\to\Fighters Anthology"
build\gui\Release\fxs.exe --smoke (Get-ChildItem "$fa\*.lib").FullName | Out-Host
echo $LASTEXITCODE # expect 0, with one "swept" line per LIB
FA installs mix filename case (FA_1.LIB, fa_7.lib). Get-ChildItem
matches case-insensitively; a case-sensitive POSIX glob like *.LIB silently
misses the lowercase ones.
Layout¶
Three-panel window:
| Panel | Contents |
|---|---|
| Left — Navigator | An icon bar selecting a category browser (named objects from the mounted workspace) or the raw Archives LIB browser |
| Center — Editor | Form/text/timeline editor for the selected record |
| Right — Preview | Live preview: images (PIC with palette switcher, RAW screenshots), the SH 3D orbit view, and the T2 terrain viewer |
Menu bar: File · View · Tools · Help
| Menu | Items |
|---|---|
| File | Open LIB… (Ctrl+L), Open File… (Ctrl+F), Recent Files, Mount FA Workspace, Close / Close All, Preferences…, Exit |
| View | Expand All, Collapse All (LIB browser sessions) |
| Tools | Install <session> as FA_0.LIB |
Library & Project Management¶
- Open FA / USNF97 / ATF Gold
.LIBfiles; open loose files directly (BRF type records, PIC, RAW, audio, missions, SEQ, INF, SH, PLT, PAL) - Multiple files open simultaneously; each appears as a collapsible session in the LIB browser
- Browse library contents with type labels (Aircraft, Ordnance, Image, Audio, Mission, …) and file sizes
- Filter records by name or type
- Session table height is individually resizable by dragging the handle below each session; double-click the handle to snap to full height
- Right-click a session header for per-session Close and Install options
- Right-click empty browser space (or use View menu) for Expand All / Collapse All
- Select a session to enable File → Close
<name>; File → Close All closes everything - Extract individual records or extract the entire LIB via the CLI (
fx lib unpack) - Patch edited records back into the in-memory LIB
- Install the modified LIB as
FA_0.LIBin the configured FA install directory (one-click override)
Type Definition Editing (BRF: OT / NT / PT / JT / SEE / ECM / GAS)¶
Form-based editor showing every field with its type token, human-readable name (where
mapped), and annotation (units, enum values). Changes are patched back via
brf_serialize for a perfect round-trip.
| Extension | Record type | Key fields |
|---|---|---|
.PT |
Aircraft | Thrust, speed, climb/dive limits, G-envelope, hardpoints (up to 9×: position, type, ordnance, qty), damage thresholds, agility, bank/corner/acceleration |
.OT |
Static object | Hitpoints, flags, damage resistance, shape assignment |
.NT |
NPC / vehicle | Speed, turn rate, damage, hardpoints, AI params |
.JT |
Ordnance / weapon | Burst characteristics, guidance params, range, hit %, damage, projectile speed, firing arc |
.SEE |
Seeker / radar | Search/track lobes, cone angles, ranges, detection probability |
.ECM |
ECM suite/pod | Chaff/flare effectiveness, jamming effectiveness, power bitmask |
.GAS |
External fuel tank | Empty weight, fuel weight |
Image Editing (PIC / PAL)¶
- Preview PIC files in the Preview panel (decoded via
fx::pic_decode) - Export PIC → PNG (uses the active preview palette, so the file matches the preview)
- Import PNG or BMP → PIC (dense format, full inline palette)
- Supports dense (format 0), sparse (format 1), and JPEG (format 0xD8FF) PIC sub-formats
- Covers aircraft skins, icons, nose art, tail art, and pilot portraits
Palette viewer and switcher¶
- Opening any
.PALrecord (ICON.PAL, PALETTE.PAL, or a standalone file) shows a 16×16 swatch grid with per-index RGB tooltips and a Use as preview palette button - A PIC's inline palette fragment appears as an Inline palette collapsible in the PIC editor
- The preview palette applies live to PIC previews (Preview panel combo) and CB8 frames (CB8 editor combo); one selection is shared by both
- Auto keeps the default behavior: PIC previews use PALETTE.PAL from any open session; CB8 renders greyscale, because the palette its videos expect is engine-internal and not stored in any LIB (PALETTE.PAL garbles it) — the switcher exists to experiment anyway
- Choosing a palette also drives PIC → PNG and CB8 frame exports; inline PIC palette fragments still overlay whichever base palette is selected
Audio Editing (11K / 5K / 8K)¶
- Waveform display (downsampled to 512 points for performance)
- In-app playback via miniaudio with real-time position tracking
- Play, pause/resume, and stop controls
- Animated playhead showing current position; left-click or drag to seek; right-click to pause at position
- Playhead color indicates state: green = playing, yellow = paused, grey = stopped
- Export raw PCM → WAV
- Import WAV → raw PCM (any sample rate; stored at the file's native rate)
- Sample rate and duration shown in header
Mission Editing (M / MM / MT)¶
- Full-featured text editor for
.M(mission),.MM(mission map), and.MT(briefing) files - All three formats are plain ASCII — edits round-trip losslessly
- Horizontal and vertical scrolling; long lines are fully reachable without wrapping
- Tab key preserved for indentation
- Save commits the text bytes back into the LIB session
Cutscene Editing (SEQ)¶
- Timeline table with fully editable rows: time (
Nabsolute or+Nrelative), command (colored by type; free text — unknown commands stay editable), arguments, and a sync checkbox - Add events: + Add Event appends 100 ticks after the resolved timeline
end; each row's + inserts after that row, inheriting its addressing
mode (a
+1neighbour after a relative row, so+chains keep resolving) - Delete events with the row's x button
- Edited rows are rebuilt tab-separated per the SEQ layout and re-parsed
through
fx::seq_parse, so the table always shows what the codec sees; comment and untouched lines round-trip byte-identically viafx::seq_serialize(CRLF)
Technical Info Editing (INF)¶
- Styled tab: each directive section (see fa/formats/INF.md — plain text dot-commands, not RTF) rendered with its in-game alignment (
.left/.center/.right) and title/body weight - Per-section editing: text (Edit → Apply/Cancel), alignment and title/body style buttons, insert-after and delete, plus + Add Section
- Source tab: the raw dot-command text in a scrollable editor; both tabs edit the same record
- Edited sections are recomposed via
fx::inf_rebuild_section(CRLF, corpus blank-line convention); untouched sections keep their exact source bytes, so saves round-trip byte-identically outside the edit - Save commits the INF bytes back into the LIB session
Screenshot Viewer (RAW)¶
- Displays resolution and file size for
.RAWscreenshot files - Decoded preview shown in the Preview panel via
fx::raw_decode - To export as PNG use
fx raw unpack <file>
HUD & Sky Overlay Preview (HUD / LAY)¶
Both editors carry an in-game-style preview (#283) drawn through
fx_render::fa — the project's documented stand-in for the G_* raster
layer the engine's own HUD and horizon code draw through
(fa/hud.md, fa/renderer.md) — alongside the
structural tables.
HUD editor — Preview
- Symbology positions come from the selected file's gauge parameters
(flight-path marker anchor, heading/speed/altitude tapes, annunciators,
readouts), so editing a
.HUDmoves the elements the way the engine would place them - Flight state (heading/speed/altitude sliders, gear/flap/brake/hook and warning toggles) is simulated; the annunciators show the file's own advisory icon labels
- Text uses an install FNT resolved from the open sessions (the file's
font asset prefixes, then
HUD*/4X6); a built-in 4x6 block font is the fallback
LAY editor — Sky Preview
- Renders one layer's sky per its gradient ramps: the zenith→horizon ramp
above the horizon line, the horizon-downward ramp below, each band
Gouraud-interpolated between adjacent ramp entries — the
GouraudHorizonpalette-index banding (fa/formats/LAY.md § Engine Notes) - The info line reports the documented per-angle band selection
(
SetActiveLayerByAngle) at level flight
Fidelity boundary (same spirit as the renderer's, see
fa/renderer.md §3.1): element geometry inside each HUD
gauge (tick counts, label styling) and the HUD colour are preview
stand-ins — the engine takes them from HUDInit's layout block, which is
not part of the .HUD file; only the per-element positions/sizes are the
file's. The LAY preview's angle-unit scale for band selection is inferred
(~256 units per quadrant fits every install file); the ramp order and
Gouraud banding are documented behaviour.
3D Model Viewer (SH)¶
- Selecting a
.SHrecord shows a shaded 3D orbit view in the Preview panel, plus scale, vertex/face counts, bounding box, and the referenced texture names in the editor. Faces are drawn as solid filled polygons — the way FA renders shapes (span fills only, no edge pass; fa/render-core.md) - Orbit by dragging; zoom with the scroll wheel; the camera auto-frames the model on selection
- The preview renders at the display's physical resolution, so it stays crisp (and the optional wireframe stays hairline) on HiDPI screens
- Wireframe toggle overlays a thin grey topology view — a validation aid, off by default so the preview matches FA's solid-fill rendering
- Destroyed toggle renders the damaged state: the inline damaged sub-model
(
0xACJumpToDamage) when the shape carries one, else the whole-model wreck sibling (<name>_A.SH, resolved from the same LIB per fa/shape-selection.md — the render-time swap the engine performs for destroyed aircraft; a(wreck: …)hint shows which sibling is displayed) - Faces render with their pre-shaded palette colour (
ShFace::color) directly, the way FA does — the model tool bakes the sun/orientation shading into the colour index (e.g. an aircraft walks a grey ramp face by face), so the preview applies no runtime relighting on top (an earlier dynamic light double-shaded those ramp entries to black) - Texture toggle (shown when the model references a PIC that resolves in the
same LIB) overlays the decoded skin on the textured faces. FA skins are
texture atlases whose palette index
0xFFis transparent (fa/formats/PIC.md): those texels show the face's flat colour through, exactly as FA composites them, rather than the atlas's unused black background. A face whose flat colour is index0(black) is a pure decal overlay (gear-pod stripes, panel markings): its transparent texels are see-through to the geometry behind, not filled — otherwise those panels render as solid black shapes. Both the OpenGL and FA-software backends honour this. Texture-swap damage (e.g. buildings) becomes visible here with Destroyed on. In the editor panel, each listed texture that resolves in the mounted workspace is a link to its PIC — open_A10.PICstraight fromA10.SH(object scope) - Frame slider (shown only for animated models, i.e.
frame_count > 1) selects the animation frame (0x40JumpToFrame); it drivesfx::ShState::frameand re-parses on change. See fa/formats/SH.md - LOD slider (shown when the model has distance LODs, i.e.
lod_count > 1) selects the level of detail (0xC8JumpToLOD): 0 = finest … coarsest; it drivesfx::ShState::lod. The Low detail checkbox (shown when the model has a0xA6JumpToDetail switch) renders the low-detail preference blocks (fx::ShState::detail = 0) - Articulation combos (one per moving-part input the shape exposes — Gear,
L Flap, R Flap, Airbrake, Rudder, Hook, … from its x86
_PL*selectors, #295). All merges every state (the codec default, which overlaps e.g. gear-up and gear-down geometry); picking a value emits just that one sub-stream (fx::ShState::articulation). The listed values are the shape's own compare cases (their per-shape meaning is documented in fa/formats/SH.md); the continuous mid-travel animation is not reproduced — a chosen state renders at its authored position - Export OBJ… writes a Wavefront OBJ (merges all state blocks; the selected frame/damage state is a preview-only choice, per the SH round-trip notes)
- Software (FA) switches the preview from OpenGL onto the FA-faithful
software rasteriser (
fx_render::fa, #290): indexed 16.16 spans, painter's order, no depth buffer — the pixel pipeline the game executable used. Colours quantize to the active preview palette (the stand-in for the engine's shade tables); geometry, spans, clipping, and occlusion are the faithful part. Same toggle headless via--render … --software
Terrain Viewer (T2)¶
- Selecting a
.T2record shows a textured 3D terrain — the leaf grid as a heightfield (elevation band → height), drawn through the shared fx_render module like the SH viewer. Drag to orbit, scroll to zoom; a Software (FA) toggle switches tofx_render::fa. - Each leaf is textured with its
texture_varianttile (<theater><N>.PIC); water is drawn as flat sea. The tiles ship in a sibling LIB from the.T2(e.g. terrain textures inFA_1.LIB, the maps inFA_2.LIB), so open both — the viewer resolves tiles across every open session, and the status line shows the tile count. See fa/formats/T2.md for the texturing model. - Fidelity boundary. The terrain-band palette (indices 192–255, which the engine fills from the atmosphere/sky state) is a default earthy ramp here, and the orbit camera is a stand-in for the engine's VIEW-subsystem framing — the faithful camera is reproduced in fxe and consumed here (#387). The mesh, per-leaf tile mapping, and the fx_render draw path are the faithful part.
Pilot Editing (PLT)¶
Identity block fields (all editable):
| Field | Offset | Length |
|---|---|---|
| Pilot name | 0x01 | 63 bytes |
| Callsign | 0x40 | 32 bytes |
| Voice file | 0x61 | 13 bytes |
| Nose art ID | 0x6E | 13 bytes |
| Left decal ID | 0x7B | 13 bytes |
| Right decal ID | 0x88 | 13 bytes |
| Portrait ID | 0x95 | 13 bytes |
| Rank | 0xA2 | 14 bytes |
Stats block (0xB0–0x0D7E) and campaign/inventory data: pending gameplay-derived saves (see fa/formats/P.md Open Questions).
Settings / Preferences¶
All settings persist automatically in fxs.ini in the per-user preferences
directory (~/.local/share/jomkz/fxs/ on Linux,
%APPDATA%\jomkz\fxs\ on Windows) across restarts.
- FA install directory — set via File → Preferences; used by the one-click LIB install
- Theme — Auto (follow the system), Dark, or Light; set via File → Preferences
- Recent files — last 5 opened files; accessible from File → Recent Files; cleared from the same submenu
- Window size and position — restored on next launch; falls back to centered if the saved position is off-screen (position persistence is unavailable on Wayland)
Workspace¶
File → Mount FA Workspace points fxs at an FA install and mounts the whole
directory as one name-keyed namespace, mirroring the engine's own startup
scan (LibStartUp builds a single sorted index over every LIB entry plus every
loose file — memory-resource.md § LIB name resolution).
The root is the install directory set in Preferences (the same one Tools →
Install uses); mounting persists it, and fxs re-mounts it automatically on the
next launch. The status line reports what mounted, e.g.
Mounted 10 LIBs + 44 loose files: 8531 names, 2 collisions.
What is mounted:
- Every
*.LIBin the root (case-insensitive — real installs mixFA_2.LIBandfa_4c.lib) is opened and its directory indexed. - Every other file in the root is indexed as a loose entry.
Name-collision precedence follows the engine: a LIB entry always resolves before a loose file of the same name, and within one kind the later-mounted source wins. The engine's registration order is the operating system's directory-enumeration order; fxs mounts LIBs in case-insensitive filename order so the outcome is deterministic. Every collision is recorded and surfaced (the status line count, and the workspace's collision list) rather than hidden — on a full retail install only a couple of names clash, because each archive owns distinct content (LIB.md inventory).
The workspace is the data layer for the object-category browsers; the raw per-LIB Archives view (the LIB browser above) is unchanged and remains the byte-level access path for validation work. (This "workspace" — a mounted install root — is distinct from a "session," a single LIB opened for editing.)
Asset-graph index¶
Once a workspace mounts, fxs builds an asset-graph index on a background
thread (the status line shows Indexing assets... 6/10 archives and never blocks
the UI). The index does two things.
The cross-reference graph. It parses each reference-bearing record and
resolves its links against the namespace: entity records (.PT/.OT/.NT/.JT/.SEE/
.ECM/.GAS, BRF text) name their shapes/sounds/HUD; a shape (.SH) names its
texture PICs and — via the engine's _A.._D wreck / _S shadow naming
(sh_variant_name) — its damage siblings; missions (.M/.MM) name their terrain
and object types; campaigns (.CAM) name their missions. Only links that resolve
to a real entry become edges, so display names and free text drop out.
Categories. Every entry is placed in at least one of eight buckets from its type, with an explicit Unassigned bucket so nothing is hidden:
| Category | Types |
|---|---|
| Aircraft | PT |
| Vehicles | NT, OT |
| Weapons | JT, SEE, ECM, GAS |
| Missions | M, MM, MT, MC |
| Campaigns | CAM, P |
| Terrain | T2, LAY |
| Audio | 11K, 5K, 8K, 22K, XMI, MUS |
| Art/UI | PIC, PAL, RAW, ICO, SH, HUD, FNT, DLG, MNU, PTS, HGR, SEQ, INF, AI, BI, CB8, VDO, FBC, TXT, WRI, HLP, CNT, INI, BIN, SMS, EXE |
| Unassigned | anything else (loose .DLL, .CFG, .DAT, extension-less files…) |
On top of the type seed, an object's category propagates along the graph into
the art it reaches — so an aircraft's shape, its skin PIC and its wreck siblings
all also carry Aircraft and group with the .PT (e.g. A10.PT → A10.SH →
_A10.PIC + A10_A.SH). Propagation only crosses into Art/UI assets, so one
object type never bleeds into another (a .PT that lists a .JT weapon does not
make the weapon Aircraft). Categories are non-exclusive: a shape shared by an
aircraft and a vehicle carries both. These categories and the graph are what the
category browsers (below) and the object-scoped workspace view render.
Icon navigation & category browsers¶
The left panel is topped by an icon bar (generated icons)
with one button per category plus Archives. Selecting a category shows a
filterable browser of that category's named objects — the entries whose
primary type is that category (Aircraft lists the .PT aircraft, Weapons the
.JT/.SEE/.ECM/.GAS, and so on). An object's cluster art (its shapes and skin
PICs) is reached by opening the object, not by crowding the list, so it appears
under Art/UI and via the object's references rather than in every browser.
Selecting an entry opens its record in the editor and scopes the editor area to
the object's file cluster (below); the current selection is remembered across
category switches.
The object categories (Aircraft, Vehicles, Weapons) browse as an SH
thumbnail grid: each cell renders the object's shape through the FA-faithful
software backend (fx_render::fa — context-free, so thumbnails render on a
worker thread and never block the UI), framed the way the
SH viewer first shows it. The grid populates
progressively — only cells scrolled into view are queued — and an object whose
shape does not resolve (or whose geometry is x86-only) keeps the category
icon. Rendered thumbnails are cached on disk under the per-user preferences
path (thumbs/), keyed by a digest of the shape record, the palette and the
size, so the next launch fills the grid from disk without rendering a single
shape. The other categories stay compact name lists.
The Archives icon keeps today's raw per-LIB browser unchanged — the byte-level tree of open LIB sessions, load-bearing for validation work. Category browsers need a mounted workspace; until one is (or while it indexes) they show a short prompt, and Archives is always available.
Object-scoped workspace¶
Selecting an object in a category browser scopes the editor area to the object's file cluster: everything its references reach in the asset graph, expanded onward only through Art/UI assets — the same crossing rule as category propagation — so an aircraft's cluster holds its entity record, shape, wreck siblings, skin PICs and sounds, while a weapon it references appears as a single unexpanded leaf instead of dragging its own cluster in.
The cluster renders as a compact file strip above the editor (e.g.
A10.PT cluster — 24 files): one chip per file, the object's record first and
the rest grouped by type, with the currently open file highlighted. Clicking a
chip opens that file in its usual editor without dropping the scope, so the
whole cluster is reachable from the object — pick the A10 in Aircraft, edit
the .PT, preview A10.SH, open _A10.PIC from it. Cross-references inside
editors navigate the same way: a texture listed in the SH editor
that resolves in the mounted workspace is a link to its PIC.
The scope follows the workspace pipeline only: opening a record from the raw
Archives browser leaves it, and editors for uncategorized formats behave
exactly as before. Headless review: fxs --render <install-dir> A10.PT --out x.png.
Object-Category Icons¶
The object-centric navigation groups every asset under nine categories — Aircraft, Vehicles, Weapons, Missions, Campaigns, Terrain, Audio, Art/UI, and Archives (the raw per-LIB view). Their icons are generated, committed line-art — no vendored icon font, in keeping with the zero-external-dependency rule.
tools/gen_icons.py is the single source of truth. From one vector description
per icon it emits both:
- theme-aware SVG sources (
gui/assets/icons/*.svg) using the same light/dark CSS recipe as the docs diagrams, for design review; and - a baked, zero-runtime-dependency form fxs consumes
(
gui/src/assets/icons_baked.{h,cpp}): 8-bit coverage at two sizes (24 px and 48 px for hidpi), anti-aliased by a stdlib rasteriser. The glyphs are monochrome, so only coverage is stored; fxs expands it to RGBA and tints it with the active theme foreground at draw time, so one baked artifact serves both light and dark.
Regenerate with python3 tools/gen_icons.py. A currency check
(gen_icons.py --check) runs on the CI docs job and as the gen_icons_currency
ctest (label docs), failing if a committed SVG or baked byte drifts from the
generator — the same guard the status matrix uses.
Building¶
See development.md.
Library API¶
fx_lib is a static C++17 library. Link it from CMake by adding the repo root:
add_subdirectory(fighters-codex) # e.g. the extern/fx_lib submodule in fa-bridge
target_link_libraries(your_target PRIVATE fx::lib)
When embedded this way, only the library target is built — no CLI/GUI/tools/tests and no test-framework fetch. The library is built position-independent, so it can be linked into a shared plugin.
All public headers are under lib/include/fx/. Include them with the fx/ prefix:
#include "fx/ealib.h"
#include "fx/pic.h"
// etc.
ealib.h — Archive container¶
namespace fx {
struct Entry {
char name[13]; // null-terminated 8.3 filename
uint8_t flags; // 0=raw, 1=lzss, 3=pxpk, 4=dcl
uint32_t offset; // absolute byte offset in the .LIB
uint32_t size; // compressed/raw size in the .LIB
};
// Read the directory from a memory-mapped .LIB
std::vector<Entry> ealib_read_dir(const uint8_t* data, size_t size);
// Find an entry by name (ASCII case-insensitive); nullptr if not found
const Entry* ealib_find(const std::vector<Entry>& entries,
const std::string& name);
// Map an entry name to an output filename that is legal and identical on
// every platform: & * ? " < > | / \ : each become '_'
std::string ealib_safe_name(const char* name);
// Extract one entry. With decompress=true, flags=0 is verbatim and flags=4 is
// blast-decompressed; flags 1 (LZSS) / 3 (PXPK) / unknown are unsupported —
// returns empty and sets *unsupported=true (decoders tracked in #54) rather
// than handing back still-compressed bytes. decompress=false returns the stored
// bytes for any flags. Decompressed-size claims above 64 MiB are rejected as
// malformed (empty return) — the size prefix is attacker-controlled.
std::vector<uint8_t> ealib_extract(const uint8_t* data, size_t size,
const Entry& entry, bool decompress = true,
bool* unsupported = nullptr);
// Build a new .LIB from a list of (name, data) pairs (stored uncompressed)
std::vector<uint8_t> ealib_build(
const std::vector<std::pair<std::string, std::vector<uint8_t>>>& files);
// Return a new .LIB with one entry replaced
std::vector<uint8_t> ealib_patch(const uint8_t* data, size_t size,
const std::string& name,
const std::vector<uint8_t>& new_data);
// Rebuild the container from its own directory (raw payloads, verbatim
// entry metadata, recomputed offsets incl. the terminator entry) —
// byte-identical for well-formed archives
std::vector<uint8_t> ealib_repack(const uint8_t* data, size_t size);
} // namespace fx
pal.h — VGA palette¶
namespace fx {
struct Palette {
uint8_t r[256], g[256], b[256]; // already scaled to 8-bit (0–255)
};
Palette pal_load(const uint8_t* data, size_t size); // load a .PAL file
void pal_save(const Palette& pal, uint8_t out[768]);
} // namespace fx
pic.h — PIC image codec¶
namespace fx {
struct PicInfo {
uint16_t format; // 0=dense, 1=sparse, 0xD8FF=JPEG
uint32_t width, height;
uint32_t pixels_offset, pixels_size;
uint32_t palette_offset, palette_size;
uint32_t spans_offset, spans_size;
uint32_t rowheads_offset, rowheads_size;
};
bool pic_info(const uint8_t* data, size_t size, PicInfo* info);
// Decode any PIC sub-format to RGBA8 (width * height * 4 bytes).
// sys_pal may be nullptr for JPEG or when the PIC has a full inline palette.
std::vector<uint8_t> pic_decode(const uint8_t* data, size_t size,
const Palette* sys_pal);
// Encode RGBA8 to a dense PIC (format 0) with a full inline palette.
// Pixels with alpha < 128 become transparent (index 0xFF).
std::vector<uint8_t> pic_encode(const uint8_t* rgba, int w, int h,
const Palette& pal);
// Byte-identical structural repack: re-derive every region from the parsed
// header and re-emit by construction (whole-file passthrough for JPEG).
// Returns empty if any byte is unaccounted for; a non-empty result is
// always byte-identical to the input.
std::vector<uint8_t> pic_repack(const uint8_t* data, size_t size);
} // namespace fx
blast.h — PKWare DCL decompressor¶
// Decompress a raw PKWare DCL stream (litmode=0, dictbits=4–6).
// Returns bytes written, or -1 on error.
int blast_decompress(const uint8_t* in, size_t in_size,
uint8_t* out, size_t out_capacity);
// EA wrapper: strips the 6-byte EA header before decompressing.
int blast_decompress_ea(const uint8_t* in, size_t in_size,
uint8_t* out, size_t out_capacity);
seq.h — Cutscene timeline¶
namespace fx {
struct SeqEvent {
bool relative; // true = time is +N ticks from previous event
int ticks;
std::string command;
std::vector<std::string> args;
};
struct SeqFile {
std::vector<std::string> header_comments;
std::vector<SeqEvent> events;
};
SeqFile seq_parse(const uint8_t* data, size_t size);
std::vector<uint8_t> seq_serialize(const SeqFile&);
} // namespace fx
audio.h — Raw PCM audio¶
namespace fx {
struct AudioInfo {
int sample_rate; // Hz
size_t sample_count;
double duration_sec;
};
AudioInfo audio_info(const uint8_t* data, size_t size, int sample_rate);
std::vector<uint8_t> audio_to_wav(const uint8_t* data, size_t size, int sample_rate);
std::vector<uint8_t> audio_from_wav(const uint8_t* wav, size_t size,
int* sample_rate_out);
} // namespace fx
brf.h / ot.h — Type definitions¶
namespace fx {
// ObjectType covers OT, NT, JT, SEE, ECM, GAS.
// PlaneType extends ObjectType with aerodynamic fields.
// Full field lists are in lib/include/fx/ot.h.
ObjectType ot_parse(const uint8_t* data, size_t size);
PlaneType pt_parse(const uint8_t* data, size_t size);
std::vector<uint8_t> ot_serialize(const ObjectType&);
std::vector<uint8_t> pt_serialize(const PlaneType&);
// nt_parse, jt_parse, see_parse, ecm_parse, gas_parse follow the same pattern
} // namespace fx
mission.h — Mission and map files¶
namespace fx {
struct MissionInfo { bool is_mission; std::string map_file, layer_file;
int layer_index, clouds, wind_dir, wind_speed,
time_h, time_m, obj_count;
std::vector<std::string> screen_flags; };
// One "key value..." line; values kept as raw text tokens.
struct MissionField { std::string key; std::vector<std::string> values; };
struct MissionWaypoint { int index; // w_index
std::vector<MissionField> fields; // other w_* fields
const std::vector<std::string>* get(const std::string&) const; };
struct MissionObj { std::string type_file; // type
int64_t pos[3]; int angle[3]; // pos / angle
std::vector<MissionField> fields; // all other fields
const std::vector<std::string>* get(const std::string&) const; };
struct MissionWaypointBlock { int count; std::vector<MissionWaypoint> waypoints; };
struct MissionObjects { std::vector<MissionObj> objects;
std::vector<MissionWaypointBlock> waypoint_blocks; };
// Summary: map/layer/wind/time/clouds + object count + screen flags.
MissionInfo mission_parse_info(const uint8_t* data, size_t size);
// Verbatim re-emit with CRLF normalization (byte-identical for canonical input).
std::vector<uint8_t> mission_roundtrip(const uint8_t* data, size_t size);
// Full placed-object decode: every `obj … .` block (type/pos/angle typed, other
// fields preserved) plus the `waypoint2` blocks, both in file order. The
// object↔waypoint-block ownership is not encoded in the file (M.md § Waypoint
// block), so blocks are a parallel list.
MissionObjects mission_parse_objects(const uint8_t* data, size_t size);
} // namespace fx
ai.h / bi.h — Compiled AI script codec¶
namespace fx {
struct AiCompileError { int line; std::string message; };
// AI source (text) → BI Phar Lap PE DLL bytes. Empty on failure (see errors).
std::vector<uint8_t> ai_compile(const std::string& source,
std::vector<AiCompileError>& errors);
// BI bytecode → AI source (the inverse of ai_compile). The recovered text
// recompiles byte-identically for any BI this toolchain produced, i.e.
// ai_compile(ai_decompile(bi)) == bi. Reads the fx CALL_BY_NAME dialect only;
// returns "" on failure (no CODE section, or a foreign CALL_DIRECT dialect).
std::string ai_decompile(const uint8_t* data, size_t size);
struct BiInstr { uint32_t offset; std::string text; };
// Disassemble BI bytecode to mnemonics, resolving CALL_DIRECT thunks via
// .idata. Handles both fx-compiled and stock game BIs. Empty on failure.
std::vector<BiInstr> bi_disasm(const uint8_t* data, size_t size);
} // namespace fx
sh.h — 3D shape / model¶
namespace fx {
struct ShVertex { float x, y, z; }; // world coordinates, feet
struct ShTexCoord { float s, t; }; // texel-space (pixels of the referenced PIC)
struct ShFace {
uint8_t color; // palette index for untextured rendering
std::string texture; // filename from last TextureFile instruction
std::vector<uint32_t> indices; // 0-based into ShMesh::vertices
std::vector<ShTexCoord> texcoords; // per-corner (parallel to indices); empty if untextured
};
struct ShInfo {
int scale_raw; // raw scale field (8 = 1 ft/unit)
float scale; // multiplier: raw_coord * scale = feet
int vert_count, face_count;
int frame_count; // animation frames (max JumpToFrame nframes); 0 = static
int lod_count; // selectable LOD levels (1 = no distance LODs)
bool has_detail; // any JumpToDetail preference switch present
bool has_damage; // any inline JumpToDamage (0xAC) branch present
float bbox[6]; // min_x min_y min_z max_x max_y max_z (feet)
std::vector<std::string> textures;
};
struct ShMesh {
float scale;
int frame_count = 0; // animation frames; 0 = static
int lod_count = 1; // selectable LOD levels (1 = no JumpToLOD sites)
bool has_detail = false;
bool has_damage = false;
std::vector<ShVertex> vertices;
std::vector<ShFace> faces;
std::vector<std::string> textures;
};
struct ShState { // selects a conditional-geometry state
bool destroyed = false; // JumpToDamage: emit the wreck sub-model
int frame = 0; // JumpToFrame: animation frame index (mod nframes)
int lod = 0; // JumpToLOD level: 0 = finest .. lod_count-1 = coarsest
int detail = 0xFFFF; // JumpToDetail preference; max = full detail
};
// Engine-generated sibling name: "A10.SH" + 'a' -> "A10_A.SH" ('a'-'d' =
// wreck variants, 's' = shadow; docs/fa/shape-selection.md). Which slots the
// engine fills depends on the type record's obj_class - probe which exist.
std::string sh_variant_name(const std::string& base, char variant);
ShInfo sh_parse_info(const uint8_t* data, size_t size);
ShMesh sh_parse_mesh(const uint8_t* data, size_t size); // intact
ShMesh sh_parse_mesh(const uint8_t* data, size_t size, const ShState& state); // state-aware
std::string sh_to_obj(const ShMesh& mesh); // returns Wavefront OBJ text
} // namespace fx
fbc.h — Video frame index¶
namespace fx {
// Parse the flat u32le frame-size array (*ok=false if size % 4 != 0)
std::vector<uint32_t> fbc_read(const uint8_t* data, size_t size,
bool* ok = nullptr);
// Serialize — byte-identical inverse of fbc_read
std::vector<uint8_t> fbc_write(const std::vector<uint32_t>& frame_sizes);
// Byte offset of frame n inside the paired .VDO (816-byte header + prefix
// sum); n == frame count yields the expected VDO file size
uint64_t fbc_frame_offset(const std::vector<uint32_t>& frame_sizes, size_t n);
} // namespace fx
bin.h — Lookup-table identification¶
namespace fx {
enum class BinKind { Insigmap, Mix2, Mix2L, Mix4, Mix4L, VFontPal, Unknown };
// Classify by entry name (case-insensitive, .BIN optional)
BinKind bin_classify(const std::string& entry_name);
// One-line description / documented table size (0 for Unknown)
const char* bin_kind_desc(BinKind kind);
size_t bin_expected_size(BinKind kind);
} // namespace fx
cam.h — Campaign DLL reader¶
namespace fx {
struct CamInfo {
bool valid; // MZ + "PL" signature with a CODE section
CodeSection code; // section geometry (pe.h)
};
CamInfo cam_info(const uint8_t* data, size_t size);
// Printable-ASCII runs >= min_len — the embedded campaign string tables
std::vector<std::string> cam_strings(const uint8_t* data, size_t size,
size_t min_len = 3);
} // namespace fx
txt.h — In-game text / directive engine¶
namespace fx {
struct TxtLine {
std::string raw; // line bytes without the terminator
bool crlf, terminated;
std::vector<std::string> directives; // ".section", "..button", ...
};
struct TxtDoc { std::vector<TxtLine> lines; };
enum class TxtKind { CampaignDescription, UiTemplate, PlainText };
TxtDoc txt_read(const uint8_t* data, size_t size); // never fails
std::vector<uint8_t> txt_write(const TxtDoc& doc); // byte-identical inverse
TxtKind txt_classify(const TxtDoc& doc);
size_t txt_count(const TxtDoc& doc, const std::string& directive);
} // namespace fx
cfg.h — EA.CFG game configuration¶
namespace fx {
constexpr size_t EA_CFG_SIZE = 347;
constexpr uint32_t EA_CFG_MAGIC = 0x24;
struct EaCfg { /* every documented CONFIG field; three untraced
pass-through fields (#54) — see cfg.h */ };
// Engine-faithful validation: exact size + magic
bool cfg_read(const uint8_t* data, size_t size, EaCfg& out);
std::vector<uint8_t> cfg_write(const EaCfg& cfg); // byte-identical inverse
} // namespace fx
dat.h — CN_INFO network configuration¶
namespace fx {
constexpr size_t DAT_FILE_SIZE = 3552; // checksum + 0xDDC CN_INFO (v3)
struct CnInfo { /* typed documented fields; checksum + unmapped regions
pass through verbatim — see dat.h */ };
bool dat_read(const uint8_t* data, size_t size, CnInfo& out);
std::vector<uint8_t> dat_write(const CnInfo& info); // byte-identical inverse
const char* dat_transport_name(uint32_t transport);
unsigned dat_baud_rate(uint32_t baud_index);
} // namespace fx
effect.h — GRAPHIC effect-spawn data¶
namespace fx {
constexpr size_t EFFECT_RECORD_SIZE = 0x30; // per-type parameter record
constexpr size_t EFFECT_SPAWN_SIZE = 0x11; // MSG 0x8003 network record
enum class EffectClass : uint8_t { None, Crater, Debris, Smoke, Chaff,
Flare, Fire, Explosion, DustPuff, Unknown };
struct EffectParams { /* type, klass, intensity, frame_count, subtype,
ground_burst, debris_count/spread, sound_variants,
sound_ptrs[8], sound_pitch — see effect.h */ };
struct EffectSpawn { /* type, x/y/z (F24.8), owner, flag0/flag1 */ };
// Classification (no game data needed)
EffectClass effect_class_for_type(int type);
const char* effect_class_name(EffectClass klass);
const char* effect_shape_for_type(int type); // "exp.SH", ...
// Read-only interpreters over a raw buffer
bool effect_parse_record(const uint8_t* data, size_t size, int type, EffectParams& out);
std::vector<EffectParams> effect_parse_table(const uint8_t* data, size_t size, int count);
bool effect_parse_spawn(const uint8_t* data, size_t size, EffectSpawn& out);
} // namespace fx
See fa/formats/EFFECT.md and fa/objects.md § GRAPHIC effect spawning.
mnu.h — Menu DLL reader¶
namespace fx {
struct MnuInfo {
bool valid; // MZ + "PL" signature with a CODE section
CodeSection code; // section geometry (pe.h)
};
MnuInfo mnu_info(const uint8_t* data, size_t size);
std::vector<std::string> mnu_strings(const uint8_t* data, size_t size,
size_t min_len = 3);
} // namespace fx
mt.h — Mission briefing text¶
namespace fx {
struct MtInfo {
std::string mission_id, source_name, title, mission_type;
size_t sections;
};
// Parsing rides txt.h (same directive engine); this adds MT semantics
MtInfo mt_info(const TxtDoc& doc);
} // namespace fx
pts.h — Aircraft screen-assets DLL reader¶
namespace fx {
struct PtsInfo {
bool valid; // MZ + "PL" signature with a CODE section
CodeSection code;
std::string icon; // the single ICON*.PIC reference; empty if absent
};
PtsInfo pts_info(const uint8_t* data, size_t size);
} // namespace fx
rgn.h — Installer UI region maps¶
namespace fx {
struct RgnRecord { uint8_t name[4]; uint32_t vertex_count; uint32_t xy[8]; };
struct RgnFile { std::vector<RgnRecord> records; };
bool rgn_read(const uint8_t* data, size_t size, RgnFile& out);
std::vector<uint8_t> rgn_write(const RgnFile& rgn); // byte-identical inverse
std::string rgn_name(const RgnRecord& rec);
} // namespace fx
ssf.h — Installer scripts¶
namespace fx {
struct SsfStatement {
size_t line; // index into the TxtDoc
std::string keyword; // e.g. "INSTALL_FILES"
std::vector<std::string> args; // unquoted argument values
};
struct SsfDoc { TxtDoc text; std::vector<SsfStatement> statements; };
SsfDoc ssf_read(const uint8_t* data, size_t size); // never fails
std::vector<uint8_t> ssf_write(const SsfDoc& doc); // byte-identical inverse
} // namespace fx
esa.h — EA installer archive¶
namespace fx {
struct EsaEntry {
std::string name, label; // label = the token .SSF INSTALL_FILES selects on
uint32_t flags; // 0x211 app-dir / 0x221 sysfile (inferred)
uint32_t usize, mtime;
std::string method; // "PKWA" (raw PKWare DCL) | "NULL" (stored)
uint32_t csize, offset;
};
// Parse the directory; archive_size (0 = size) bounds-checks blob offsets.
std::vector<EsaEntry> esa_read_dir(const uint8_t* data, size_t size,
uint64_t archive_size = 0);
size_t esa_dir_size(const uint8_t* data, size_t size);
const EsaEntry* esa_find(const std::vector<EsaEntry>&, const std::string& name);
std::string esa_safe_name(const std::string& name);
// PKWA is blast-decoded into a usize buffer (NO EA size prefix — the size is
// the directory's); NULL is copied. Unknown method -> {} and *unsupported=true.
std::vector<uint8_t> esa_extract(const uint8_t* data, size_t size,
const EsaEntry& entry, bool decompress = true,
bool* unsupported = nullptr);
std::vector<uint8_t> esa_repack(const uint8_t* data, size_t size); // byte-identical
std::vector<uint8_t> esa_build(const std::vector<EsaInput>& files); // stored-only
} // namespace fx
install.h — Disc install engine¶
Executes the .SSF scripts against the .ESA archive: what SETUP.EXE does,
portably. A disc is a directory. The planner is a pure function of scanned
metadata — no disc, no destination, no I/O — so it can be unit-tested and fuzzed
on synthetic input, and only install_scan/install_execute/install_verify
touch a filesystem. See fa/formats/SSF.md § Engine Notes.
namespace fx {
struct DiscFile { std::string name; uint64_t size; };
struct DiscScript { std::string name; SsfDoc doc; };
struct DiscSource { // one scanned disc root
std::string root;
int disc; // 1, 2, or 0 = unrecognised
std::vector<DiscFile> loose;
std::string esa_name;
std::vector<EsaEntry> esa;
std::vector<DiscScript> scripts;
};
int install_probe_disc(const DiscSource&); // pure: which disc, by content
DiscSource install_scan(const std::string& root); // I/O: the only stage-1 read
bool install_match(const std::string& pattern, const std::string& name); // DOS glob
enum class InstallOrigin { Archive, Loose };
enum class InstallStatus { Copy, KeepExisting, SkipSysfile };
enum class MediaBuild { Unknown, V100F, V102F }; // fingerprinted from the ESA
struct InstallItem { std::string dest; InstallStatus status; InstallOrigin origin;
size_t disc; std::string source, label; uint64_t bytes;
std::string note; };
struct InstallDirective { std::string script; size_t line; std::string keyword;
std::vector<std::string> args; bool honored; std::string note; };
struct InstallOptions { bool full, cd_resident, overwrite, allow_unknown_media; };
struct InstallPlan { MediaBuild build; std::string company, app_name, default_path,
script; std::vector<InstallItem> items;
std::vector<InstallDirective> directives;
std::vector<std::string> errors; uint64_t bytes; };
// PURE. `existing` is the destination's file names (install_list_dir), which
// drives the SKIP_ON_REMOVE clobber guard. A non-empty plan.errors means
// install_execute will refuse.
InstallPlan install_plan(const std::vector<DiscSource>& discs,
const std::vector<std::string>& existing,
const InstallOptions& opt);
const char* install_build_name(MediaBuild);
std::vector<std::string> install_list_dir(const std::string& dir);
// A plain function pointer, not std::function: fa-bridge links fx_lib into a
// shared plugin.
typedef void (*InstallProgress)(const InstallItem&, uint64_t done, uint64_t total,
void* user);
// Streams: peak memory over a 989 MiB install is a few MB. Writes .part, renames.
bool install_execute(const std::vector<DiscSource>&, const InstallPlan&,
const std::string& dest, InstallProgress, void* user,
std::vector<std::string>* errors);
// Re-derives each item from the disc and byte-compares it.
bool install_verify(const std::vector<DiscSource>&, const InstallPlan&,
const std::string& dest, std::vector<std::string>* errors);
} // namespace fx
mc.h / hgr.h — Mission condition & hangar DLL readers¶
namespace fx {
struct McInfo { bool valid; CodeSection code; };
McInfo mc_info(const uint8_t* data, size_t size);
std::vector<std::string> mc_strings(const uint8_t* data, size_t size,
size_t min_len = 3);
struct HgrInfo {
bool valid;
CodeSection code;
std::vector<std::string> pics; // referenced *.PIC assets, in order
};
HgrInfo hgr_info(const uint8_t* data, size_t size);
std::vector<std::string> hgr_strings(const uint8_t* data, size_t size,
size_t min_len = 3);
} // namespace fx
dlg.h — Menu dialog DLL reader¶
namespace fx {
struct DlgInfo {
bool valid; // MZ + "PL" signature with a CODE section
CodeSection code; // control dispatch table (pe.h geometry)
};
DlgInfo dlg_info(const uint8_t* data, size_t size);
std::vector<std::string> dlg_strings(const uint8_t* data, size_t size,
size_t min_len = 3);
} // namespace fx
xmi.h — Extended MIDI (XMI → MID)¶
namespace fx {
struct XmiChunk { std::string tag; uint32_t offset, size; };
struct XmiSequence { std::vector<XmiChunk> chunks; uint16_t timbres; };
struct XmiFile { bool valid; uint16_t seq_count;
std::vector<XmiSequence> sequences; };
// Parse the IFF envelope (FORM/XDIR + INFO + CAT XMID + per-seq FORM XMID)
XmiFile xmi_parse(const uint8_t* data, size_t size);
// Export one sequence to a Standard MIDI File (format 0); empty on error.
// One-way translation: AIL delays -> SMF deltas, note durations -> note-offs
std::vector<uint8_t> xmi_to_smf(const uint8_t* data, size_t size,
size_t seq_index, uint16_t ppqn = 60);
} // namespace fx
mus.h — Music playlist sequencer¶
namespace fx {
// One decoded sequencer instruction; fields carry meaning per `op`:
// FF playlist id -> playlist_id · FA setup -> sub,value · FB play track ->
// mode,track_idx,xmi · FC shuffle · FD jump -> value · FE branch -> value
struct MusOp { uint32_t offset; uint8_t op, sub, mode, track_idx;
uint32_t value; std::string playlist_id, xmi; };
struct MusScript { bool valid; std::vector<MusOp> ops;
bool stopped_early; uint8_t stop_byte; };
// Map an XMI track index to its filename (1 -> VALK01.XMI, else AIRnnn.XMI).
std::string mus_xmi_name(uint8_t index);
// Disassemble the playlist bytecode from a .MUS DLL's CODE section.
// Read-only (the CODE section is Miles-consumed as-is; see #101).
// MusScript{valid=false} when there is no CODE section.
MusScript mus_disassemble(const uint8_t* data, size_t size);
} // namespace fx
fnt.h — Font glyph compiler¶
namespace fx {
struct FntGlyph { uint8_t ch; uint32_t width, height; std::vector<uint8_t> pixels; };
struct FntFile { bool valid; uint32_t font_height, glyph_fn_va[256], glyph_width[256];
std::vector<FntGlyph> glyphs; };
// Parse the FONT struct from the PE DLL's CODE section.
FntFile fnt_parse(const uint8_t* data, size_t size);
// Interpret the x86 glyph functions into bitmaps (all 256 characters). The
// vocabulary — byte/word/dword run writes, row advance, RET — is complete
// across every install font.
void fnt_render_glyphs(FntFile& fnt, const uint8_t* cs_data, size_t cs_size, uint32_t cs_vma);
// Emit one glyph body with the original compiler's canonical encoding
// (greedy 4/2/1 pixel runs; byte-identical over all install bodies).
std::vector<uint8_t> fnt_emit_glyph(const uint8_t* pixels, uint32_t width, uint32_t height);
// Rebuild a FNT DLL around edited glyphs: bodies re-emitted in character
// order, the function-VA table rebuilt, the rest of the container carried
// verbatim. Empty if the code would overrun the original region.
std::vector<uint8_t> fnt_repack(const uint8_t* orig, size_t orig_size,
uint32_t height, const uint32_t widths[256],
const std::vector<FntGlyph>& glyphs);
} // namespace fx
raw.h — Screenshot codec¶
namespace fx {
struct RawInfo { uint32_t width, height; };
// Parse the mhwanh header (width/height u16 big-endian at +8/+10).
bool raw_info(const uint8_t* data, size_t size, RawInfo* info);
// Decode to width*height*4 RGBA through the embedded 8-bit palette.
std::vector<uint8_t> raw_decode(const uint8_t* data, size_t size);
// Encode RGBA to a RAW screenshot: palette rebuilt from distinct colours in
// first-seen order (max 256; alpha ignored). Empty on overflow.
std::vector<uint8_t> raw_encode(const uint8_t* rgba, int w, int h);
// Byte-identical structural repack; a non-empty result always equals the
// input. Trailing undescribed bytes fail.
std::vector<uint8_t> raw_repack(const uint8_t* data, size_t size);
} // namespace fx
cb8.h — FMV video codec¶
namespace fx {
struct Cb8Info {
uint32_t width;
uint32_t height;
uint32_t frame_count;
uint32_t samples_per_frame; // sync counter ticks per frame (400)
uint32_t audio_sync_rate; // sync counter ticks per second (6000 = 400 x 15 fps)
};
// Parse VooM header from a CB8 file in memory.
bool cb8_info(const uint8_t* data, size_t size, Cb8Info* out);
struct Cb8Decoder; // opaque
// Open a decoder. Returns nullptr on bad input. `data` must remain valid
// for the lifetime of the decoder.
Cb8Decoder* cb8_open(const uint8_t* data, size_t size);
void cb8_close(Cb8Decoder* dec);
// Decode frame `frame_idx` to palette index bytes (width x height,
// row-major). Every frame is a self-contained key frame: frames decode in
// any order. Empty on error.
std::vector<uint8_t> cb8_decode_frame(Cb8Decoder* dec, uint32_t frame_idx);
// The frame's embedded 768-byte palette, widened 6->8 bit like pal_load.
bool cb8_frame_palette(Cb8Decoder* dec, uint32_t frame_idx, Palette* out);
// RGBA8 decode through the frame's embedded palette.
std::vector<uint8_t> cb8_decode_frame_rgba(Cb8Decoder* dec, uint32_t frame_idx);
// One re-encodable frame: indices plus the 6-bit palette to embed.
struct Cb8Frame {
std::vector<uint8_t> indices; // width * height
std::array<uint8_t, 768> palette6{}; // 6-bit VGA RGB
};
// Rebuild a CB8 around new video frames. The DRBC header, audio chunks,
// stream order, and VooM timing carry from `orig` verbatim; each MRFI is
// re-encoded (pixel-exact; byte identity is a non-goal). Empty if a frame
// needs more than 256 codebook entries per band after splitting.
std::vector<uint8_t> cb8_repack(const uint8_t* orig, size_t orig_size,
const std::vector<Cb8Frame>& frames);
} // namespace fx
hud.h — HUD overlay codec¶
namespace fx {
struct HudParam { std::string gauge, field; int16_t value; };
struct HudFile { bool valid; std::vector<std::string> asset_strings;
std::string icon_a, icon_b, icon_c, icon_d;
std::vector<HudParam> params; };
// Parse the fixed 0x2BB-byte HUD struct from the PE DLL's CODE section:
// asset strings, four advisory icon labels, and the named gauge parameters.
HudFile hud_parse(const uint8_t* data, size_t size);
// Rebuild a HUD DLL around edited gauge params and icon labels. `params`
// must hold exactly one entry per known gauge field (any order); icons fit
// their 8-byte slots. Asset strings and every unmodelled byte carry over
// verbatim — an unedited parse→repack is byte-identical. Empty on
// unknown/missing params, out-of-range values, or oversized labels.
std::vector<uint8_t> hud_repack(const uint8_t* orig, size_t orig_size,
const HudFile& hud);
} // namespace fx
lay.h — Sky/atmosphere codec¶
namespace fx {
struct LayGrad { uint8_t r, g, b; };
struct LayLayer { uint8_t flags; int32_t sel_alt_min, /* ... */ gradient_val_end;
uint8_t base_rgb[3]; LayGrad zenith_grad[31], horizon_grad[32];
uint8_t horizon_base_rgb[3]; uint32_t fog_density;
std::string cloud_pic, sky_pic; uint8_t visibility; };
struct LayFile { bool valid; uint32_t sky_angle_scale, below_angle_scale;
uint32_t sky_layer_va[10], below_layer_va[10];
uint32_t colour_entry_table_va, palette_buffer_va, layer_array_va;
std::vector<LayLayer> layers; };
// Parse the DLL data header and walk the LAYER array to its end sentinel
// (flags bit 0).
LayFile lay_parse(const uint8_t* data, size_t size);
// Rebuild a LAY DLL around edited header fields and layers. The layer
// count and each layer's sentinel bit must match the original, and the
// structural VAs (layer array, colour table, palette buffer) cannot be
// relocated; the sky/below band tables stay editable. cloud_pic/sky_pic
// fit their 22-byte slots. Unmodelled bytes carry over verbatim — an
// unedited parse→repack is byte-identical. Empty on any mismatch.
std::vector<uint8_t> lay_repack(const uint8_t* orig, size_t orig_size,
const LayFile& lay);
} // namespace fx
t2.h — Terrain map¶
namespace fx {
struct T2Info { uint32_t dim_x, dim_y, tile_count, leaf_step,
leaves_w, leaves_h, leaf_offset, summary_offset;
std::map<uint8_t, uint32_t> surface_dist; };
// One 3-byte terrain record (leaf or tile summary).
struct T2Record { uint8_t surface_class; // 0xFF = water
uint8_t elevation; // elevation band
uint8_t texture_variant; // 0-31 atlas sub-texture
};
struct T2Map { std::string theater, atlas_pic;
uint32_t tiles_w, tiles_h, leaves_w, leaves_h, leaf_step;
std::vector<uint8_t> header; // pre-payload bytes, verbatim
std::vector<T2Record> leaves; // leaves_w*leaves_h row-major
std::vector<T2Record> summaries; // tiles_w*tiles_h row-major
const T2Record& leaf(uint32_t x, uint32_t y) const;
const T2Record& summary(uint32_t col, uint32_t row) const; };
// Parse the header and build the per-tile surface class distribution.
bool t2_info(const uint8_t* data, size_t size, T2Info* info);
// Decode the full terrain map: header strings, the per-leaf records
// (surface class / elevation band / texture variant), and the per-tile
// summary array (the engine's far-LOD fallback). The raw header is kept
// verbatim, so header + records reassemble the file byte-identically.
// Returns false on any structural inconsistency.
bool t2_read(const uint8_t* data, size_t size, T2Map* map);
// Serialize a map back to file bytes: verbatim header + leaf array + summary
// array. The record vectors may be edited first but must still match the
// header's grid; empty on any mismatch. A t2_read -> t2_write round-trip is
// byte-identical.
std::vector<uint8_t> t2_write(const T2Map& map);
// Read then write — a byte-identical round-trip for valid T2 data, empty for
// anything t2_read rejects.
std::vector<uint8_t> t2_repack(const uint8_t* data, size_t size);
} // namespace fx
plt.h — Pilot save¶
namespace fx {
struct PltInfo { uint8_t version_tag;
std::string name, callsign, voice_file, nose_art,
left_decal, right_decal, portrait, rank;
std::string cam_file, cam_name, aircraft; // campaign block
std::vector<std::string> aircraft_pool, sensors;
std::vector<PltOrdnance> ordnance; };
struct PltStats { /* mission counters, 13 kill tallies, 8 weapon-accuracy
groups — see plt.h for the full field list */ };
// A pilot file decoded for round-trip editing: `raw` is every original byte
// verbatim (the pass-through backbone); `info`/`stats` are decoded views over
// the mapped regions (`stats` valid only when `has_stats`).
struct PltFile { std::vector<uint8_t> raw;
PltInfo info; PltStats stats; bool has_stats; };
// Parse the identity block (+ campaign scan). False if size < 0xB0 or the
// version tag != 0x0F.
bool plt_parse(const uint8_t* data, size_t size, PltInfo* info);
// Parse the confirmed stats block. False if size < 0x21F8 (stats not present).
bool plt_parse_stats(const uint8_t* data, size_t size, PltStats* stats);
// Read a pilot file: keep the full bytes in out->raw and decode the identity
// and stats views. Same validity criteria as plt_parse.
bool plt_read(const uint8_t* data, size_t size, PltFile* out);
// Serialize a pilot file: overlay only the fixed-offset mapped fields (the
// identity block, and the stats counters when has_stats) onto a copy of
// f.raw. The four unmapped gap regions and the variable-length
// campaign/ordnance region pass through verbatim, so a plt_read -> plt_write
// round-trip is byte-identical. Empty if f.raw is shorter than 0xB0.
std::vector<uint8_t> plt_write(const PltFile& f);
// Read then write — a byte-identical round-trip for a valid pilot file, empty
// for anything plt_read rejects.
std::vector<uint8_t> plt_repack(const uint8_t* data, size_t size);
} // namespace fx
Development¶
This is the full developer reference — build setup, IDE configuration, project structure, and release workflow. For commit message conventions, see CONTRIBUTING.md; branch naming rules are below.
Primary development happens on Fedora Linux; the Windows bench is kept for the tasks listed in What still needs the Windows bench.
Prerequisites¶
On Linux (GCC or Clang):
sudo dnf install gcc-c++ clang cmake ninja-build python3 git SDL3-devel
Any distribution works with the equivalents: a C++17 compiler, CMake 3.21+,
Ninja, Python 3 (release scripts and the real-asset test harness), and Git.
SDL3-devel serves the fxs build; where no system SDL3 exists, the
build automatically compiles a pinned, checksummed SDL3 from source instead
(see Vendored Dependencies and
ADR-0001).
On Windows (MSVC):
- Visual Studio 2022 or 2026 with the following workloads:
- Desktop development with C++
- C++ CMake tools for Windows (installs cmake.exe into the VS directory)
- Windows 10 or 11 recommended for development (target runtime is Windows 7+)
- Git, and Python 3 for the release scripts and real-asset harness
CMake ships with Visual Studio but is not added to PATH by default. Find
cmake.exe under your VS install (typically
Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\) and add that
directory to your user PATH.
Building¶
All builds go through CMake presets:
| Preset | OS | Compiler | Config | Binary dir |
|---|---|---|---|---|
gcc |
Linux | g++ | Debug | build/gcc/ |
clang |
Linux | clang++ | Debug | build/clang/ |
asan-ubsan |
Linux | clang++ + ASan/UBSan | Debug | build/asan-ubsan/ |
coverage |
Linux | g++ + gcov (--coverage) |
Debug | build/coverage/ |
fuzz |
Linux | clang++ + libFuzzer/ASan/UBSan | Debug | build/fuzz/ |
release |
Linux | default | Release | build/release/ |
msvc |
Windows | MSVC x64 | multi-config | build/ |
Linux day-to-day:
cmake --preset gcc # configure (first time per preset)
cmake --build --preset gcc # build everything
ctest --preset gcc # run the test suite
Swap gcc for clang, asan-ubsan (sanitized build), or release
(optimized). Single targets: cmake --build --preset gcc --target fx.
Binaries land in build/<preset>/cli/fx, build/<preset>/gui/fxs,
build/<preset>/lib/libfx_lib.a, and build/<preset>/tests/fx_tests.
Two options steer the GUI build:
FX_BUILD_GUI(defaultON;OFFin thecoverageandfuzzpresets) — buildfxsand its tests.FX_SDL3_VENDORED(defaultOFF) — skipfind_package(SDL3)and always build the pinned FetchContent SDL3 statically; CI and the release workflow set it so shipped binaries stay self-contained.
Windows:
cmake --preset msvc # configure
cmake --build --preset msvc-debug # Debug build (development)
cmake --build --preset msvc # Release build (CI/release parity)
ctest --preset msvc # run the test suite (Release)
The msvc preset uses the installed Visual Studio's default generator, so
artifacts keep their historical multi-config paths: build\cli\Release\fx.exe,
build\gui\Release\fxs.exe, build\lib\Release\fx_lib.lib (swap Debug
for debug builds).
Plain cmake -B build still works on both OSes for one-off configures, and is
the path embedders use (see api.md).
Platform notes¶
- macOS is unsupported and ships no release artifacts; the presets are
deliberately Linux/Windows-only. CI does compile and run the suite on
macOS as an informational, never-blocking check (a plain
cmake -Bbuild withcontinue-on-error— see the CI table below), so gross portability breaks surface early even though the platform stays unsupported. - The
msvcpreset assumes the platform-default generator is Visual Studio; aCMAKE_GENERATORenvironment override (e.g. to Ninja) conflicts with itsx64architecture setting. fxandfxsread arguments through narrowargv, but both embed an application manifest (cmake/win-utf8.manifest) declaring the UTF-8 active code page, so on Windows 10 1903+ non-ASCII file paths arrive and open correctly. On older Windows the manifest entry is ignored and paths outside the ANSI code page can still fail to open — harmless for FA's own data, which is 8.3 ASCII throughout (#165).fxs.exebuilds as aWIN32-subsystem app (no console window), so PowerShell launches it detached without waiting. For the headless--smokesweep, pipe the output so the shell waits and reports$LASTEXITCODE— see gui.md.
Testing¶
ctest --preset <name> runs several layers:
- Unit suite (
fx_tests): Catch2 codec tests against in-memory data and the committed synthetic fixtures under tests/fixtures/ (loaded throughtests/support/fixture.h). Catch2 is fetched with FetchContent on the first configure of each preset directory, which needs network access; for offline work pointFETCHCONTENT_SOURCE_DIR_CATCH2at an existing Catch2 v3.7.1 checkout. embed_smoke: configures and builds the api.md consumer contract as a child project — repo root viaadd_subdirectory, linkingfx::libinto a shared library, offline. The child inherits the parent's generator, compiler, and config, but not sanitizer flags (deliberate: the test validates the consumer contract, not instrumentation).cli_e2e_lib: round-trips a synthetic archive through the realfxbinary — pack, ls, extract, unpack, patch — byte-comparing every output.- GUI tests (label
gui):gui_testscovers the display-free gui units (string helpers, async-dialog completion queue, preview matrix math, and the audio player state machine on miniaudio's null backend viaFX_AUDIO_NULL=1) on every leg;gui_smokerunsfxs --smoke— three frames rendered headlessly — on Linux (CI wraps it inxvfb-run). - Fuzz smoke runs (
fuzzpreset only, labelfuzz): each libFuzzer harness fuzzes for 60 seconds from its committed seed corpus — see Fuzzing. - Docs checks (label
docs):check_status_selftestandcheck_status_docsruntools/check_status.pyon every preset leg, so a codec change that invalidates a format spec's front-matter claims — or leaves the generated status matrix stale — failsctestlocally, not just the CIdocs-statusjob. The same checker validates the game-executable reconstruction symbol database (db/), per-subsystem coverage, and the generated reconstruction matrix. See spec-authoring.md and db/README.md.
Test data follows the synthetic-first fixture policy
(tests/fixtures/README.md):
everything committed is synthetic — produced by our own encoders or
hand-assembled from the format specs — and validation against real game data
runs only behind FX_FA_ROOT / FX_FA_DISC1+FX_FA_DISC2 (below), under the
ctest label integration, on the local benches. CI never sees a byte of game
content.
Real-asset integration mode (FX_FA_ROOT)¶
With a licensed FA install available, configure with -DFX_FA_ROOT (or set
the FX_FA_ROOT environment variable) to register the fa_extract_manifest
test:
cmake --preset gcc -DFX_FA_ROOT="/path/to/Fighters Anthology"
ctest --preset gcc -R fa_extract_manifest
The test unpacks every .LIB in the install and verifies each extracted
file's SHA-256 against the committed manifest
(tests/integration/fa-extract.sha256).
A manifest generated on one OS and verified on the other proves the extraction
pipeline is byte-identical across platforms. To regenerate after an intended
output change:
python3 tests/integration/fa_manifest.py generate \
--fx build/release/cli/fx --fa-root "$FX_FA_ROOT" \
--out tests/integration/fa-extract.sha256 \
--work-dir build/release/fa-extract-work
Hashes are facts about the game data; the assets themselves must never enter
the repository (*.LIB, *.PIC, *.PAL, … are gitignored — keep it that way).
Real-media install mode (FX_FA_DISC1/FX_FA_DISC2)¶
FX_FA_ROOT proves the pipeline that reads an installed tree. The retail discs
prove the one that writes it — the ESA installer archive and the fx install
engine. Point both variables at the two disc roots (an ISO mount, or a copy of
each) to register the fa_disc_install test:
udisksctl loop-setup -r -f disc1.iso && udisksctl mount -b /dev/loop0 # etc.
cmake --preset gcc \
-DFX_FA_DISC1=/run/media/you/FA_1_00F1 -DFX_FA_DISC2=/run/media/you/FA_1_00F
ctest --preset gcc -R fa_disc_install
Both discs are needed: disc 1 carries the installer archive and no LIBs, disc 2
the reverse. Which is which is decided by content — the volume labels are
identical, and a Linux mount hands them out in either order, so the test asserts
the plan comes out the same whichever way round they are named. It checks the
plan for both scripts, extracts every SETUP.ESA entry against the committed
manifest (tests/integration/fa-esa.sha256),
repacks the 110 MB archive byte-for-byte, and runs a real minimal install (73 MiB)
that it then verifies back against the disc. Regenerate the manifest exactly as
for fa_manifest.py, with fa_disc.py generate --out ….
Three options extend it:
-DFX_FA_DISC_FULL=ONalso executes the full 989 MiB install. The plan for it is checked either way — a plan is pure — so this only adds the copy.-DFX_FA_ROOT=…set alongside enables the cross-build oracle: a fresh install off the 1.00F discs is compared file-by-file against that 1.02F tree, and must differ in exactly the set the patch rewrites (FA.EXE,FA.SMS,FA_1.LIB,FA_2.LIB) and no other. That check is the executable statement of the gap the RTPatch codec closes: the discs ship 1.00F,db/describes 1.02F.-DFX_FA_PATCH=…(the updaterfae102.exe) set alongside closes that gap: the harness installs 1.00F, runsfx install --patch, and checks the four rebuilt game files against the committed 1.02F manifest. Where the cross-build oracle asserts which files differ, this proves the patch produces the 1.02F tree. (FX_FA_PATCHalso registers a standalonefa_patch_applytest — see below.)
The three self-oracles here need no committed hash at all: the four entries that
sit both inside SETUP.ESA and loose on disc 1 (README.TXT, IP.EXE,
IP.CFG, EAHELP.HLP — three of them PKWare-compressed) must extract to the
same bytes as the loose copies. The disc checks itself.
Real-media RTPatch mode (FX_FA_PATCH)¶
FX_FA_PATCH names the 1.02F updater fae102.exe. Given the 1.00F originals —
from Disc 1's SETUP.ESA (FX_FA_DISC1) or an explicit FX_FA_PATCH_SOURCE
directory — the fa_patch_apply test applies the patch with fx patch apply and
checks each rebuilt file's SHA-256 against the committed
fa-patch.sha256:
cmake --preset gcc -DFX_FA_PATCH=/path/to/fae102.exe -DFX_FA_DISC1=/run/media/you/FA_1_00F1
ctest --preset gcc -R fa_patch_apply
FA.SMS, FA_1.LIB, FA_2.LIB and the official FA.EXE all reconstruct
byte-for-byte to 1.02F. Regenerate the manifest with fa_patch.py generate
--out … after an intended output change. Set FX_FA_PATCH alongside the disc
variables (above) to also drive the full install-then---patch pipeline.
The committed FA.EXE hash is the pristine official 1.02F byte; a licensed
install's copy may differ by one byte if it carries a no-CD crack (a JNZ→JZ
flip in the CD check) — a property of that install, not of the patch.
Fuzzing¶
libFuzzer harnesses live in fuzz/ and build only under the fuzz
preset (Clang; the whole tree gets coverage instrumentation plus ASan/UBSan):
cmake --preset fuzz
cmake --build --preset fuzz --target fuzzers
ctest --preset fuzz # 60-second smoke run per harness
For a longer local session, run a harness directly against its seed corpus:
build/fuzz/fuzz/fuzz_ealib -max_total_time=600 \
-dict=fuzz/fuzz_ealib.dict fuzz/corpus/fuzz_ealib
To add a harness (the Phase 4 rollout, epic #51):
create fuzz/<name>.cpp implementing LLVMFuzzerTestOneInput, add
fx_add_fuzzer(<name>) to fuzz/CMakeLists.txt, and commit tiny synthetic
seeds under fuzz/corpus/<name>/ — never game assets, and name them
seed-*.bin (*.LIB and friends are gitignored by design). An optional
fuzz/<name>.dict is picked up automatically. The ctest smoke run and the CI
fuzz job need no further wiring.
Fuzzing runs in two CI tiers (#119):
- Per-PR smoke (
fuzz-smoke, in ci.yml): 60 seconds per harness over its committed seed corpus — cheap enough to run every harness on every PR, so a parser regression on malformed input fails the PR that introduces it. - Weekly deep run
(fuzz-deep.yml,
Tuesdays +
workflow_dispatch): 30 minutes per harness with the same limits and dictionaries, deep enough to reach states the smoke never will.
Finding policy: findings are written as crash-*/oom-*/timeout-*
reproducers (gitignored; in CI they upload as the fuzz-findings /
fuzz-deep-findings artifacts). The deep run also fails red and auto-files —
or extends — an open fuzzing-labeled issue pointing at the run and its
reproducer artifact. Minimize with the harness's -minimize_crash=1, then
fix and add a Catch2 regression test in the same PR as the fix.
Continuous Integration¶
Every PR to main (and every push to it) runs the
CI workflow: a matrix that runs
cmake --preset <p>, cmake --build --preset <p>, ctest --preset <p> per leg.
| Check | Runner | Proves |
|---|---|---|
gcc |
ubuntu-latest | Linux GCC build + full test suite |
clang |
ubuntu-latest | Linux Clang build + full test suite |
asan-ubsan |
ubuntu-latest | Full suite under AddressSanitizer + UBSan — memory errors and UB in the binary parsers fail the PR |
msvc |
windows-latest | Windows MSVC build + full test suite |
macos (informational) |
macos-latest | AppleClang build + suite as an early-warning signal; continue-on-error — never blocks a PR |
fuzz-smoke |
ubuntu-latest | 60-second libFuzzer run per harness over its seed corpus — parser crashes on malformed input fail the PR |
| Fuzz (deep) | ubuntu-latest | Weekly scheduled 30-minute-per-harness run; findings upload as reproducer artifacts and auto-file a fuzzing issue — see Fuzzing |
docs-status |
ubuntu-latest | tools/check_status.py --self-test + --check: format-spec front-matter and template conformance (spec-authoring.md), encoding and link hygiene across all markdown — relative links resolve case-exactly, links in docs/ stay inside the docs tree, repo blob/tree URLs point at real main paths — front-matter claims vs. lib/+cli/+tests/+fuzz/ reality, and currency of the generated status matrix — a stale matrix fails the PR |
coverage |
ubuntu-latest | gcov line coverage over lib/ + cli/, gcovr summary on the run's summary page + HTML artifact; enforces a floor that only ratchets up (raised by epic #50, never lowered) |
| CodeQL | ubuntu-latest | Static analysis (security-extended) over all first-party C++; also runs weekly against refreshed query packs |
| Docs | ubuntu-latest | mkdocs build --strict over the whole docs tree — a broken link, broken anchor, or page missing from the site nav fails the PR; on push to main it also deploys the published site (runs only when docs or site config change) |
Every uses: in the workflows is pinned to a commit SHA (with the version in a
trailing comment); Dependabot
keeps the pins current. Test presets are configured with noTestsAction: error,
so a leg that discovers zero tests fails instead of passing vacuously.
Documentation Site¶
The docs/ tree is published as https://fighterscodex.com/ —
an mkdocs-material site built from the same markdown sources GitHub renders.
The Docs workflow
builds the site with mkdocs build --strict on every PR that touches docs or
site config, and deploys to GitHub Pages on push to main. Strict mode plus
the validation: block in
mkdocs.yml
means a broken link, a broken #anchor, or a page missing from the nav fails
the build — site health is CI-enforced, not aspirational.
The site is served from the custom domain fighterscodex.com. The domain lives
in the repository's Pages configuration, not in the deployed content: because
Pages is deployed from a workflow artifact rather than a branch, GitHub does not
read a CNAME file out of the artifact to set it (that behaviour is branch-deploy
only). docs/CNAME is still committed — mkdocs copies it verbatim into site/,
and it is the conventional backstop GitHub's own docs ask for — but it is not what
makes the domain take effect, and editing it alone will not change the domain.
DNS: the apex resolves via A/AAAA records to GitHub's Pages IPs, www is a CNAME
to jomkz.github.io (registrar DNS forms want the bare hostname — a trailing dot
is zone-file syntax and produces a malformed record), and fighterscodex.org is a
registrar-level redirect to the .com. HTTPS is enforced, on a GitHub-provisioned
Let's Encrypt certificate.
To preview locally (the site toolchain is the repo's only pip dependency, pinned in requirements-docs.txt):
python3 -m venv ~/.venvs/fx-docs
~/.venvs/fx-docs/bin/pip install -r requirements-docs.txt
~/.venvs/fx-docs/bin/mkdocs serve # live preview at http://127.0.0.1:8000/
~/.venvs/fx-docs/bin/mkdocs build --strict # what CI runs
Two conventions keep GitHub and the site rendering identically: heading
anchors use GitHub-style slugs (configured via pymdownx.slugs.slugify), and
links from docs/ to files outside the docs tree (source, workflows, repo
meta) are written as absolute github.com/... URLs — check_status.py
verifies those URLs point at real paths, so they can't silently rot.
The site also exposes a Print / PDF page (mkdocs-print-site-plugin, last
in the plugins: list by requirement): every page concatenated into a single
document with a cover and table of contents — use the browser's print-to-PDF
for an offline copy of the whole knowledge base.
IDE Setup¶
VS Code (Linux and Windows)¶
Recommended extensions are declared in .vscode/extensions.json: C/C++,
CMake Tools, and Hex Editor (useful for inspecting binary game assets). CMake
Tools reads CMakePresets.json natively — pick the preset from the status
bar. IntelliSense works from the compile_commands.json each Linux preset
exports (clangd users: --compile-commands-dir=${workspaceFolder}/build/gcc).
Tasks in .vscode/tasks.json are preset-based and pick the right commands per
OS (gcc preset on Linux, msvc on Windows):
| Task | Shortcut | Action |
|---|---|---|
| Configure | — | cmake --preset gcc / cmake --preset msvc |
| Build all | Ctrl+Shift+B |
cmake --build --preset gcc / --preset msvc-debug |
| Build fx (CLI) | — | Build all, restricted to the fx target |
| Build fx_tests | — | Build the test binary |
| Run tests | — | ctest --preset gcc / ctest --preset msvc |
| Run fxs | — | Launches the GUI (both OSes) |
If cmake is not in PATH on Windows, add it via terminal.integrated.env.windows
in your user settings.json.
Visual Studio¶
Open the generated solution directly (build\fighters-codex.sln after
cmake --preset msvc), or use File → Open → CMake… on the root
CMakeLists.txt — VS configures the project automatically. Set the startup
project to fxs for F5 debugging.
Project Structure¶
fighters-codex/
├── lib/ # fx_lib static library (all codecs, no platform deps)
│ ├── include/fx/ # public headers
│ ├── src/ # codec implementations
│ └── vendor/ # stb (vendored)
├── cli/ # fx CLI frontend
├── gui/ # fxs ImGui frontend (SDL3 + OpenGL 3.3, Linux + Windows)
│ ├── src/
│ │ ├── main.cpp # SDL3 + GL host, event loop, window placement, ImGui init
│ │ ├── app.h / app.cpp # App class, session management, menu bar
│ │ ├── platform/ # window, texture, dialogs, theme, fonts, audio player
│ │ ├── panels/ # lib_browser, editor_host, preview
│ │ └── editors/ # per-format editors (audio, mission, brf, pic, ...)
│ └── vendor/ # Dear ImGui, glad, miniaudio (vendored)
├── tests/ # Catch2 suite, embed smoke, CLI e2e, FA integration
├── tools/ # dll_info and other RE utilities
├── scripts/ # release tooling, Ghidra headless scripts
├── db/ # game-executable reconstruction symbol database (epic #209)
└── docs/ # documentation (the primary output)
Adding a new editor¶
- Add a new
EditorKindenum value ingui/src/app.h - Wire the file extension to the new kind in
App::OpenEntry()(app.cpp) - Create
gui/src/editors/<format>_editor.hand<format>_editor.cpp - Call
Draw<Format>Editor(app)fromDrawEditorHost()ingui/src/panels/editor_host.cpp - Add the
.cpptogui/CMakeLists.txt
Editors never touch SDL or GL directly — file dialogs, GPU textures, and
audio go through gui/src/platform/. Dialogs are asynchronous: the
continuation runs frames later, so capture inputs by value at request time
and re-validate app.editor state on arrival (see
gui/src/platform/dialogs.h).
What still needs the Windows bench¶
- fxs interactive verification on Windows — CI compiles it and runs
the display-free
gui_tests, but Windows runners expose no GL 3.3, so rendering, native dialogs, and theming need eyes on the bench - Release packaging verification for the Windows zips (the Linux tarballs are exercised by the release workflow's Linux leg and its dry-run mode)
re-gameplaywork: anything requiring the running game, batched into bench campaigns (epic #56)- The Windows-side
fa_extract_manifestverify run that closes the cross-platform byte-identity loop for epic #42
Branch Names¶
<type>/<short-kebab-description>
The type prefix matches the Conventional Commit type of the work (see CONTRIBUTING.md):
| Prefix | Use for | Example |
|---|---|---|
feat/ |
New functionality | feat/seq-codec |
fix/ |
Bug fixes | fix/pic-palette-index |
docs/ |
Documentation-only changes | docs/lib-format-spec |
refactor/ |
Restructuring without behavior change | refactor/split-cmd-lib |
build/ |
Build system and tooling | build/cmake-presets |
test/ |
Test-only additions | test/fnt-fixtures |
chore/ |
Maintenance, releases, CI | chore/release-v0.5.0 |
Rules: lowercase kebab-case after the slash, keep it short, one logical change
per branch. Branches merge to main via PR.
Releasing¶
- Optionally draft changelog entries from conventional commits since the last tag:
python3 scripts/draft-changelog.py
Review and edit CHANGELOG.md (the script drafts; you refine), then commit any
changes before releasing. See
CONTRIBUTING.md
for the commit message format that drives this.
- Ensure
CHANGELOG.mdhas the desired content under## [Unreleased]. - When ready to ship, run the release script with the new version — from
main(the script creates thechore/release-vX.Y.Zbranch) or from that release branch if the changelog curation already lives there:
python3 scripts/release.py 0.5.0
This will:
- Switch to chore/release-v0.5.0 when starting from main
- Bump the version in CMakeLists.txt
- Rotate CHANGELOG.md — promotes [Unreleased] to the new version with today's date and updates the comparison links
- Commit both files as chore: release v0.5.0
The script never tags: main is protected, so the release commit lands via
PR squash-merge, and the local commit's SHA never appears on main — a tag
created now would point at an unreachable commit (the v0.4.0 misfire).
- Review the commit (
git show --stat HEAD), push the branch, and open the PR:
git push -u origin chore/release-v0.5.0
gh pr create --fill
- After CI is green, squash-merge the PR, then tag the squash commit:
git switch main && git pull
git log -1 --oneline # must show: chore: release v0.5.0 (#<PR>)
git tag v0.5.0 && git push origin v0.5.0
- After the release workflow publishes, verify all six artifacts (
fx,fxs, andfx-lib— one Windows zip and one Linux tarball each) and bump fa-bridge'sextern/fx_libsubmodule to the new tag when the release changedfx_lib.
Pushing the tag triggers the GitHub Actions release workflow: it builds and
tests on both OSes, packages the Windows zips and Linux tarballs, extracts
the release body with scripts/extract-changelog.py (run it locally with a
version argument; the changelog_extract ctest exercises it on every run),
and publishes the GitHub Release.
To validate packaging without tagging, run the workflow manually (Actions → Release → Run workflow): the dry run builds, tests, and uploads the packages as workflow artifacts but skips the publish job.
Vendored Dependencies¶
Runtime dependencies are checked in — the library, CLI, and GUI build without
a package manager. Two network fetches exist: Catch2 for the test suite (see
Testing), and SDL3 for the GUI only when no system SDL3 is
found — find_package(SDL3) first, then a pinned, SHA-256-checksummed
release tarball built statically (the decision record is
ADR-0001; the pin lives in
gui/CMakeLists.txt).
| Library | Location | License |
|---|---|---|
| Dear ImGui (+ SDL3/OpenGL3 backends) | gui/vendor/imgui/ |
MIT |
| glad (GL 3.3 core loader) | gui/vendor/glad/ |
CC0 / Apache-2.0 (generated) |
| miniaudio | gui/vendor/miniaudio/ |
MIT-0 / Public Domain |
| stb_image | lib/vendor/ |
MIT / Public Domain |
| stb_image_write | lib/vendor/ |
MIT / Public Domain |
| blast (PKWare DCL) | lib/src/blast.cpp |
zlib/libpng (Mark Adler) |
Project
Roadmap¶
Development is tracked through GitHub milestones,
one per phase. Each phase is a set of epic-labeled issues whose sub-issues carry the work.
This document is the map; the tracker is the source of truth for status.
Charter¶
The RE documentation is the primary output — format specs, architecture notes, recovered
symbols. The fx_lib library, fx CLI, and fxs (Fighters Studio, the OpenGL GUI) are
the format validation layer: a working, byte-identical codec is the proof that a format is
truly understood. Its executable counterpart is fxe (the fx-engine) — a clean-room
modern C++ source port generated from the reconstruction: a port that runs the original content is
the proof that the game executable is understood.
(Restated in #33; see
README.)
fxsdirection (Fighters Studio): beyond today's filesystem-style 3-panel LIB/entry chooser,fxsgrows an entity-based editing surface. A game entity — an aircraft, a campaign, a mission, a loadout — is not a single file but an aggregate of many LIB records: e.g. an aircraft ties together its.SHshape(s),.PTflight model,.PICtextures,.JT/.SEE/.ECMweapon and countermeasure records, cockpit art, and audio. The Studio surface presents and edits that whole entity as one first-class object — resolving and cross-linking its constituent files — instead of requiring the user to edit each raw record in isolation. The existing filesystem chooser stays as the low-level view. Tracked as its own scope under the fxs/GUI epics.
The 1.0 definition¶
v1.0.0 ships when, and only when:
- Every format and subsystem spec is complete, or its remaining gaps are explicitly documented as requires-gameplay-evidence or unrecoverable.
- Every codec is round-trip-proven (byte-identical repack) or carries a written rationale for being one-way (e.g. OBJ→SH is intentionally out of scope).
- Every codec has tests, fixtures, and a fuzz target, enforced by the status matrix.
- The documentation is restructured (#38) and published as a docs site.
- Releases are current; the project then enters maintenance mode.
The Phase 2 status matrix (#82) is the audit instrument: the 1.0 audit (#148) is a mechanical check against it.
Phases¶
Phases are gated by ordering constraints, not dates — week numbers drift from reality; gates don't.
P0 ─→ P1 ─→ P3 (gui port)
│ ├──→ P4 (codecs/tests/fuzz) ──┐
└──→ P2 (docs system) ─────────────┼──→ P5 (deep static RE + reconstruction) ──→ P6 (gameplay + multi-game + 1.0)
┘
Reality check (mid-2026): the P5 reconstruction ran *ahead* of the P4 validation layer — the game
executable and its overlay binaries are fully named and documented while the codecs/tests/fuzz work
is barely started. P4 and the P5 forward programs (fxe, fx_render) now **interleave**
release-to-release, so neither the docs nor the validation layer goes stale.
P5d (VDO, #55) may start any time after P1 — it is the schedule's long pole.
The RE workbench migration (#120) may start any time after P0.
ATF/USNF acquisition (#144) is external — start immediately.
| Phase | Milestone | Goal | Exit gate |
|---|---|---|---|
| 0 — Program Reset | Phase 0 | Roadmap live, claims true, release tooling portable, v0.3.0 shipped | Structure instantiated; README contradicts nothing |
| 1 — Cross-Platform Core & CI | Phase 1 | fx_lib + fx + tests green on Linux (GCC/Clang) and Windows (MSVC); ctest, sanitizers, CodeQL, fuzz scaffold in CI | Green matrix incl. tests; fx output on Fedora byte-identical to Windows |
| 2 — Documentation System | Phase 2 | #38 executed: one spec template, all 44 specs restructured, status matrix CI-enforced, docs site published | Site live; matrix drift fails CI |
| 3 — fxs Port | Phase 3 | SDL3 + OpenGL3 + miniaudio backends; ready-now validation features | Parity on Fedora + Windows; in CI + releases |
| 4 — Codec & Test Completeness | Phase 4 | Round-trip upgrades, codecs for the 16 uncovered formats, full test/fixture coverage, fuzzing rollout | Matrix: every format round-trips or carries a rationale, with tests + fixtures + fuzz target |
| 5 — Deep Static RE + Reconstruction | Phase 5 | SH/renderer semantics, format-unknown closure, VDO/Cobra decoded, and the full reconstruction programs (game executable + overlay binaries named & documented); forward programs fxe and fx_render begin here |
Static analysis exhausted; the docs alone implement a bridge or a source port |
| 6 — Gameplay, Multi-Game & 1.0 | Phase 6 | PLT gap campaign on the Windows bench, ATF/USNF verification, 1.0 audit | The 1.0 definition above; v1.0.0 tagged |
Epic index¶
| Phase | Epic | Theme |
|---|---|---|
| 0 | #41 | Program reset — roadmap, truth pass, portable tooling, v0.3.0 |
| 1 | #42 | fx_lib + fx CLI + tests Linux port |
| 1 | #43 | CI matrix, sanitizers, CodeQL, fuzz scaffold |
| 2 | #44 | Format spec restructure (#38) |
| 2 | #45 | Status matrix + published docs site |
| 3 | #46 | fxs cross-platform port (supersedes #39) |
| 3 | #47 | GUI validation features — ready-now set |
| 4 | #48 | Round-trip upgrades for one-way codecs |
| 4 | #49 | Codecs for the 16 uncovered formats |
| 4 | #50 | Test & fixture completeness |
| 4 | #51 | Fuzzing rollout |
| 4 | #279 | fx_lib asset interpreters — SH geometry + effect data (complete — SH delivered; effect-data → #315 after #128) |
| 4 | #281 | fx_render — shared render abstraction (OpenGL + software backends) |
| 5 | #53 | Renderer & effects internals — residual static closure |
| 5 | #54 | Format-unknown closure (static) — residual tail |
| 5 | #55 | VDO/Cobra video — the long pole |
| 5 | #209 | Game-executable reconstruction — every function/variable named & documented (complete) |
| 5 | #247 | Overlay-binary reconstruction — WAIL32 / IP.EXE / comms DLLs (complete) |
| 5 | #272 | MSAPI.dll — matchmaking / internet-play client reconstruction |
| 5† | #280 | fxe — clean-room modern C++ source port of the game executable |
| 6 | #56 | Gameplay-gated RE (Windows bench) |
| 6 | #57 | ATF/USNF verification pass |
| 6 | #58 | v1.0 audit, release, maintenance mode |
† fxe (#280) has its own milestone, sequenced
out of the Phase 5 reconstruction and interleaved with Phase 4; it is a stretch program, not a 1.0 gate.
The SH-semantics epic (#52) and the two reconstruction milestones are complete and folded in here.
Standalone Phase 5 prerequisite: #120 — migrate the RE workbench (Ghidra project + FA corpus) to Fedora.
Phase 5 folds in the two reconstruction programs — a per-subsystem lens (naming + documentation
+ diagrams) over the same code the static-RE epics decode — plus the forward programs (fxe,
fx_render) they enable. Both reconstruction programs are complete.
Program: Game-executable reconstruction¶
Epic #209 is the long-horizon goal of a
complete understanding of the game executable: every function and variable named in the Ghidra
project, and every subsystem documented in docs/fa/ with recovered symbols, struct maps, and an
SVG flow diagram.
Complete (as of v0.5.7): all 20 mapped subsystems are complete — 1,728 in-scope functions
named and every referenced global named or waived, each with a docs/fa/ page and a theme-aware
SVG. See the reconstruction matrix. A from-scratch
reproducibility audit
(rebuild the project from db/, diff against the canonical inventory export) confirms zero name drift
and that db/ fully drives the named project — that audit surfaced the final subsystems the original
map missed (the .SEQ cutscene player #240 and
the in-flight VIEW camera/replay cluster #257).
The program is driven by a machine-readable symbol database under
db/: a manifest of
the subsystems, per-subsystem symbols/*.csv files (the canonical VA→name record,
applied to the Ghidra project by scripts/ghidra/ApplySymbols.java and checked against
the local inventory export — Ghidra output itself is never committed,
#342), and the generated
reconstruction matrix. check_status.py enforces, per completed
subsystem, that every code-referenced function is named and every referenced global is
named or waived (the referenced-globals rule), plus the subsystem doc's structure and
theme-aware diagram. The definition of done is exemplified by
objects.md + shape-selection.md. The same database is the
substrate for the fxe source port (below).
Program: Overlay Reconstruction¶
Epic #247 is the sibling program: the same
treatment — every function/variable named, every subsystem documented — applied to the binaries the
game ships alongside the executable. Unlike the main executable these ship no .SMS symbol map, so
naming seeds from the DLL export/import tables, strings, and RTTI. The same
db/ machinery drives it — the
subsystems.csv binary column and a per-binary db/inventory/<binary>/ export scope every check
to one image (VAs collide across binaries — IP.EXE bases at the same 0x00400000 as the main
executable; the comms DLLs all at 0x10000000), and each binary gets its own section in the
reconstruction matrix. Complete across 7 binaries. Key findings from the
RE (correcting earlier assumptions):
- WAIL32.DLL = the Miles Sound System (AIL) audio middleware — boundary-documented (exported API named, third-party internals waived).
- IP.EXE = an EA system-info / tech-support tool (MFC), not a TCP/IP transport as first assumed — no Winsock.
- The comms/modem drivers (
CDRV*32.DLL,COMMSC32.DLL) = third-partyCdrvserial/modem middleware — boundary-documented. - The real internet-play / matchmaking client is MSAPI.dll (the genuinely game-relevant network
binary the epic first mistook
IP.EXEfor) — reconstruction tracked as epic #272.
The interface to the Microsoft / third-party redistributables is documented at the boundary (external-imports.md, #260) without reversing their code.
Program: fxe — the fx-engine (clean-room source port of the game executable)¶
Where fx_lib / fx / fxs prove the format documentation by processing assets, fxe (the
fx-engine) proves the reconstruction documentation of the game executable by being a runnable,
clean-room, modern C++ source port (epic #280,
its own milestone). Give it the content from the
user's original disks and it plays the game the way FA.EXE did — rendering through the shared
fx_render module with software and OpenGL backends (Vulkan later) and modern audio. It is generated from db/ + the subsystem docs by an
in-repo generator that is the source of truth; the emitted C++ is committed and kept in sync by CI
(the same pattern as the generated matrices), clean-room from our own facts and prose and never
transcribed from decompiler output. Legal model: a source port — ship no assets, require the
original disks (documented in an ADR + NOTICE). fxe is a validation lens, interleaved with the
Phase 4 train, not a 1.0 gate, and independent of fa-bridge.
Program: fx_render — one renderer for three consumers¶
fx_render (epic #281) is a committed MIT
render-abstraction module — a backend-agnostic geometry→pixels API with OpenGL and
FA-faithful software backends — extracted from fxs's renderer so the OpenGL/software path is
built once and shared by fxs and fxe (and available for the fighters-legacy engine to
adopt, rather than a third implementation). The SH/effect asset interpretation it renders comes
from fx_lib (epic #279); the full runtime lives
in fxe.
Relationship to fighters-legacy¶
The RE understanding produced here has two independent consumers, and this repo produces the shared pieces both build on:
- codex (MIT) produces the RE docs (primary), the format validation layer (
fx_lib/fx/fxs), the sharedfx_rendercore (OpenGL + software backends), andfxe— a committed clean-room source port of the game executable (generated fromdb/+ docs, requires the user's original content). - fa-bridge (GPL) implements the engine's
IContentPackusingfx_libas a submodule and the RE docs; it runs FA content on the fighters-legacy (GPL) engine, which may itself adoptfx_renderrather than write a third OpenGL/software path.
fxe and fa-bridge are independent consumers of the same reconstruction — one a standalone MIT
source port, one a GPL bridge — and share no code. The reconstruction being complete unblocks
fa-bridge's former interpreter/rasterizer work (the docs it was waiting on now exist). After each
release, fa-bridge's extern/fx_lib submodule is bumped to the tag (the release script prints the
reminder).
License boundary: fighters-codex is MIT; OpenFA and fa-bridge are GPL. RE facts are documented
here with attribution where verified against other projects' findings; code is never transcribed
across the boundary. MIT→GPL reuse (fa-bridge/fighters-legacy consuming fx_lib/fx_render) is
one-way and clean.
Releases¶
Minimum one release per phase gate: v0.3.0 (P0) · v0.4.0 (P1) · v0.5.0 (P2+P3) · v0.6.0 (P4) · v0.7–v0.9 (P5, as RE lands) · v1.0.0 (P6).
The v0.5.x train (P5 reconstruction) shipped ahead of P4; from here the P4 validation work and the
P5 forward programs (fxe, fx_render, residual RE) interleave — alternating release-to-release
so neither the docs nor the codec/test layer goes stale. fxe is a stretch program on its own
milestone and is not a 1.0 gate.
How this roadmap is maintained¶
- Phases are milestones; epics are
epic-labeled issues; work items are native sub-issues. - New work lands as a sub-issue of the epic it serves, in that epic's milestone.
- Gaps discovered during RE are tagged
re-static(Ghidra can answer it) orre-gameplay(needs the Windows bench + running game); Phase 6's bench campaign batches the latter. - The status matrix (#82) is updated in the same PR as the change it reflects (see the docs-currency rule in CLAUDE.md).
- No standalone TODO files — if it's worth doing, it's an issue.
Format Spec Authoring Guide¶
How to write and restructure the format specs in docs/fa/formats/.
Every spec follows one template — general reference for modders at the top, deep
technical spec below — and carries machine-readable YAML front-matter that feeds the
generated status matrix. tools/check_status.py enforces
everything on this page in CI (--check); the canonical vocabularies (section names,
enums) are constants in that script, and this page documents them.
python3 tools/check_status.py --self-test # checker's own test suite
python3 tools/check_status.py --check # what CI runs
python3 tools/check_status.py --write-matrix # regenerate STATUS.md after edits
The template¶
---
format: XXX # uppercase token == filename stem
name: Human Name # short; the H1 must match
extensions: [".XXX"]
category: graphics # see category table below
endianness: little # little | big | mixed | none (plain-text formats)
spec:
status: partial # complete | partial | stub
gaps: # required unless status: complete
- kind: re-static # re-static | re-gameplay | unrecoverable
issue: 54 # tracking issue (omit only for unrecoverable)
note: "short label shown in the matrix"
codec:
direction: round-trip # none | read | write | round-trip
byte_identical: true # round-trip only; true requires a proving test
lib: [lib/src/xxx.cpp] # every path is existence-checked by CI
commands: [xxx] # fx subcommand tokens (dispatch literals)
tests: [tests/test_xxx.cpp]
fuzz: []
fixtures:
synthetic: true # tests build their own inputs
real_manifest: false # extension appears in fa-extract.sha256
related: [LIB] # tokens; each must be linked in the body
---
# XXX — Human Name (.XXX)
Intro prose (required, no heading): what the format is, which .LIBs or install
paths contain it, container notes. This is the modder-facing overview.
## Tools
### fx
fx command examples. Required section whenever codec.commands is non-empty.
### Other Tools
External tools, free ones plain, paid ones marked `$`.
## File Layout
All multi-byte integers are little-endian unless noted. <- first line, binary formats
| Offset | Size | Type | Description |
|--------|------|------|-------------|
| `0x00` | 4 | u32 | ... |
Free H3 structure below (### Header, ### Records, ### Calibration, ...).
Text/DSL formats describe grammar and record structure here instead.
## File Inventory
Known instances in the game, counts, per-file tables. Optional.
## Engine Notes
Confirmed engine functions (VAs + FA.SMS symbols), runtime behavior. Optional.
## Round-Trip Notes
What re-encoding canonicalises, one-way rationale prose, parity caveats. Optional.
## Open Questions
Required iff spec.gaps is non-empty; forbidden when status: complete.
### 1. Short gap title
Evidence so far, what was tried, what would close it.
*Status: open — re-static (#54)*
## Related
**Formats:** [LIB](LIB.md) — ...
**Engine:** [architecture.md](../architecture.md) — ...
Only the intro prose, ## File Layout, and ## Related are unconditional.
A trivially small format needs nothing else — no empty boilerplate sections
(see FBC.md). H2s must come from the canonical set above,
appear at most once, and keep that relative order. LIB.md
is the full-complexity exemplar.
Vocabularies¶
Categories (front-matter category → section of
formats/README.md the spec must be indexed under):
| Token | README section | Token | README section | |
|---|---|---|---|---|
archive |
Archive | mission |
Mission & Campaign | |
graphics |
Graphics & Images | typedef |
Type Definitions (BRF DSL) | |
terrain |
Terrain & Maps | ui-overlay |
UI & Win32 Overlays | |
3d |
3D & Scene | system |
System & Config | |
audio |
Audio | installer |
Installer | |
video |
Video & Cutscenes | text |
Text |
Confidence markers (three levels, used everywhere in docs/fa/):
- confirmed — decompile evidence with the VA cited, or proven by a byte-identical round-trip
- inferred — consistent with all observed data, but not traced in the game executable
- unknown — unmapped
Region headings take a suffix (### Identity block (0x00–0xAF) — confirmed);
unknown fields get Type ? and a Description starting **Unknown**, optionally
followed by the hypothesis. Do not use the retired spellings (Status: unmapped,
unresolved, TBD, requires trace, High/Low).
Gap taxonomy (spec.gaps[].kind, from the roadmap):
re-static— Ghidra static analysis can answer it; issue under epic #54re-gameplay— needs the running game on the Windows bench; issue under #56unrecoverable— provably lost (document why in the Open Questions entry)
Every ## Open Questions entry is a numbered H3 ending with a status line that
names the gap's issue: *Status: open — re-static (#123)*.
Codec claims (codec.*): direction is the fx_lib state — none (no codec;
give issue pointing at the Phase-4 coverage work, or a rationale if none will
ever exist), read/write (one-way; rationale if by design, else issue for
the upgrade), round-trip (+ byte_identical). Once a codec is byte-identical
round-trip, rationale/issue are forbidden — there is nothing left to track.
All lib/tests/fuzz/gui paths and commands tokens are verified against
the repository; once all specs are converted, the reverse also holds (every
codec, test, fuzz harness, CLI command, and GUI editor must be claimed by a spec).
Offset tables — one style, markdown tables with backticked hex offsets:
| Offset | Size | Type | Description |. Record-relative offsets use +0x0E.
Types: u8 u16 u32 s8 s16 s32 f32 char[N] ?. State endianness once, in the
File Layout preamble; per-field suffixes (u16 BE) only for deviations.
BRF-DSL annotated listings of file content stay as fenced code blocks — they
are examples, not layout tables.
Explanatory diagrams (format specs)¶
Format specs may embed explanatory SVGs — byte layouts, mode trees, pipelines
(epic #341, issue
#345). They are editorial:
unlike the subsystem flow diagram below, nothing enforces their presence. When you
add one, it lives in docs/fa/formats/diagrams/, uses the same theme-aware CSS
recipe as the flow diagrams, and follows the shared byte-layout grammar so
diagrams read uniformly across specs (exemplar:
diagrams/lib-layout.svg):
- a vertical stack of field boxes, top = offset 0; a monospace offset gutter on
the left (
0x00; bracketed[offset]for positions known only at runtime); - each box: field name on the left, type · size on the right; variable-length regions are taller and state their size formula; unknown/reserved fields get a dashed outline;
- record expansions (one directory entry, one span) pop out beside the stack, connected by an arrow;
- color semantics match the flow-diagram palette: neutral = fixed header fields, amber = directory/index/selector structures, blue = payload data, green = engine-side consumption notes.
Subsystem docs (reconstruction program)¶
The FA.EXE reconstruction program (epic #209) documents
whole engine subsystems rather than file formats. These docs live directly in
docs/fa/ (not formats/), carry no YAML front-matter — their metadata is the
row in db/subsystems.csv —
and share the confidence markers and offset-table style above. check_status.py
enforces the structure once a subsystem's manifest status is active or
complete. Each doc has:
- a one-paragraph intro and a
> **Provenance:**blockquote (source dump, a link to the symbol database, and the confidence-marker reference — see objects.md); - a
## Functionstable (| VA | Symbol | Role |) whose VAs and names are checked against the subsystem'sdb/symbols/<slug>.csv(docs curate a subset; the DB is the full record, so the table cannot drift); - at least one theme-aware
flow diagram — copy the@media (prefers-color-scheme: dark)+:root[data-theme="dark"|"light"]+fill="context-stroke"recipe fromdiagrams/shape-selection.svg; ## Open Questions(numbered H3s ending in a*Status: …*line) and## Related.
Naming recovered symbols: names not in FA.SMS follow the subsystem's existing
prefix and casing (OBJ…, T_…, Setup…) so decompiled code reads uniformly;
provenance lives in the DB source column (sms/re), not the name. Definition
of done (enforced at status=complete): every code-referenced function in the
subsystem's VA ranges is named, and every referenced global is named or carries a
waiver row explaining why — see db/README.md.
Migration map (restructuring a legacy spec)¶
| Old section | Goes to |
|---|---|
| Overview / Location / Found in | intro prose |
| Format / Structure / File Layout / Header | ## File Layout |
| Calibration (BRF family) | ### Calibration under File Layout |
| Applications / fx commands | ## Tools |
| Confirmed Engine Functions / Playback Architecture | ## Engine Notes |
| Pending Trace / gap-status blocks | ## Open Questions + spec.gaps |
| Toolkit Roadmap | deleted — file the items as issues under the epic they serve |
| Related / Related Formats | ## Related |
Restructuring is content-preserving: no sentence is deleted unless it is
duplicated or migrated to a quoted issue. Review such diffs with
git diff --color-moved=dimmed-zebra. All 44 specs conform as of epic #44;
every spec (and any new one) must carry valid front-matter, and
--write-matrix must be re-run whenever front-matter changes — CI fails
otherwise, in both directions.
Worked front-matter for the odd cases¶
Every irregular shape in the corpus, so no conversion has to invent policy:
- Multiple extensions, one format — 11K:
extensions: [".11K", ".5K", ".8K"],commands: [audio]. - Multiple variant files, one format — CFG (
variants: ["EA.CFG", "IP.CFG"]) and DAT (variants: ["NET.DAT", "MODEM.DAT", "SERIAL.DAT"]); a variant's layout is an H3 under File Layout, never a second H1. - Sub-format variants — PIC:
variants: ["dense", "sparse", "jpeg"]. - Two specs, one codec — M and MM both claim
lib: [lib/src/mission.cpp]andcommands: [mission]; MM additionally claims its aliasmm. Shared paths are expected; claims are a many-to-many mapping. - Family umbrella — BRF:
extensions: []plusfamily: BRF; the seven member specs (OT NT PT JT SEE ECM GAS) each setfamily: BRF, claimlib: [lib/src/brf.cpp, lib/src/ot.cpp], their own dispatch token (commands: [pt]), andtests: [tests/test_brf.cpp]. - CLI-only handler — MUS:
direction: read,lib: [],commands: [mus](the parser lives incli/cmd_mus.cpp; lifting it into fx_lib is #101). - GUI-only viewer, no codec — FBC:
direction: none,issue: 107,gui: [gui/src/editors/vdo_editor.cpp]. - One-way by design — SH:
direction: read,rationale: "OBJ export is intentionally one-way (#48: no OBJ→SH encoder planned)". - Compiler pair —
directiondescribes fx_lib's ability on this format: AI isread(fx ai compilefully parses.AI; nothing writes it until the #102 decompiler), while BI isround-tripwithbyte_identical: false(fx ai compilewrites it,fx bi dumpreads it, dump→recompile is not byte-exact — #102). - Big-endian — XMI:
endianness: big(IFF-style chunk sizes); the File Layout preamble states it. - Project artifact, not game data — SMS (the recovered symbol map):
documented like any other format;
category: system. - No codec yet — the uncovered formats reference their Phase-4 issue: TXT/CFG/MNU/DAT → #104, DLG → #105, XMI → #106, FBC/BIN/CAM → #107, MT/PTS/RGN → #108, SSF/MC/HGR → #109, VDO → #55.
Front-matter YAML subset¶
The checker parses front-matter with a deliberately small stdlib-only parser.
Stay within: 2-space indentation, at most two nesting levels, scalars
(bare token, "quoted string", integer, true/false), inline lists of
scalars ([a, b]), and block lists of flat maps (the gaps shape). No
anchors, flow maps, multiline scalars, tabs, or inline comments. Full-line
# comments are fine. GitHub renders the block as a table at the top of the
spec; the docs site consumes it as
page metadata.
Decisions
Architecture Decision Records¶
Engineering decisions about the toolkit (fx_lib, fx, fx-gui) that are not
self-evident from the code get a short, numbered decision record here. RE facts about
the game belong in the FA knowledge base, not here; codec-level
one-way decisions stay in spec front-matter (codec.rationale, see
spec-authoring.md).
When to write one¶
Write an ADR when a decision (a) affects more than one component or an external contract (dependencies, platforms, artifact shape), (b) overturns or amends a previously documented policy, or (c) chooses between alternatives that a future reader would reasonably re-litigate without the context.
Conventions¶
- Files are numbered
NNNN-kebab-title.mdin decision order and listed in the index below and in the site nav (mkdocs.yml). - Status is one of
Proposed,Accepted, orSuperseded by ADR-NNNN. Accepted ADRs are immutable except for status changes and link fixes — a changed decision gets a new ADR that supersedes the old one. - Each record uses the template: Status / Context / Options considered / Decision / Consequences, and names the issues it serves.
Index¶
| ADR | Title | Status |
|---|---|---|
| 0001 | fx-gui cross-platform backend — SDL3 + OpenGL 3.3 + miniaudio | Accepted |
| 0002 | fxe — committed, generated, clean-room source port of the game executable | Accepted |
ADR-0001: fx-gui cross-platform backend — SDL3 + OpenGL 3.3 + miniaudio¶
Status: Accepted (2026-07-02) Serves: epic #46 (#85 is this record); informs fighters-legacy#156
Context¶
fx-gui is the interactive validation surface for the format research, but it is
Windows-only: a Win32 message pump and DX11 swapchain host
(gui/src/main.cpp),
waveOut audio preview, Win32 common dialogs, and registry-based dark/light theming.
Primary development moved to Fedora (see development.md), so the
GUI cannot be exercised against the local FA install where the rest of the toolkit
lives. Phase 3 of the roadmap requires parity on Fedora and Windows,
shipping in CI and releases.
Constraints that shaped the decision:
- Dependency ethos. The toolkit builds without a package manager; runtime dependencies are vendored (Dear ImGui, stb) and the only network fetch is Catch2 for tests. A windowing library is the first dependency too large to vendor as source.
- One small 3D preview. The only bespoke GPU work is the SH mesh viewer — an offscreen render of a few hundred triangles. Whatever the backend, Dear ImGui does the rest.
- Lesson transfer. The fighters-legacy engine's platform layer is SDL3-based with
pure-virtual HAL interfaces; its upcoming
IGuiHAL (fighters-legacy#156) will host Dear ImGui behindimgui_impl_sdl3. The closer this port's seams are to that design, the more the porting experience feeds it.
Options considered¶
Windowing + rendering¶
| Option | Assessment |
|---|---|
| SDL3 + OpenGL 3.3 core (chosen) | Both ImGui backends (imgui_impl_sdl3, imgui_impl_opengl3) are mature, maintained, and version-locked to the vendored ImGui. GL 3.3 core is everywhere fx-gui needs to run, including Mesa llvmpipe for headless CI. SDL3 additionally covers file dialogs, system-theme queries and change events, display scale, and clipboard — eliminating three would-be dependencies. Matches fighters-legacy's platform layer. |
| SDL3 + SDL_GPU | Wrong altitude for this tool: adds a shader-toolchain step (SDL_shadercross/offline compile) and a younger ImGui backend for zero visual benefit on a 200-triangle preview. Revisit if fx-gui ever needs real rendering throughput. |
| SDL3 + Vulkan | Maximum boilerplate for the same preview; complicates headless CI (llvmpipe GL is simpler than lavapipe swapchains); imgui_impl_vulkan integration effort belongs in fighters-legacy where Vulkan is the actual renderer. |
| GLFW + OpenGL 3.3 | Lighter than SDL3 but covers only windowing/input — dialogs, theme detection, and DPI-change events would each need another dependency or per-OS code, and nothing transfers to fighters-legacy. |
Audio preview¶
| Option | Assessment |
|---|---|
| miniaudio (chosen) | Single vendored header (public domain / MIT-0), fits the stb-style convention. Loads platform audio backends (WASAPI, ALSA/PulseAudio/PipeWire) at runtime via dlopen, so it adds zero link-time dependencies and keeps artifacts self-contained. Keeps audio decoupled from windowing — mirroring fighters-legacy's separate IAudio HAL. Its null backend enables hardware-free unit tests of the player state machine. |
| SDL3 audio | No extra dependency, but couples audio preview to the windowing library and diverges from fighters-legacy's audio split. |
| OpenAL Soft | A 3D-audio API for one-shot mono PCM preview is overkill, and it is an external library to acquire on both OSes. |
SDL3 acquisition¶
| Option | Assessment |
|---|---|
| System-first + pinned fallback (chosen) | find_package(SDL3 CONFIG) first — Fedora ships SDL3-devel, so local builds use the system package with zero configure-time network. Where absent, FetchContent builds a pinned release tarball (URL + SHA-256), static. FX_SDL3_VENDORED=ON forces the pinned path; CI and release jobs set it so shipped artifacts never silently pick up a system SDL3. Both acquisition paths get exercised for free (Fedora developers vs. CI runners). |
| FetchContent always | Reproducible everywhere but recompiles SDL3 on every local first configure and ignores the system package the primary platform already provides. |
| Vendor SDL3 source / require system everywhere | SDL3 is far too large to vendor in-tree; system-only is impossible on GitHub's Ubuntu runners (no libsdl3-dev ≤ 24.04) and would make Linux artifacts depend on libSDL3.so. |
Decision¶
fx-gui runs on SDL3 (window, GL context, input, file dialogs, theme, DPI) with
OpenGL 3.3 core rendering through the stock ImGui backends, a vendored glad
GL loader for first-party GL code, and miniaudio for audio preview. The SH
preview is reimplemented as a GL FBO pipeline with GLSL ports of the HLSL shaders.
Initial pin: SDL 3.4.12 (hash recorded alongside the FetchContent declaration in
gui/CMakeLists.txt).
The DX11/Win32 path is removed, not maintained in parallel — Windows uses the same
SDL3/GL3 backend.
Consequences¶
- Dependency policy amendment. The "only network fetch is Catch2" rule becomes "Catch2, plus SDL3 when no system package exists" — system-first, pinned, and checksummed. miniaudio and glad are vendored under the existing policy. development.md documents the amended rule; this ADR is the authority for why.
- CI. Ubuntu runners install X11/Wayland dev headers so FetchContent can compile SDL3; the GUI enters CodeQL scope on Linux; a headless smoke test runs under Xvfb/llvmpipe. GitHub's Windows runners expose no GL 3.3, so Windows CI compile-checks the GUI and runs its display-free unit tests only.
- Artifacts stay self-contained. Static SDL3 in release builds (SDL resolves
platform libraries at runtime via
dlopen), so the Linux tarball and Windows zip remain single executables. - Wayland caveat. Window position save/restore is a no-op under Wayland (size and maximized state still restore). Accepted; documented in gui.md.
- Behavior changes. Settings move from a CWD-relative
fx-gui.inito the per-user SDL preferences path; theming followsSDL_GetSystemThemeinstead of the Windows registry; DPI scaling becomes dynamic.
Lessons for fighters-legacy#156¶
Seeded here; finalized with findings from the port itself.
- Frame lifecycle.
IGui::beginFramemust run after the platform event pump;endFrameshould emit into a render target it is handed (the frame's command buffer under Vulkan) — presentation belongs toIRenderer, neverIGui. - Texture handles. Editors that only ever touched an opaque
ImTextureIDported for free; the one leak of a backend type (AppholdingID3D11Device*) is exactly what had to be rewritten.IGui::drawImageshould take an opaque 64-bitTextureHandleminted byIRenderer. - Input arbitration.
wantsKeyboardInput()/wantsMouseInput()map toio.WantCaptureKeyboard/Mouseand are valid only afterbeginFrame— forward events to the GUI unconditionally and have the sim consult the flags per frame. - DPI. Style rebuild must be idempotent (ImGui's
ScaleAllSizesis cumulative);IGuiwants adpiChanged(scale)notification fromIWindow. - Dialogs are async. SDL3's file dialogs are callback-based and may complete on any thread — a blocking dialog API in a HAL would be unimplementable on the SDL3 backend. The workable shape: continuation queue pumped on the main thread, capture-by-value, revalidate state on arrival.
- Audio. A control-plane (main thread) / data-plane (realtime callback) split
with atomics-only crossings ports cleanly between audio APIs; track playback
position from the submission cursor, not device queries. Found empirically
during the port: device start-up prefill can consume a sub-period clip
synchronously, so
isPlaying()may legitimately be false immediately after a successful play — consumers must treat play-then-finished as a normal sequence, not an error. - Theme is a platform concern (query + change event);
IGuireceives it rather than detecting it.
ADR-0002: fxe — a committed, generated, clean-room source port of the game executable¶
Status: Accepted (2026-07-04) Serves: epic #280 (fxe), epic #281 (fx_render); charters the executable-level validation program alongside the format-validation layer.
Context¶
The two reconstruction programs are complete: the game executable (all 20 subsystems named and
documented, #209) and its overlay binaries
(#247). The
db/ symbol
database — struct maps with
offsets, function signatures, dispatch tables, a globals registry — plus the prose subsystem docs now
describe the executable's behaviour completely enough to build against.
The format side of the toolkit has a validation principle: a byte-identical codec is the proof a format is understood. The executable side had no equivalent. fxe is that equivalent — a runnable, clean-room, modern C++ source port: give it the content from the user's original disks and it plays the game, on modern rendering and audio. A port that boots real content and behaves correctly is proof-of-understanding at the executable level.
Two questions shaped the decision:
- Legal posture. An open-source re-implementation of a commercial game engine is only safe as a clean-room work — written from documentation of behaviour and non-copyrightable facts (interfaces, struct layouts, algorithms described in prose), never a transcription of the original's decompiled expression. Separately, the game's content is EA's; the port must ship none of it.
- Where the code lives. Keeping generated output out of the repo was considered as caution, but that reduces visibility, not liability — and it forfeits a browsable, buildable, CI-tested port.
Options considered¶
- Generated-only, not committed — the repo holds only the generator; fxe is ephemeral build output. Lowest visibility, but no usable artifact and no CI coverage of the port itself.
- Committed, generator is the source of truth (chosen) — a committed generator emits fxe's C++; the emitted source is committed and kept in sync by CI (the same pattern already used for the generated matrices). Usable port + documented legal posture.
- Committed, hand-written — a conventional hand-written clean-room port, no generator. Simplest to
start and unambiguously clean-room, but loses the generate-from-truth link to
db/+ docs and drifts by hand.
Decision¶
fxe is a committed, generated, clean-room modern C++ source port of the game executable, with the generator as the source of truth.
- Generated from
db/+ docs by an in-repo generator; the emitted C++ is committed and a CI currency check regenerates and diffs it (as the reconstruction/format matrices are checked). - Clean-room discipline: the generator consumes our own facts and prose only. Behaviour is expressed independently; decompiler output is never transcribed. Same boundary as the rest of the RE effort (CLAUDE.md).
- Source-port legal model: fxe ships no assets and is inert without the user's original disks (the ScummVM / OpenMW model). The NOTICE records the clean-room provenance and the require-original-content posture.
- Rendering via
fx_render(ADR forthcoming if it grows): the OpenGL + faithful-software backends are a shared MIT module, extracted from fxs, that fxe and fxs both use — one renderer, not three (the fighters-legacy engine may adopt it too). - Validation lens, independent of fa-bridge: fxe proves the reconstruction docs by running; it is not a fa-bridge dependency. fxe (MIT source port) and fa-bridge (GPL bridge into the fighters-legacy engine) are independent consumers of the same reconstruction and share no code.
- Not a 1.0 gate: fxe is a stretch program on its own milestone, interleaved with the Phase 4 validation train.
Naming: fxe fits the fx family (fx_lib / fx / fxs / fxe) — the fx-engine: the runnable
engine, generated from db/ + docs, that replays FA content the way FA.EXE did. It renders
through fx_render (software + OpenGL today, Vulkan later).
Consequences¶
- New milestone (fxe — Clean-room source port) and epics #280 (fxe) / #281 (fx_render); #279 scopes the fx_lib asset interpreters the renderer consumes.
- CI gains an fxe currency check (regenerate → diff); the build never commits assets.
- MIT→GPL reuse is one-way and clean: fa-bridge / fighters-legacy may consume
fx_libandfx_render; nothing flows back. - The repo now hosts a generated implementation of the executable (fxe), not only documentation — a deliberate extension of "the docs are the product": fxe is the docs made executable.