wormz-a / docs /re-notes /frontend-screens.md
paavohtl's picture
Claude Opus 4.6
docs: frontend state machine RE + 47 Ghidra labels
8f33a03
|
Raw History Blame Contribute Delete
9.26 kB
# Frontend Navigation State Machine
## Overview
The WA frontend uses MFC `CDialog` modal dialogs driven by a main navigation loop.
The loop is at `0x4E6440` (`Frontend__MainNavigationLoop`), which is the `CWinApp::Run()`
virtual method override β€” a 19KB function that handles initialization and screen dispatch.
## Architecture
```
g_CurrentScreen (0x6B3504)
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Main Navigation Loop β”‚ (0x4E6440)
β”‚ do { β”‚
β”‚ switch(screenId) { β”‚
β”‚ case 10: ... │──► Create dialog ──► DoModal() ──► result
β”‚ case 11: ... β”‚ β”‚
β”‚ ... β”‚ β”‚
β”‚ } │◄──── store result in g_CurrentScreen β”€β”˜
β”‚ } while(true); β”‚
β”‚ β”‚
β”‚ case 15: EXIT │──► cleanup, return
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
Each iteration:
1. Reads `g_CurrentScreen` (0x6B3504)
2. Dispatches to the matching case β†’ allocates dialog β†’ calls constructor β†’ DoModal
3. `FrontendChangeScreen(screen_id)` inside the dialog calls `EndDialog(screen_id)`
4. DoModal returns the result code β†’ stored back into `g_CurrentScreen`
5. Previous dialog destroyed via `Frontend__PostScreenCleanup` (0x4EB450)
6. Loop continues
## FrontendChangeScreen (0x447A20)
`stdcall(screen_id)` with `ESI` = dialog this pointer (MSVC `__usercall`).
Two modes based on `g_FrontendFrame` (0x6B3908):
- **Normal** (g_FrontendFrame != 0): Disable window β†’ palette transition
(0x422180) β†’ call vtable[0x15C] twice (args 1, 2) β†’ re-enable window β†’
CDialog::EndDialog(screen_id)
- **Init** (g_FrontendFrame == 0): Store screen_id at dialog+0x44, clear flag
bit 0x10 at dialog+0x3C
## Screen Dispatch Table
### Valid main-loop screens (10–30)
| ID | Hex | Name | Constructor | Notes |
|----|-----|------|-------------|-------|
| 10 | 0x0A | Deathmatch | 0x440F40 | `PreTransitionCleanup` before |
| 11 | 0x0B | LocalMultiplayer | 0x49C420 | `PreTransitionCleanup` before |
| 12 | 0x0C | Training | 0x4E0880 | Palette load before |
| 14 | 0x0E | Missions | 0x499190 | Palette load before |
| 15 | 0x0F | **Exit** | β€” | Cleanup + return from CWinApp::Run |
| 16 | 0x10 | PostInitMenu | 0x4C91B0 | Calls 0x4E4BD0 after |
| 17 | 0x11 | MainMenuRedirect | β€” | Sets g_CurrentScreen = 18 |
| 18 | 0x12 | **MainMenu** | 0x4866C0 | Primary menu dialog |
| 21 | 0x15 | SinglePlayer | 0x4D69F0 | Palette load before |
| 22 | 0x16 | CampaignA | 0x4A2B70 | `PreTransitionCleanup` after |
| 23 | 0x17 | CampaignB | 0x4A24D0 | `PreTransitionCleanup` after |
| 24 | 0x18 | AdvancedSettings | 0x4279E0 | Calls 0x4E4BD0 after |
| 25 | 0x19 | IntroMovie | 0x470870 | Hardcodes next=18, sets g_SkipToMainMenu |
| 27 | 0x1B | NetworkHostSetup | 0x4ADCA0 | `PreTransitionCleanup` after |
| 28 | 0x1C | NetworkOnlineSetup | 0x4ACBC0 | `PreTransitionCleanup` after |
| 29 | 0x1D | NetworkDialogA | 0x4BC970 | `PreTransitionCleanup` after |
| 30 | 0x1E | NetworkDialogB | 0x4BBC00 | `PreTransitionCleanup` after |
### Network/lobby screens (1700–1707)
| ID | Hex | Name | Constructor | Notes |
|----|-----|------|-------------|-------|
| 1700 | 0x6A4 | NetworkProviderSelect | 0x4A7990 | Special post-DoModal logic |
| 1701 | 0x6A5 | NetworkSettings | 0x4C23C0 | |
| 1702 | 0x6A6 | LAN | 0x480A80 | |
| 1703 | 0x6A7 | WormNet | 0x472400 | |
| 1704 | 0x6A8 | LobbyHost | 0x4B0160 | Fall-through from setup |
| 1705 | 0x6A9 | LobbyHostContinued | (fall-through) | |
| 1706 | 0x6AA | LobbyClient | (fall-through) | |
| 1707 | 0x6AB | LobbyGameStart | 0x4BDBE0 | |
### Dialog result codes (NOT dispatched by main loop)
These are returned by `EndDialog` from sub-dialogs (button handlers, options screens).
If they reach the main loop, they hit the default case β†’ `g_CurrentScreen = 0x0F` β†’ exit.
By that point, the game has already launched or the action has been taken.
| ID | Hex | Name | Meaning |
|----|-----|------|---------|
| 0 | 0x00 | Cancel | Return to previous / back |
| 1 | 0x01 | Splash | Splash/intro trigger |
| 2 | 0x02 | NetworkOptionA | Network option result |
| 4 | 0x04 | SinglePlayerPrep | SP prep result |
| 5 | 0x05 | SinglePlayerConfirm | SP confirm result |
| 32 | 0x20 | SchemeEditorDone | Return from scheme/weapon editor |
| 35 | 0x23 | StartGameOffline | Start game, no network |
| 36 | 0x24 | StartGameOnline | Start game, network available |
| 37 | 0x25 | MultiplayerLocal | Multiplayer, no network |
| 38 | 0x26 | MultiplayerOnline | Multiplayer, network available |
| 39 | 0x27 | OptionsAccepted | Return after applying options + saving theme |
| 50 | 0x32 | InGameA | In-game state |
| 52 | 0x34 | InGameB | In-game state |
| 58 | 0x3A | InGameC | In-game state |
#### Offline/Online pairs
The "Start Game" and "Multiplayer" paths each check a network availability flag
and branch to an offline/online result code:
- **Start Game** (`this+0x244` flag): 35 (offline) / 36 (online)
- **Multiplayer** (`this+0x140` flag): 37 (offline) / 38 (online)
Both paths use 0 (Cancel) as the "back to main menu" value.
## Key Functions
| Address | Name | Purpose |
|---------|------|---------|
| 0x4E6440 | Frontend__MainNavigationLoop | CWinApp::Run override β€” main dispatch loop |
| 0x447A20 | FrontendChangeScreen | Ends current dialog via EndDialog(screen_id) |
| 0x447960 | Frontend__DoModalWrapper | Palette transition + custom DoModal |
| 0x40FD60 | CDialog__DoModal_Custom | Custom MFC DoModal |
| 0x40FBE0 | CDialog__CustomMsgPump | Custom message pump (replaces RunModalLoop) |
| 0x40FF90 | FrontendDialog__OnIdle | Per-frame idle: cursor, mouse, paint dispatch |
| 0x40BF60 | FrontendDialog__PaintControlTree | Traverse control tree, paint dirty controls |
| 0x4EB450 | Frontend__PostScreenCleanup | Destroys previous dialog |
| 0x4E4AE0 | Frontend__PreTransitionCleanup | Cleanup before certain transitions |
| 0x422180 | Frontend__PaletteAnimation | Palette transition effect |
| 0x446BA0 | FrontendDialog__Constructor | Base frontend dialog constructor |
| 0x44E850 | Frontend__OnMultiplayerButton | Multiplayer button handler |
| 0x44EC10 | Frontend__OnNetworkButton | Network button handler |
| 0x48DAB0 | Frontend__OnOptionsAccept | Options accept handler |
| 0x4F14A0 | Frontend__OnStartGameButton | Start game button handler |
| 0x4D4920 | Network__IsAvailable | Check network availability |
## Key Globals
| Address | Name | Type | Description |
|---------|------|------|-------------|
| 0x6B3504 | g_CurrentScreen | u32 | Screen ID driving the main dispatch loop |
| 0x6B3908 | g_FrontendFrame | CFrameWnd* | Main frame window |
| 0x6B390C | g_FrontendHwnd | HWND | Main window handle |
| 0x79D6D4 | g_DDDisplayWrapper | void* | Display backend wrapper (valid during entire frontend) |
| 0x7A083D | g_SkipToMainMenu | u8 | Skip splash, go directly to main menu |
| 0x7A083F | g_AutoNetworkFlag | u8 | Auto-network mode flag |
| 0x7C0A20 | g_MainMenuActive | u8 | 0xFF during screen 18 |
| 0x7C03D0 | g_CWinApp | CWinApp* | MFC application singleton |
| 0x7C0D40 | g_NetworkMode | u32 | 0=LAN, nonzero=WormNET |
| 0x7C0D68 | g_NetworkSubtype | u32 | Network sub-type selector |
## Bitmap Font System
WA uses a custom bitmap font renderer, NOT GDI, for all game/frontend text.
### Font files
- `Graphics\Font.bmp` + `Data\Gfx\FontExt\%s2.fex` (extended glyphs)
- Four sizes: `SmlFont2.bmp` (12px), `StdFont2.bmp` (17px), `MedFont2.bmp` (25px), `BigFont2.bmp` (33px)
### Key structures
- Font array at `0x7A0F58`, each font is `0x241C` bytes
- Glyph table: 256 entries at `font+0x14`, each `0x24` bytes
- Character width table at `0x6B2DD9` (256 bytes)
- Registry options: `LargerFonts` (font+0x20), `FrontendFontDynamicAntialiasing` (font+0x70)
### Key functions
| Address | Name | Description |
|---------|------|-------------|
| 0x414680 | Font__LoadFonts | Loads all four font sizes from BMP files |
| 0x4143D0 | Font__RenderGlyphs | Core glyph renderer: iterates chars, blits from sprite sheet |
| 0x427830 | Font__DrawText | Higher-level: measures string, creates surface, renders |
| 0x5236B0 | DDDisplay__DrawTextOnBitmap | thiscall(font_id, bitmap, hAlign, vAlign, msg, a7, a8) |
| 0x4FAF00 | DDDisplay__ConstructTextbox | thiscall(dst, length, fontid) |
| 0x4FB070 | SetTextboxText | stdcall(textbox, msg, textcolor, color1, color2, a6, a7, opacity) |
| 0x542200 | DrawTextboxLocal | Draws textbox at screen position |
### Rendering pipeline
1. Create surface via `g_DDDisplayWrapper->vtable[22]` (CreateSurface factory)
2. `Font__RenderGlyphs` iterates characters, looks up glyph data in font table
3. Each glyph is blitted from the font sprite sheet onto the surface
4. Surface blitted to screen via `Surface__Blit` (0x403BF0 β†’ surface->vtable[11])
### Display backends
The display wrapper at `0x79D6D4` (vtable `0x662EC8`, size 0x1C) delegates to one of 6 backends:
DirectDraw legacy, DirectDraw classic, D3D9 basic, D3D9 shader, OpenGL CPU, OpenGL GPU.
Concrete backend stored at `wrapper+0x18`. Initialized in the main navigation loop
before any dialog is created.