Add frontend screen system: ScreenId enum and 21 named functions
Browse filesMap the MFC CDialog-based menu system from 65 decompiled call sites.
Add ScreenId enum with 18 screen IDs (main menu through lobby).
Name 21 frontend/lobby functions and 4 global variables in Ghidra.
Add 25 new addresses to address.rs (14 frontend + 7 lobby + 4 globals).
Document screen transition architecture in frontend-screens.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
crates/openwa-types/src/address.rs
CHANGED
|
@@ -163,18 +163,53 @@ pub mod va {
|
|
| 163 |
pub const SHOW_CHAT_MESSAGE: u32 = 0x0052_ACB0;
|
| 164 |
pub const ON_CHAT_INPUT: u32 = 0x0052_B730;
|
| 165 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
// === Lobby / network ===
|
| 167 |
|
| 168 |
pub const LOBBY_HOST_COMMANDS: u32 = 0x004B_9B00;
|
| 169 |
pub const LOBBY_CLIENT_COMMANDS: u32 = 0x004A_ABB0;
|
| 170 |
pub const SEND_GAME_PACKET_WRAPPED: u32 = 0x0054_1130;
|
| 171 |
-
pub const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
// === Memory ===
|
| 174 |
|
| 175 |
pub const WA_MALLOC_MEMSET: u32 = 0x0053_E910;
|
| 176 |
pub const WA_FREE: u32 = 0x005D_0D2B;
|
| 177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
// === Game context (DDGame struct offsets) ===
|
| 179 |
// These are offsets from the DDGame base pointer, not absolute addresses.
|
| 180 |
// DDGame pointer is obtained from hookConstructDDGameWrapper param.
|
|
|
|
| 163 |
pub const SHOW_CHAT_MESSAGE: u32 = 0x0052_ACB0;
|
| 164 |
pub const ON_CHAT_INPUT: u32 = 0x0052_B730;
|
| 165 |
|
| 166 |
+
// === Frontend / menu screens ===
|
| 167 |
+
|
| 168 |
+
pub const FRONTEND_CHANGE_SCREEN: u32 = 0x0044_7A20;
|
| 169 |
+
pub const FRONTEND_FRAME_CONSTRUCTOR: u32 = 0x004E_CCA0;
|
| 170 |
+
pub const FRONTEND_DIALOG_CONSTRUCTOR: u32 = 0x0044_6BA0;
|
| 171 |
+
pub const FRONTEND_PALETTE_ANIMATION: u32 = 0x0042_2180;
|
| 172 |
+
pub const FRONTEND_LOAD_TRANSITION_PAL: u32 = 0x0044_7AA0;
|
| 173 |
+
pub const FRONTEND_PRE_TRANSITION_CLEANUP: u32 = 0x004E_4AE0;
|
| 174 |
+
pub const FRONTEND_ON_INITIAL_LOAD: u32 = 0x0042_9830;
|
| 175 |
+
pub const FRONTEND_LAUNCH_SINGLE_PLAYER: u32 = 0x0044_1D80;
|
| 176 |
+
pub const FRONTEND_ON_MULTIPLAYER: u32 = 0x0044_E850;
|
| 177 |
+
pub const FRONTEND_ON_NETWORK: u32 = 0x0044_EC10;
|
| 178 |
+
pub const FRONTEND_ON_MINIMIZE: u32 = 0x0048_6A10;
|
| 179 |
+
pub const FRONTEND_ON_OPTIONS_ACCEPT: u32 = 0x0048_DAB0;
|
| 180 |
+
pub const FRONTEND_ON_START_GAME: u32 = 0x004F_14A0;
|
| 181 |
+
pub const CDIALOG_DO_MODAL_CUSTOM: u32 = 0x0040_FD60;
|
| 182 |
+
|
| 183 |
// === Lobby / network ===
|
| 184 |
|
| 185 |
pub const LOBBY_HOST_COMMANDS: u32 = 0x004B_9B00;
|
| 186 |
pub const LOBBY_CLIENT_COMMANDS: u32 = 0x004A_ABB0;
|
| 187 |
pub const SEND_GAME_PACKET_WRAPPED: u32 = 0x0054_1130;
|
| 188 |
+
pub const LOBBY_DISPLAY_MESSAGE: u32 = 0x0049_3CB0;
|
| 189 |
+
pub const LOBBY_SEND_GREENTEXT: u32 = 0x004A_A990;
|
| 190 |
+
pub const LOBBY_PRINT_USED_VERSION: u32 = 0x004B_7E20;
|
| 191 |
+
pub const LOBBY_ON_DISCONNECT: u32 = 0x004B_AE40;
|
| 192 |
+
pub const LOBBY_ON_GAME_END: u32 = 0x004B_AEC0;
|
| 193 |
+
pub const LOBBY_ON_MESSAGE: u32 = 0x004B_D400;
|
| 194 |
+
pub const LOBBY_DIALOG_CONSTRUCTOR: u32 = 0x004C_D9A0;
|
| 195 |
+
pub const NETWORK_IS_AVAILABLE: u32 = 0x004D_4920;
|
| 196 |
|
| 197 |
// === Memory ===
|
| 198 |
|
| 199 |
pub const WA_MALLOC_MEMSET: u32 = 0x0053_E910;
|
| 200 |
pub const WA_FREE: u32 = 0x005D_0D2B;
|
| 201 |
|
| 202 |
+
// === Global variables (in .data) ===
|
| 203 |
+
|
| 204 |
+
/// Main frontend frame window (CWnd*)
|
| 205 |
+
pub const G_FRONTEND_FRAME: u32 = 0x006B_3908;
|
| 206 |
+
/// Main frontend HWND
|
| 207 |
+
pub const G_FRONTEND_HWND: u32 = 0x006B_390C;
|
| 208 |
+
/// Network mode (0=LAN, nonzero=WormNET)
|
| 209 |
+
pub const G_NETWORK_MODE: u32 = 0x007C_0D40;
|
| 210 |
+
/// Network sub-type selector
|
| 211 |
+
pub const G_NETWORK_SUBTYPE: u32 = 0x007C_0D68;
|
| 212 |
+
|
| 213 |
// === Game context (DDGame struct offsets) ===
|
| 214 |
// These are offsets from the DDGame base pointer, not absolute addresses.
|
| 215 |
// DDGame pointer is obtained from hookConstructDDGameWrapper param.
|
crates/openwa-types/src/frontend.rs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// Frontend screen identifiers.
|
| 2 |
+
///
|
| 3 |
+
/// Used by FrontendChangeScreen (0x447A20) to transition between
|
| 4 |
+
/// menu screens. The screen ID is passed to CDialog::EndDialog as
|
| 5 |
+
/// the result code, then returned from DoModal to drive navigation.
|
| 6 |
+
///
|
| 7 |
+
/// The frontend uses MFC CDialog modal dialogs, not the CTask hierarchy.
|
| 8 |
+
/// The main frame window is stored at global 0x6B3908.
|
| 9 |
+
///
|
| 10 |
+
/// Source: Ghidra decompilation of 65 call sites + wkJellyWorm Lobby.cpp
|
| 11 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
| 12 |
+
#[repr(i32)]
|
| 13 |
+
pub enum ScreenId {
|
| 14 |
+
/// Return to previous screen / cancel action
|
| 15 |
+
Cancel = 0,
|
| 16 |
+
/// Main menu screen
|
| 17 |
+
MainMenu = 10,
|
| 18 |
+
/// Local multiplayer setup
|
| 19 |
+
LocalMultiplayer = 11,
|
| 20 |
+
/// Window minimized / inactive state (checked via IsIconic)
|
| 21 |
+
Minimized = 15,
|
| 22 |
+
/// Initial game loading screen
|
| 23 |
+
InitialLoad = 16,
|
| 24 |
+
/// Single player game (Deathmatch)
|
| 25 |
+
SinglePlayer = 21,
|
| 26 |
+
/// Network options sub-screen
|
| 27 |
+
NetworkSubScreen = 26,
|
| 28 |
+
/// Start game path A (game setup, param+0x244 == 0)
|
| 29 |
+
StartGameA = 35,
|
| 30 |
+
/// Start game path B (game setup fallthrough)
|
| 31 |
+
StartGameB = 36,
|
| 32 |
+
/// Network setup path A (param+0x140 == 0)
|
| 33 |
+
NetworkSetupA = 37,
|
| 34 |
+
/// Network setup path B (fallthrough)
|
| 35 |
+
NetworkSetupB = 38,
|
| 36 |
+
/// Options accepted / theme saved (writes current.thm)
|
| 37 |
+
OptionsAccept = 39,
|
| 38 |
+
/// Network provider selection screen
|
| 39 |
+
NetworkSelection = 1700,
|
| 40 |
+
/// LAN game
|
| 41 |
+
Lan = 1702,
|
| 42 |
+
/// WormNET online lobby
|
| 43 |
+
WormNet = 1703,
|
| 44 |
+
/// Lobby as host
|
| 45 |
+
LobbyHost = 1704,
|
| 46 |
+
/// Lobby as client / join game
|
| 47 |
+
LobbyClient = 1706,
|
| 48 |
+
/// Lobby game start
|
| 49 |
+
LobbyGameStart = 1707,
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
impl TryFrom<i32> for ScreenId {
|
| 53 |
+
type Error = i32;
|
| 54 |
+
|
| 55 |
+
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
| 56 |
+
match value {
|
| 57 |
+
0 => Ok(Self::Cancel),
|
| 58 |
+
10 => Ok(Self::MainMenu),
|
| 59 |
+
11 => Ok(Self::LocalMultiplayer),
|
| 60 |
+
15 => Ok(Self::Minimized),
|
| 61 |
+
16 => Ok(Self::InitialLoad),
|
| 62 |
+
21 => Ok(Self::SinglePlayer),
|
| 63 |
+
26 => Ok(Self::NetworkSubScreen),
|
| 64 |
+
35 => Ok(Self::StartGameA),
|
| 65 |
+
36 => Ok(Self::StartGameB),
|
| 66 |
+
37 => Ok(Self::NetworkSetupA),
|
| 67 |
+
38 => Ok(Self::NetworkSetupB),
|
| 68 |
+
39 => Ok(Self::OptionsAccept),
|
| 69 |
+
1700 => Ok(Self::NetworkSelection),
|
| 70 |
+
1702 => Ok(Self::Lan),
|
| 71 |
+
1703 => Ok(Self::WormNet),
|
| 72 |
+
1704 => Ok(Self::LobbyHost),
|
| 73 |
+
1706 => Ok(Self::LobbyClient),
|
| 74 |
+
1707 => Ok(Self::LobbyGameStart),
|
| 75 |
+
_ => Err(value),
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
}
|
crates/openwa-types/src/lib.rs
CHANGED
|
@@ -10,4 +10,5 @@ pub mod ddgame_wrapper;
|
|
| 10 |
pub mod landscape;
|
| 11 |
pub mod dssound;
|
| 12 |
pub mod opengl;
|
|
|
|
| 13 |
pub mod address;
|
|
|
|
| 10 |
pub mod landscape;
|
| 11 |
pub mod dssound;
|
| 12 |
pub mod opengl;
|
| 13 |
+
pub mod frontend;
|
| 14 |
pub mod address;
|
docs/re-notes/frontend-screens.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Frontend Screen System
|
| 2 |
+
|
| 3 |
+
## Architecture
|
| 4 |
+
|
| 5 |
+
The frontend uses **MFC CDialog modal dialogs**, not the CTask class hierarchy.
|
| 6 |
+
Screen transitions work through a centralized dispatcher:
|
| 7 |
+
|
| 8 |
+
```
|
| 9 |
+
FrontendFrame (global at 0x6B3908, constructed at 0x4ECCA0)
|
| 10 |
+
βββ CDialog modal loop (CDialog__DoModal_Custom at 0x40FD60)
|
| 11 |
+
βββ Button handler calls FrontendChangeScreen(screen_id)
|
| 12 |
+
βββ FrontendChangeScreen calls CDialog::EndDialog(screen_id)
|
| 13 |
+
βββ DoModal returns screen_id to caller
|
| 14 |
+
βββ Caller decides next dialog based on return value
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
## FrontendChangeScreen (0x447A20)
|
| 18 |
+
|
| 19 |
+
Two modes based on `g_FrontendFrame` (0x6B3908):
|
| 20 |
+
|
| 21 |
+
- **Normal** (g_FrontendFrame != 0): Disable window β palette transition
|
| 22 |
+
(0x422180) β call vtable method at +0x15C twice β re-enable window β
|
| 23 |
+
CDialog::EndDialog(screen_id)
|
| 24 |
+
- **Init** (g_FrontendFrame == 0): Store screen_id at ESI+0x44, clear flag bit
|
| 25 |
+
|
| 26 |
+
## Screen IDs
|
| 27 |
+
|
| 28 |
+
| ID | Hex | Name | Context |
|
| 29 |
+
|----|-----|------|---------|
|
| 30 |
+
| 0 | 0x00 | Cancel | Return to previous / close dialog |
|
| 31 |
+
| 10 | 0x0A | MainMenu | Main menu screen |
|
| 32 |
+
| 11 | 0x0B | LocalMultiplayer | Local multiplayer setup |
|
| 33 |
+
| 15 | 0x0F | Minimized | Window minimized (IsIconic check) |
|
| 34 |
+
| 16 | 0x10 | InitialLoad | Initial game loading screen |
|
| 35 |
+
| 21 | 0x15 | SinglePlayer | Single player / Deathmatch |
|
| 36 |
+
| 26 | 0x1A | NetworkSubScreen | Network options sub-screen |
|
| 37 |
+
| 35 | 0x23 | StartGameA | Start game (path A) |
|
| 38 |
+
| 36 | 0x24 | StartGameB | Start game (path B) |
|
| 39 |
+
| 37 | 0x25 | NetworkSetupA | Network setup (path A) |
|
| 40 |
+
| 38 | 0x26 | NetworkSetupB | Network setup (path B) |
|
| 41 |
+
| 39 | 0x27 | OptionsAccept | Options saved (writes current.thm) |
|
| 42 |
+
| 1700 | 0x6A4 | NetworkSelection | Network provider selection |
|
| 43 |
+
| 1702 | 0x6A6 | Lan | LAN game |
|
| 44 |
+
| 1703 | 0x6A7 | WormNet | WormNET online lobby |
|
| 45 |
+
| 1704 | 0x6A8 | LobbyHost | Hosting a game |
|
| 46 |
+
| 1706 | 0x6AA | LobbyClient | Joining a game |
|
| 47 |
+
| 1707 | 0x6AB | LobbyGameStart | Lobby β game start |
|
| 48 |
+
|
| 49 |
+
Note: Some callers pass dynamic screen IDs. There may be additional IDs not
|
| 50 |
+
discovered through static analysis.
|
| 51 |
+
|
| 52 |
+
## Key Globals
|
| 53 |
+
|
| 54 |
+
| Address | Name | Type | Description |
|
| 55 |
+
|---------|------|------|-------------|
|
| 56 |
+
| 0x6B3908 | g_FrontendFrame | CWnd* | Main application frame window |
|
| 57 |
+
| 0x6B390C | g_FrontendHwnd | HWND | Main window handle |
|
| 58 |
+
| 0x7C0D40 | g_NetworkMode | int | 0=LAN, nonzero=WormNET |
|
| 59 |
+
| 0x7C0D68 | g_NetworkSubtype | int | Network sub-type selector |
|
| 60 |
+
|
| 61 |
+
## Key Functions
|
| 62 |
+
|
| 63 |
+
| Address | Name | Purpose |
|
| 64 |
+
|---------|------|---------|
|
| 65 |
+
| 0x40FD60 | CDialog__DoModal_Custom | Custom DoModal returning screen ID |
|
| 66 |
+
| 0x422180 | Frontend__PaletteAnimation | Palette transition effect |
|
| 67 |
+
| 0x429830 | Frontend__OnInitialLoad | Initial game load handler |
|
| 68 |
+
| 0x441D80 | Frontend__LaunchSinglePlayer | Single player launch |
|
| 69 |
+
| 0x446BA0 | FrontendDialog__Constructor | Dialog constructor |
|
| 70 |
+
| 0x447A20 | FrontendChangeScreen | Central screen transition dispatcher |
|
| 71 |
+
| 0x447AA0 | Frontend__LoadTransitionPalette | Load transition palette |
|
| 72 |
+
| 0x44E850 | Frontend__OnMultiplayerButton | Multiplayer button handler |
|
| 73 |
+
| 0x44EC10 | Frontend__OnNetworkButton | Network button handler |
|
| 74 |
+
| 0x486A10 | Frontend__OnMinimize | Window minimize handler |
|
| 75 |
+
| 0x48DAB0 | Frontend__OnOptionsAccept | Options accept handler |
|
| 76 |
+
| 0x493CB0 | Lobby__DisplayMessage | Display lobby chat message |
|
| 77 |
+
| 0x4AA990 | Lobby__SendGreentext | Send colored lobby text |
|
| 78 |
+
| 0x4B7E20 | Lobby__PrintUsedVersion | Print version to lobby |
|
| 79 |
+
| 0x4BAE40 | Lobby__OnDisconnect | Handle lobby disconnect |
|
| 80 |
+
| 0x4BAEC0 | Lobby__OnGameEnd | Handle game end in lobby |
|
| 81 |
+
| 0x4BD400 | Lobby__OnMessage | Handle lobby message |
|
| 82 |
+
| 0x4CD9A0 | LobbyDialog__Constructor | Lobby dialog constructor |
|
| 83 |
+
| 0x4D4920 | Network__IsAvailable | Check network availability |
|
| 84 |
+
| 0x4E4AE0 | Frontend__PreTransitionCleanup | Pre-transition cleanup |
|
| 85 |
+
| 0x4ECCA0 | FrontendFrame__Constructor | Frame window constructor |
|
| 86 |
+
| 0x4F14A0 | Frontend__OnStartGameButton | Start game button handler |
|