Skip to content

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