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.