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 |