paavohtl commited on
Commit
3b3c953
Β·
2 Parent(s): c7109bc47c8e50

Merge branch 'init-game-state': hook all InitGameState sub-functions

Browse files

19 hooks on DDGame__InitGameState sub-functions:
- 7 usercall functions ported to pure Rust (no asm bridges)
- 12 stdcall constructors with passthrough hooks (verified conventions)
- 1 bridged function (InitFeatureFlags, 600 lines, deferred)

New files: engine/game_state_init.rs, replacements/game_state_hooks.rs
Headful + headless replay tests pass throughout.

crates/openwa-core/src/address.rs CHANGED
@@ -350,6 +350,46 @@ pub mod va {
350
  /// PaletteContext__Init (0x5411A0): initialize palette context free-list.
351
  /// usercall(EAX=context), plain RET.
352
  pub const PALETTE_CONTEXT_INIT: u32 = 0x0054_11A0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  /// DDGame__InitDisplayFinal_Maybe (0x56A830): display finalization for non-headless.
354
  pub const DDGAME_INIT_DISPLAY_FINAL: u32 = 0x0056_A830;
355
  /// PCLandscape__Constructor (0x57ACB0): constructs 0xB44-byte landscape object.
 
350
  /// PaletteContext__Init (0x5411A0): initialize palette context free-list.
351
  /// usercall(EAX=context), plain RET.
352
  pub const PALETTE_CONTEXT_INIT: u32 = 0x0054_11A0;
353
+ /// SpriteGfxTable__Init (0x541620): fastcall(ECX=base, EDX=count), plain RET.
354
+ pub const SPRITE_GFX_TABLE_INIT: u32 = 0x0054_1620;
355
+ /// RingBuffer__Init (0x541060): usercall(EAX=capacity, ESI=struct), plain RET.
356
+ pub const RING_BUFFER_INIT: u32 = 0x0054_1060;
357
+ /// CGameTask__InitTeamScoring (0x528510): fastcall(ECX=wrapper), plain RET.
358
+ pub const INIT_TEAM_SCORING: u32 = 0x0052_8510;
359
+ /// CGameTask__InitAllianceData (0x5262D0): usercall(EAX=wrapper), plain RET.
360
+ pub const INIT_ALLIANCE_DATA: u32 = 0x0052_62D0;
361
+ /// DDGame__IsSuperWeapon (0x565960): usercall(EAX=weapon_index) + 1 stack param, plain RET.
362
+ pub const IS_SUPER_WEAPON: u32 = 0x0056_5960;
363
+ /// DDGame__CheckWeaponAvail (0x53FFC0): fastcall(ECX=ddgame) + unaff_ESI, plain RET.
364
+ pub const CHECK_WEAPON_AVAIL: u32 = 0x0053_FFC0;
365
+ /// CGameTask__InitTurnState (0x528690): usercall(EAX=wrapper), plain RET.
366
+ pub const INIT_TURN_STATE: u32 = 0x0052_8690;
367
+ /// CGameTask__InitLandscapeFlags (0x528480): usercall(EAX=wrapper), plain RET.
368
+ pub const INIT_LANDSCAPE_FLAGS: u32 = 0x0052_8480;
369
+ /// HudPanel__Constructor (0x524070): stdcall(this), RET 0x4.
370
+ pub const HUD_PANEL_CONSTRUCTOR: u32 = 0x0052_4070;
371
+ /// DDGame__InitTeamsFromSetup (0x5220B0): stdcall(team_arena, setup_data), RET 0x8.
372
+ pub const INIT_TEAMS_FROM_SETUP: u32 = 0x0052_20B0;
373
+ /// TeamManager__Constructor (0x563D40): stdcall(this, wrapper), RET 0x8.
374
+ pub const TEAM_MANAGER_CONSTRUCTOR: u32 = 0x0056_3D40;
375
+ /// CTaskTurnGame__Constructor (0x55B280): stdcall(this, setup_data), RET 0x8.
376
+ pub const TURN_GAME_CONSTRUCTOR: u32 = 0x0055_B280;
377
+ /// CTaskGameState__Constructor (0x532330): stdcall(this, param), RET 0x8.
378
+ pub const GAME_STATE_CONSTRUCTOR: u32 = 0x0053_2330;
379
+ /// DisplayGfx__ConstructFull (0x563FC0): stdcall(5 params), RET 0x14.
380
+ pub const DISPLAYGFX_CONSTRUCT_FULL: u32 = 0x0056_3FC0;
381
+ /// DDDisplay__ConstructTextbox (0x4FAF00): stdcall(3 params), RET 0xC.
382
+ pub const CONSTRUCT_TEXTBOX: u32 = 0x004F_AF00;
383
+ /// FUN_567770 (0x567770): stdcall(1 param = wrapper), RET 0x4.
384
+ pub const FUN_567770: u32 = 0x0056_7770;
385
+ /// FUN_545FD0 (0x545FD0): stdcall(3 params), RET 0xC. Buffer object constructor.
386
+ pub const BUFFER_OBJECT_CONSTRUCTOR: u32 = 0x0054_5FD0;
387
+ /// FUN_4FB490 (0x4FB490): stdcall(1 param), RET 0x4. GameStateStream sub-init.
388
+ pub const GAME_STATE_STREAM_INIT: u32 = 0x004F_B490;
389
+ /// FUN_540440 (0x540440): stdcall(2 params), RET 0x8. Display object constructor.
390
+ pub const DISPLAY_OBJECT_CONSTRUCTOR: u32 = 0x0054_0440;
391
+ /// DDGame__InitFeatureFlags (0x524700): stdcall(wrapper), RET 0x4.
392
+ pub const DDGAME_INIT_FEATURE_FLAGS: u32 = 0x0052_4700;
393
  /// DDGame__InitDisplayFinal_Maybe (0x56A830): display finalization for non-headless.
394
  pub const DDGAME_INIT_DISPLAY_FINAL: u32 = 0x0056_A830;
395
  /// PCLandscape__Constructor (0x57ACB0): constructs 0xB44-byte landscape object.
crates/openwa-core/src/engine/game_state_init.rs ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Pure Rust implementations of DDGame__InitGameState sub-functions.
2
+ //!
3
+ //! Each function is hooked individually so it works regardless of whether
4
+ //! InitGameState itself is Rust or the original WA code.
5
+
6
+ use crate::wa_alloc::wa_malloc;
7
+
8
+ /// Pure Rust implementation of SpriteGfxTable__Init (0x541620).
9
+ ///
10
+ /// Convention: fastcall(ECX=base, EDX=count), plain RET.
11
+ ///
12
+ /// Initializes two parallel arrays:
13
+ /// - `base[0..count]`: identity permutation (index[i] = i)
14
+ /// - `base+0x2000[0..count]`: all 0xFFFFFFFF (unused markers)
15
+ /// Plus 3 trailer fields at +0x3000/+0x3004/+0x3008.
16
+ pub unsafe fn sprite_gfx_table_init(base: *mut u8, count: u32) {
17
+ for i in 0..count {
18
+ // Index table: base + i*4 = i
19
+ *((base as *mut u32).add(i as usize)) = i;
20
+ // Lookup table: base + 0x2000 + i*4 = 0xFFFFFFFF
21
+ *((base.add(0x2000) as *mut u32).add(i as usize)) = 0xFFFF_FFFF;
22
+ }
23
+ *(base.add(0x3000) as *mut u32) = count;
24
+ *(base.add(0x3004) as *mut u32) = 0;
25
+ *(base.add(0x3008) as *mut u32) = count;
26
+ }
27
+
28
+ /// Pure Rust implementation of RingBuffer__Init (0x541060).
29
+ ///
30
+ /// Convention: usercall(EAX=capacity, ESI=struct_ptr), plain RET.
31
+ ///
32
+ /// Allocates a zero-filled buffer of `capacity` bytes (aligned + 0x20 header),
33
+ /// then initializes the ring buffer struct (7 DWORDs):
34
+ /// - [0]: data pointer
35
+ /// - [1]: capacity
36
+ /// - [2]-[6]: zeroed (head, tail, count, etc.)
37
+ pub unsafe fn ring_buffer_init(struct_ptr: *mut u8, capacity: u32) {
38
+ let alloc_size = ((capacity + 3) & !3) + 0x20;
39
+ let data = wa_malloc(alloc_size);
40
+ if !data.is_null() {
41
+ core::ptr::write_bytes(data, 0, capacity as usize);
42
+ }
43
+
44
+ let s = struct_ptr as *mut u32;
45
+ *s.add(0) = data as u32; // data pointer
46
+ *s.add(1) = capacity; // capacity
47
+ *s.add(2) = 0; // field 2
48
+ *s.add(3) = 0; // field 3
49
+ *s.add(4) = 0; // field 4
50
+ *s.add(5) = 0; // field 5
51
+ *s.add(6) = 0; // field 6
52
+ }
53
+
54
+ /// Maximum number of teams (13 slots: 6 real + sentinels/padding).
55
+ const MAX_TEAMS: usize = 13;
56
+
57
+ /// Team entry stride in GameInfo (3000 bytes per team slot).
58
+ const TEAM_ENTRY_STRIDE: usize = 3000;
59
+
60
+ /// Helper: read the GameInfo pointer from a DDGameWrapper pointer.
61
+ ///
62
+ /// Chain: wrapper+0x488 β†’ DDGame, DDGame+0x24 β†’ GameInfo.
63
+ #[inline]
64
+ unsafe fn game_info_from_wrapper(wrapper: *mut u8) -> *mut u8 {
65
+ let ddgame = *(wrapper.add(0x488) as *const *mut u8);
66
+ *(ddgame.add(0x24) as *const *mut u8)
67
+ }
68
+
69
+ /// Helper: read the DDGame pointer from a DDGameWrapper pointer.
70
+ #[inline]
71
+ unsafe fn ddgame_from_wrapper(wrapper: *mut u8) -> *mut u8 {
72
+ *(wrapper.add(0x488) as *const *mut u8)
73
+ }
74
+
75
+ /// Pure Rust implementation of CGameTask_InitTeamScoring_Maybe (0x528510).
76
+ ///
77
+ /// Convention: fastcall(ECX=wrapper), plain RET.
78
+ ///
79
+ /// Initializes 7 parallel u32 arrays (13 elements each) in CGameTask, then
80
+ /// sets the starting-team flag, initializes team task pointer flags based on
81
+ /// game mode (training vs normal), and zeros CTask sub-object fields.
82
+ ///
83
+ /// # CGameTask array layout (all 13 Γ— u32):
84
+ /// - +0x0F4..+0x128: array_0 β€” zeroed
85
+ /// - +0x128..+0x15C: array_1 β€” zeroed
86
+ /// - +0x15C..+0x190: array_2 β€” scoring_param_a * 50
87
+ /// - +0x190..+0x1C4: array_3 β€” zeroed
88
+ /// - +0x1C4..+0x1F8: array_4 β€” scoring_param_b * 50
89
+ /// - +0x1F8..+0x22C: array_5 β€” scoring_param_b * 50
90
+ /// - +0x2BC..+0x2F0: array_6 β€” set to 1
91
+ ///
92
+ /// # Other fields:
93
+ /// - +0x128[starting_team]: set to 1 (starting team marker in array_1)
94
+ /// - +0x22C[0..team_count]: team activity flags (-1 normal, -2 training)
95
+ /// - +0x054[0..team_count]: CTask pointers β€” sub-fields zeroed if non-null
96
+ pub unsafe fn init_team_scoring(wrapper: *mut u8) {
97
+ let game_info = game_info_from_wrapper(wrapper);
98
+
99
+ // GameInfo+0xD788: scoring_param_a (u16)
100
+ // GameInfo+0xD78A: scoring_param_b (u16)
101
+ let scoring_param_a = *(game_info.add(0xD788) as *const u16) as u32;
102
+ let scoring_param_b = *(game_info.add(0xD78A) as *const u16) as u32;
103
+ let value_a = scoring_param_a * 50; // uVar1 * 0x32
104
+ let value_b = scoring_param_b * 50; // iVar4
105
+
106
+ // Initialize 7 parallel arrays (13 elements each).
107
+ // puVar3 starts at wrapper+0x128, indexed relative to that.
108
+ let base = wrapper.add(0x128) as *mut u32;
109
+ for i in 0..MAX_TEAMS {
110
+ let p = base.add(i);
111
+ *p.sub(0xd) = 0; // +0x0F4[i]: array_0 β€” zeroed
112
+ *p = 0; // +0x128[i]: array_1 β€” zeroed
113
+ *p.add(0xd) = value_a; // +0x15C[i]: array_2 β€” scoring_param_a * 50
114
+ *p.add(0x27) = value_b; // +0x1C4[i]: array_4 β€” scoring_param_b * 50
115
+ *p.add(0x34) = value_b; // +0x1F8[i]: array_5 β€” scoring_param_b * 50
116
+ *p.add(0x1a) = 0; // +0x190[i]: array_3 β€” zeroed
117
+ *p.add(99) = 1; // +0x2BC[i]: array_6 β€” set to 1
118
+ }
119
+
120
+ // Mark the starting team in array_1: wrapper+0x128[starting_team] = 1
121
+ // GameInfo+0xD9DC: starting team index (i8)
122
+ let starting_team = *(game_info.add(0xD9DC) as *const i8) as i32;
123
+ *(base.add(starting_team as usize)) = 1;
124
+
125
+ // GameInfo+0xD9DD: mode flag (i8, negative = training/replay mode)
126
+ let mode_flag = *(game_info.add(0xD9DD) as *const i8);
127
+
128
+ // GameInfo+0x0000: first byte = total team count (num_teams, u8)
129
+ let num_teams = *game_info as u8 as i32;
130
+
131
+ // Team activity flags at wrapper+0x22C (one u32 per team)
132
+ let flags_base = wrapper.add(0x22C) as *mut u32;
133
+
134
+ if mode_flag < 0 {
135
+ // Training/replay mode: set all flags to -2 (0xFFFFFFFE)
136
+ if num_teams > 0 {
137
+ for i in 0..num_teams as usize {
138
+ *flags_base.add(i) = 0xFFFF_FFFE;
139
+ }
140
+ }
141
+
142
+ // For each active team slot in GameInfo, clear the flag to 0
143
+ // GameInfo+0x44C: team count for alliance data (u8)
144
+ let game_info2 = game_info_from_wrapper(wrapper);
145
+ let alliance_team_count = *game_info2.add(0x44C) as u8 as i32;
146
+ if alliance_team_count > 0 {
147
+ let mut offset: usize = 0;
148
+ for _ in 0..alliance_team_count {
149
+ // GameInfo+0x450 + team_index*3000: alliance group (i8)
150
+ let alliance_group = *(game_info2.add(0x450 + offset) as *const i8) as i32;
151
+ if alliance_group >= 0 {
152
+ *flags_base.add(alliance_group as usize) = 0;
153
+ }
154
+ // Re-read game_info in case pointer chain changed (matches original)
155
+ let gi = game_info_from_wrapper(wrapper);
156
+ let _ = gi; // original re-reads but we already have the value
157
+ offset += TEAM_ENTRY_STRIDE;
158
+ }
159
+ }
160
+ } else {
161
+ // Normal mode: set all flags to -1 (0xFFFFFFFF)
162
+ if num_teams > 0 {
163
+ for i in 0..num_teams as usize {
164
+ *flags_base.add(i) = 0xFFFF_FFFF;
165
+ }
166
+ }
167
+
168
+ // Set starting team's flag to 1
169
+ // GameInfo+0xD9DD: starting team index for flags (i8, reuse mode_flag which is the same field)
170
+ let starting_flag_team = *(game_info_from_wrapper(wrapper).add(0xD9DD) as *const i8) as i32;
171
+ *flags_base.add(starting_flag_team as usize) = 1;
172
+ }
173
+
174
+ // Zero CTask sub-object fields for each team's task pointer
175
+ // wrapper+0x54[i] = CTask pointer, clear offsets +0x08..+0x18 (5 DWORDs)
176
+ let num_teams2 = *game_info_from_wrapper(wrapper) as u8 as i32;
177
+ if num_teams2 > 0 {
178
+ let task_ptrs = wrapper.add(0x54) as *mut *mut u8;
179
+ for i in 0..num_teams2 as usize {
180
+ let task = *task_ptrs.add(i);
181
+ if !task.is_null() {
182
+ *(task.add(0x18) as *mut u32) = 0;
183
+ *(task.add(0x14) as *mut u32) = 0;
184
+ *(task.add(0x10) as *mut u32) = 0;
185
+ *(task.add(0x0C) as *mut u32) = 0;
186
+ *(task.add(0x08) as *mut u32) = 0;
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ /// Pure Rust implementation of CGameTask_InitAllianceData_Maybe (0x5262D0).
193
+ ///
194
+ /// Convention: usercall(EAX=wrapper), plain RET.
195
+ ///
196
+ /// Initializes alliance bitmask table, per-team scoring flags in DDGame,
197
+ /// and an auxiliary alliance array.
198
+ ///
199
+ /// # Layout:
200
+ /// - wrapper+0x350..+0x384: 13 Γ— u32 alliance bitmasks (one per alliance group)
201
+ /// - DDGame+0x7E70..+0x7E88: 6 Γ— u32 per-team scoring flags
202
+ /// - wrapper+0x3AC..: auxiliary array of non-starting-team alliance IDs (+0x10)
203
+ ///
204
+ /// # GameInfo fields used:
205
+ /// - +0x000: num_teams (u8, first byte)
206
+ /// - +0x44C: alliance_team_count (u8)
207
+ /// - +0x450 + i*3000: alliance_group (i8) for team i
208
+ /// - +0x451 + i*3000: alliance_id (u8) for team i
209
+ /// - +0xD778: game_version (i32)
210
+ /// - +0xD9DC: starting_team_index (i8)
211
+ ///
212
+ /// - wrapper+0x490: first byte used as scoring override value
213
+ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
214
+ // Zero 13 alliance bitmasks at wrapper+0x350..+0x384
215
+ for i in 0..MAX_TEAMS {
216
+ *(wrapper.add(0x350) as *mut u32).add(i) = 0;
217
+ }
218
+
219
+ // Build alliance bitmasks: for each team, set bit (alliance_id & 0x1F)
220
+ // in bitmask[alliance_group]
221
+ let game_info = game_info_from_wrapper(wrapper);
222
+ let num_teams = *game_info as u8 as i32;
223
+
224
+ // Global DAT_007a087c+4 = 0 (static variable, offset 0x7A0880)
225
+ // We skip this β€” it's a global we don't own. The original sets it but
226
+ // it appears unused in the init path.
227
+
228
+ let alliance_team_count = *game_info.add(0x44C) as u8 as i32;
229
+ if num_teams != 0 && alliance_team_count > 0 {
230
+ let mut offset: usize = 0;
231
+ for _ in 0..alliance_team_count {
232
+ let gi = game_info_from_wrapper(wrapper);
233
+ let alliance_group = *(gi.add(0x450 + offset) as *const i8) as i32;
234
+ let alliance_id = *(gi.add(0x451 + offset) as *const u8);
235
+
236
+ if alliance_group >= 0 {
237
+ let bitmask = (wrapper.add(0x350) as *mut u32).add(alliance_group as usize);
238
+ *bitmask |= 1u32 << (alliance_id & 0x1F);
239
+ }
240
+
241
+ offset += TEAM_ENTRY_STRIDE;
242
+ }
243
+ }
244
+
245
+ // Zero 6 per-team scoring flags at DDGame+0x7E70..+0x7E88
246
+ let ddgame = ddgame_from_wrapper(wrapper);
247
+ for i in 0..6u32 {
248
+ *(ddgame.add(0x7E70) as *mut u32).add(i as usize) = 0;
249
+ }
250
+
251
+ // Build scoring flags and auxiliary alliance array
252
+ let game_info = game_info_from_wrapper(wrapper);
253
+ let alliance_team_count = *game_info.add(0x44C) as u8 as i32;
254
+
255
+ // Local tracking: which alliance IDs we've already added to auxiliary array
256
+ let mut seen: [u8; 6] = [0; 6]; // local_8[0..6], indexed by alliance_id
257
+ let mut aux_count: usize = 0; // index into wrapper+0x3AC auxiliary array
258
+
259
+ if alliance_team_count > 0 {
260
+ let mut team_offset: usize = 0;
261
+
262
+ for team_idx in 0..alliance_team_count {
263
+ let gi = game_info_from_wrapper(wrapper);
264
+ let alliance_group = *(gi.add(0x450 + team_offset) as *const i8) as i32;
265
+ let alliance_id = *(gi.add(0x451 + team_offset) as *const u8) as u32;
266
+
267
+ let ddgame = ddgame_from_wrapper(wrapper);
268
+ let scoring_flag = (ddgame as *mut u32).add((0x7E70 / 4) + team_idx as usize);
269
+
270
+ if alliance_group < 0 {
271
+ // No alliance: flag = value from wrapper+0x490 (i8 sign-extended)
272
+ let override_val = *(wrapper.add(0x490) as *const i8) as i32;
273
+ *scoring_flag = override_val as u32;
274
+ } else {
275
+ // Check if this team is allied with the starting team
276
+ let gi = game_info_from_wrapper(wrapper);
277
+ let num_teams_byte = *gi as u8;
278
+ let override_byte = *(wrapper.add(0x490) as *const u8);
279
+
280
+ if num_teams_byte == 0 || override_byte != 0 {
281
+ // No teams or override set: flag = 1
282
+ *scoring_flag = 1;
283
+ } else {
284
+ let game_version = *(gi.add(0xD778) as *const i32);
285
+ let starting_team = *(gi.add(0xD9DC) as *const i8) as i32;
286
+
287
+ let team_bitmask: u32 = if game_version < 0x83 {
288
+ // Old version: use full alliance bitmask
289
+ *((wrapper.add(0x350) as *const u32).add(alliance_group as usize))
290
+ } else {
291
+ // New version: just the single team's bit
292
+ 1u32 << (alliance_id & 0x1F)
293
+ };
294
+
295
+ let starting_bitmask =
296
+ *((wrapper.add(0x350) as *const u32).add(starting_team as usize));
297
+
298
+ if (starting_bitmask & team_bitmask) != 0 {
299
+ // Same alliance as starting team: flag = 1
300
+ *scoring_flag = 1;
301
+ }
302
+ // else: flag stays 0 (already zeroed above)
303
+ }
304
+
305
+ // Build auxiliary array for non-starting-team alliances
306
+ let gi2 = game_info_from_wrapper(wrapper);
307
+ let starting_team2 = *(gi2.add(0xD9DC) as *const i8) as i32;
308
+
309
+ if alliance_group != starting_team2 && seen[alliance_id as usize] == 0 {
310
+ let starting_bitmask2 =
311
+ *((wrapper.add(0x350) as *const u32).add(starting_team2 as usize));
312
+ if (starting_bitmask2 & (1u32 << (alliance_id & 0x1F))) != 0 {
313
+ // Store alliance_id + 0x10 into auxiliary array
314
+ let aux_ptr = (wrapper.add(0x3AC) as *mut u32).add(aux_count);
315
+ *aux_ptr = alliance_id + 0x10;
316
+ aux_count += 1;
317
+ seen[alliance_id as usize] = 1;
318
+ }
319
+ }
320
+ }
321
+
322
+ team_offset += TEAM_ENTRY_STRIDE;
323
+ }
324
+ }
325
+ }
326
+
327
+ /// Pure Rust implementation of DDGame__IsSuperWeapon (0x565960).
328
+ ///
329
+ /// Convention: usercall(EAX=weapon_index) + 1 stack param (param_1: u8), plain RET.
330
+ /// Returns 1 for super weapons, param_1 for 0x3B, 0 otherwise.
331
+ pub unsafe fn is_super_weapon(weapon_index: u32, param_1: u8) -> u8 {
332
+ match weapon_index {
333
+ 10 | 0x13 | 0x1D | 0x1E | 0x1F | 0x24 | 0x29 | 0x2A | 0x2D | 0x2E | 0x31 | 0x32 | 0x33
334
+ | 0x36 | 0x37 | 0x38 | 0x3C | 0x3D => 1,
335
+ 0x3B => param_1,
336
+ _ => 0,
337
+ }
338
+ }
339
+
340
+ /// Pure Rust implementation of DDGame__CheckWeaponAvail (0x53FFC0).
341
+ ///
342
+ /// Convention: fastcall(ECX=ddgame) + unaff_ESI=weapon_index, plain RET. Returns i32.
343
+ ///
344
+ /// Checks whether a weapon (1..0x46) is available given current game state.
345
+ /// `ddgame` is the DDGame pointer directly (not wrapper).
346
+ pub unsafe fn check_weapon_avail(ddgame: *mut u8, weapon_index: u32) -> i32 {
347
+ let game_info = *(ddgame.add(0x24) as *const *mut u8);
348
+ let game_version = *(game_info.add(0xD778) as *const i32);
349
+ let num_teams = *game_info as u8;
350
+
351
+ // Step 1: Special per-weapon disabling rules
352
+ match weapon_index {
353
+ 10 | 0x37 | 0x38 => {
354
+ if *(game_info.add(0xD946) as *const u8) != 0
355
+ && *(game_info.add(0xD9A2) as *const u8) == 0
356
+ {
357
+ return 0;
358
+ }
359
+ }
360
+ 0x36 => {
361
+ if *(game_info.add(0xD94C) as *const u8) != 0 {
362
+ return 0;
363
+ }
364
+ }
365
+ 0x42 => {
366
+ let db08 = *(game_info.add(0xDB08) as *const u32);
367
+ if db08 == 0 {
368
+ if *(ddgame.add(0x1C) as *const u32) == 0 {
369
+ return 0;
370
+ }
371
+ } else if (num_teams as u32) < 2 {
372
+ return 0;
373
+ }
374
+ }
375
+ 0x45 => {
376
+ if game_version > 0xD1 {
377
+ let val = *(game_info.add(0xD932) as *const u16);
378
+ if val > 0x7FFF {
379
+ return 0;
380
+ }
381
+ }
382
+ }
383
+ _ => {}
384
+ }
385
+
386
+ // Step 2: Branch on weapon table entry + DDGame+0x777C
387
+ // DDGame+0x510 is a POINTER to the weapon table (double deref)
388
+ let weapon_table = *(ddgame.add(0x510) as *const *const u8);
389
+ let weapon_entry = *(weapon_table.add(0x10 + (weapon_index as usize) * 0x1D0) as *const i32);
390
+
391
+ if *(ddgame.add(0x777C) as *const i32) == 0 || weapon_entry != 0 {
392
+ // Main path: check super weapon, 7E25, etc.
393
+ let super_result = is_super_weapon(weapon_index, *(ddgame.add(0x7E3F) as *const u8));
394
+ // After is_super_weapon, re-read ddgame (ECX may change in original β€” doesn't matter in Rust)
395
+ if super_result != 0 && *(game_info.add(0xD93C) as *const u8) == 0 {
396
+ // (game_version < 0x2A) - 1: if < 0x2A β†’ 1-1=0, else β†’ 0-1=-1
397
+ return (game_version < 0x2A) as i32 - 1;
398
+ }
399
+
400
+ if *(ddgame.add(0x7E25) as *const u8) == 0 {
401
+ return 1;
402
+ }
403
+
404
+ // Check if weapon_index == (0x19 - (game_info+0xD956 != 0))
405
+ let d956_nonzero = (*(game_info.add(0xD956) as *const u8) != 0) as u32;
406
+ if weapon_index != 0x19 - d956_nonzero {
407
+ return 1;
408
+ }
409
+
410
+ return 0;
411
+ }
412
+
413
+ // Else branch: 777C != 0 AND weapon_entry == 0
414
+ if game_version > 0x29 && *(game_info.add(0xD959) as *const u8) != 0 {
415
+ return -2;
416
+ }
417
+
418
+ 0
419
+ }
420
+
421
+ /// Bridge to DDGame__InitFeatureFlags (0x524700): stdcall(wrapper), RET 0x4.
422
+ unsafe fn wa_init_feature_flags(wrapper: *mut u8) {
423
+ let f: unsafe extern "stdcall" fn(*mut u8) = core::mem::transmute(crate::rebase::rb(
424
+ crate::address::va::DDGAME_INIT_FEATURE_FLAGS,
425
+ ) as usize);
426
+ f(wrapper);
427
+ }
428
+
429
+ /// Pure Rust implementation of CGameTask__InitTurnState (0x528690).
430
+ ///
431
+ /// Convention: usercall(EAX=wrapper), plain RET.
432
+ ///
433
+ /// Initializes turn-related state fields in both the DDGameWrapper and DDGame
434
+ /// structs. Zeroes camera state, timing fields, per-team flags, and calls
435
+ /// DDGame__InitFeatureFlags. Also dispatches a vtable call on the landscape
436
+ /// object.
437
+ pub unsafe fn init_turn_state(wrapper: *mut u8) {
438
+ let ddgame = ddgame_from_wrapper(wrapper);
439
+ let game_info = *(ddgame.add(0x24) as *const *mut u8);
440
+
441
+ // wrapper+0x458 = -1, wrapper+0x450 = 0, wrapper+0x454 = 0
442
+ *(wrapper.add(0x458) as *mut u32) = 0xFFFF_FFFF;
443
+ *(wrapper.add(0x450) as *mut u32) = 0;
444
+ *(wrapper.add(0x454) as *mut u32) = 0;
445
+
446
+ // DDGame+0x72E0/E4 = -1, DDGame+0x72E8 = 0
447
+ *(ddgame.add(0x72E0) as *mut u32) = 0xFFFF_FFFF;
448
+ *(ddgame.add(0x72E4) as *mut u32) = 0xFFFF_FFFF;
449
+ *(ddgame.add(0x72E8) as *mut u32) = 0;
450
+
451
+ // Zero array at DDGame+0x73B0, stride 0x14, while offset < 0x118
452
+ {
453
+ let mut off = 0u32;
454
+ while off < 0x118 {
455
+ *(ddgame.add(0x73B0 + off as usize) as *mut u32) = 0;
456
+ off += 0x14;
457
+ }
458
+ }
459
+
460
+ // More DDGame field zeroing
461
+ *(ddgame.add(0x739C) as *mut u32) = 0;
462
+ *(ddgame.add(0x72F4) as *mut u32) = 0;
463
+ *(ddgame.add(0x72F8) as *mut u32) = 0;
464
+ *(ddgame.add(0x72FC) as *mut u32) = 0;
465
+ *(ddgame.add(0x7300) as *mut u32) = 0;
466
+ *(ddgame.add(0x7304) as *mut u32) = 0;
467
+
468
+ // DDGame+0x72EC = game_info+0xD774 (RNG seed from scheme)
469
+ let rng_seed = *(game_info.add(0xD774) as *const u32);
470
+ *(ddgame.add(0x72EC) as *mut u32) = rng_seed;
471
+ *(ddgame.add(0x72F0) as *mut u32) = rng_seed; // duplicate
472
+
473
+ *(ddgame.add(0x7378) as *mut u32) = 0;
474
+ *(ddgame.add(0x7374) as *mut u32) = 0;
475
+ *(ddgame.add(0x737C) as *mut u32) = 0;
476
+ *(ddgame.add(0x77DC) as *mut u32) = 0;
477
+ *(ddgame.add(0x77E0) as *mut u32) = 0;
478
+ *(ddgame.add(0x7784) as *mut u32) = 0;
479
+
480
+ // DDGame+0x7788 = game_info+0xF362 (byte β†’ u32)
481
+ *(ddgame.add(0x7788) as *mut u32) = *(game_info.add(0xF362) as *const u8) as u32;
482
+ *(ddgame.add(0x778C) as *mut u32) = 0x10000; // Fixed-point 1.0
483
+ *(ddgame.add(0x7790) as *mut u32) = 0;
484
+
485
+ // Camera center: (level_width << 16) / 2, (level_height << 16) / 2
486
+ let level_width = *(ddgame.add(0x77C0) as *const i32);
487
+ let level_height = *(ddgame.add(0x77C4) as *const i32);
488
+ let cx = (level_width << 16) / 2;
489
+ let cy = (level_height << 16) / 2;
490
+ *(ddgame.add(0x7380) as *mut i32) = cx;
491
+ *(ddgame.add(0x7388) as *mut i32) = cx; // duplicate
492
+ *(ddgame.add(0x7384) as *mut i32) = cy;
493
+ *(ddgame.add(0x738C) as *mut i32) = cy; // duplicate
494
+
495
+ *(ddgame.add(0x7D84) as *mut u32) = 0;
496
+ *(ddgame.add(0x7E4C) as *mut u32) = 0;
497
+ *(ddgame.add(0x77D4) as *mut u32) = 0;
498
+ *(ddgame.add(0x77D8) as *mut u32) = 0;
499
+
500
+ // Per-team loop
501
+ let num_teams = *game_info as u8 as i32;
502
+ if num_teams > 0 {
503
+ let mut off = 0x7D88u32;
504
+ for i in 0..num_teams {
505
+ *(ddgame.add(off as usize) as *mut u32) = 0;
506
+ *(ddgame.add(0x7DBC + i as usize) as *mut u8) = 1;
507
+ *(ddgame.add(0x7DC9 + i as usize) as *mut u8) = 1;
508
+ *(ddgame.add(0x7DD6 + i as usize) as *mut u8) = 0;
509
+ *(ddgame.add(0x7DE3 + i as usize) as *mut u8) = 0;
510
+ *(ddgame.add(0x7DF0 + i as usize) as *mut u8) = 0;
511
+ off += 4;
512
+ }
513
+ }
514
+
515
+ *(ddgame.add(0x7E03) as *mut u8) = 0;
516
+ *(ddgame.add(0x7E04) as *mut u8) = 0;
517
+
518
+ // Call DDGame__InitFeatureFlags (600-line feature flag init, bridged)
519
+ wa_init_feature_flags(wrapper);
520
+
521
+ // Post-feature-flag field writes
522
+ *(ddgame.add(0x7E41) as *mut u8) = 0;
523
+ *(ddgame.add(0x7E50) as *mut u32) = 0;
524
+ *(ddgame.add(0x7E54) as *mut u32) = 0;
525
+ *(ddgame.add(0x7E58) as *mut u32) = 0;
526
+ *(ddgame.add(0x7E5C) as *mut u32) = 0;
527
+ *(ddgame.add(0x7E60) as *mut u32) = 0;
528
+ *(ddgame.add(0x7E64) as *mut u32) = 0;
529
+ *(ddgame.add(0x7E68) as *mut u32) = 0;
530
+ *(ddgame.add(0x7E6C) as *mut u32) = 0;
531
+ *(ddgame.add(0x7E88) as *mut u32) = 0;
532
+ *(ddgame.add(0x7E8C) as *mut u32) = 0;
533
+ *(ddgame.add(0x7E90) as *mut u32) = 0;
534
+ *(ddgame.add(0x7E94) as *mut u32) = 0;
535
+ *(ddgame.add(0x7E98) as *mut u32) = 0;
536
+ *(ddgame.add(0x7EA0) as *mut u32) = 0;
537
+ *(ddgame.add(0x7EA4) as *mut u32) = 0;
538
+
539
+ *(ddgame.add(0x8148) as *mut u32) = 1;
540
+
541
+ let ddgame2 = ddgame_from_wrapper(wrapper);
542
+ *(ddgame2.add(0x8158) as *mut u32) = 0;
543
+ *(ddgame2.add(0x815C) as *mut u32) = 0;
544
+ let ddgame3 = ddgame_from_wrapper(wrapper);
545
+ *(ddgame3.add(0x8160) as *mut u32) = 0;
546
+ *(ddgame3.add(0x8164) as *mut u32) = 0;
547
+
548
+ // Vtable dispatch: DDGame+0x20 β†’ landscape object, vtable slot 1,
549
+ // param = game_info+0xD94C (byte)
550
+ let ddgame4 = ddgame_from_wrapper(wrapper);
551
+ let landscape = *(ddgame4.add(0x20) as *const *mut u8);
552
+ if !landscape.is_null() {
553
+ let vtable = *(landscape as *const *const usize);
554
+ let slot1: unsafe extern "thiscall" fn(*mut u8, u32) = core::mem::transmute(*vtable.add(1));
555
+ let game_info2 = *(ddgame4.add(0x24) as *const *const u8);
556
+ let param = *game_info2.add(0xD94C) as u32;
557
+ slot1(landscape, param);
558
+ }
559
+ }
560
+
561
+ /// Pure Rust implementation of CGameTask__InitLandscapeFlags (0x528480).
562
+ ///
563
+ /// Convention: usercall(EAX=wrapper), plain RET.
564
+ ///
565
+ /// Checks game_info+0xD94B and dispatches landscape vtable slot 6 with
566
+ /// appropriate parameters, then updates DDGame+0x777C.
567
+ pub unsafe fn init_landscape_flags(wrapper: *mut u8) {
568
+ let ddgame = ddgame_from_wrapper(wrapper);
569
+ let game_info = *(ddgame.add(0x24) as *const *mut u8);
570
+
571
+ let scheme_flag = *(game_info.add(0xD94B) as *const u8);
572
+
573
+ // Read the 4 DDGame fields used as params
574
+ let field_7318 = *(ddgame.add(0x7318) as *const u32);
575
+ let field_730c = *(ddgame.add(0x730C) as *const u32);
576
+ let field_734c = *(ddgame.add(0x734C) as *const u32);
577
+ let field_7340 = *(ddgame.add(0x7340) as *const u32);
578
+
579
+ // DDGame+0x20 = landscape object
580
+ let landscape = *(ddgame.add(0x20) as *const *mut u8);
581
+ let vtable = *(landscape as *const *const usize);
582
+ // Vtable slot 6 = offset +0x18
583
+ let slot6: unsafe extern "thiscall" fn(*mut u8, u32, u32, u32, u32, u32, u32, u32, u32) =
584
+ core::mem::transmute(*vtable.add(6));
585
+
586
+ if scheme_flag != 0 {
587
+ // Call with (1, 1, 1, 1, field_7318, field_730c, field_734c, field_7340)
588
+ slot6(
589
+ landscape, 1, 1, 1, 1, field_7318, field_730c, field_734c, field_7340,
590
+ );
591
+ *(ddgame.add(0x777C) as *mut u32) = 1;
592
+ } else if *(ddgame.add(0x777C) as *const u32) != 0 {
593
+ // Call with (0, 0, 1, 0, field_7318, field_730c, field_734c, field_7340)
594
+ slot6(
595
+ landscape, 0, 0, 1, 0, field_7318, field_730c, field_734c, field_7340,
596
+ );
597
+ }
598
+ }
crates/openwa-core/src/engine/mod.rs CHANGED
@@ -2,6 +2,7 @@ pub mod ddgame;
2
  pub mod ddgame_wrapper;
3
  pub mod game_info;
4
  pub mod game_session;
 
5
  pub mod game_timer;
6
  pub mod net_wrapper;
7
 
 
2
  pub mod ddgame_wrapper;
3
  pub mod game_info;
4
  pub mod game_session;
5
+ pub mod game_state_init;
6
  pub mod game_timer;
7
  pub mod net_wrapper;
8
 
crates/openwa-wormkit/src/replacements/ddgame_init.rs CHANGED
@@ -18,6 +18,10 @@ use openwa_core::engine::ddgame::{
18
  bit_grid_init, ddgame_init_fields, ddgame_init_render_indices, display_layer_color_init,
19
  gfx_dir_find_entry, gfx_dir_load_dir, gfx_resource_create, DDGame,
20
  };
 
 
 
 
21
  use openwa_core::engine::DDGameWrapper;
22
 
23
  // ─── DDGame__InitFields (0x526120) ──────────────────────────────────────────
@@ -200,7 +204,196 @@ pub fn install() -> Result<(), String> {
200
  va::GFX_DIR_LOAD_DIR,
201
  load_dir_trampoline as *const (),
202
  )?;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  }
204
 
205
  Ok(())
206
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  bit_grid_init, ddgame_init_fields, ddgame_init_render_indices, display_layer_color_init,
19
  gfx_dir_find_entry, gfx_dir_load_dir, gfx_resource_create, DDGame,
20
  };
21
+ use openwa_core::engine::game_state_init::{
22
+ check_weapon_avail, init_alliance_data, init_landscape_flags, init_team_scoring,
23
+ init_turn_state, is_super_weapon, ring_buffer_init, sprite_gfx_table_init,
24
+ };
25
  use openwa_core::engine::DDGameWrapper;
26
 
27
  // ─── DDGame__InitFields (0x526120) ──────────────────────────────────────────
 
204
  va::GFX_DIR_LOAD_DIR,
205
  load_dir_trampoline as *const (),
206
  )?;
207
+
208
+ hook::install(
209
+ "SpriteGfxTable__Init",
210
+ va::SPRITE_GFX_TABLE_INIT,
211
+ sprite_gfx_table_init_trampoline as *const (),
212
+ )?;
213
+
214
+ hook::install(
215
+ "RingBuffer__Init",
216
+ va::RING_BUFFER_INIT,
217
+ ring_buffer_init_trampoline as *const (),
218
+ )?;
219
+
220
+ hook::install(
221
+ "CGameTask__InitTeamScoring",
222
+ va::INIT_TEAM_SCORING,
223
+ init_team_scoring_trampoline as *const (),
224
+ )?;
225
+
226
+ hook::install(
227
+ "CGameTask__InitAllianceData",
228
+ va::INIT_ALLIANCE_DATA,
229
+ init_alliance_data_trampoline as *const (),
230
+ )?;
231
+
232
+ hook::install(
233
+ "CGameTask__InitTurnState",
234
+ va::INIT_TURN_STATE,
235
+ init_turn_state_trampoline as *const (),
236
+ )?;
237
+
238
+ hook::install(
239
+ "DDGame__CheckWeaponAvail",
240
+ va::CHECK_WEAPON_AVAIL,
241
+ check_weapon_avail_trampoline as *const (),
242
+ )?;
243
+
244
+ hook::install(
245
+ "CGameTask__InitLandscapeFlags",
246
+ va::INIT_LANDSCAPE_FLAGS,
247
+ init_landscape_flags_trampoline as *const (),
248
+ )?;
249
+
250
+ hook::install(
251
+ "DDGame__IsSuperWeapon",
252
+ va::IS_SUPER_WEAPON,
253
+ is_super_weapon_trampoline as *const (),
254
+ )?;
255
  }
256
 
257
  Ok(())
258
  }
259
+
260
+ // ─── SpriteGfxTable__Init (0x541620) ────────────────────────────────────────
261
+ // Convention: fastcall(ECX=base, EDX=count), plain RET.
262
+
263
+ unsafe extern "fastcall" fn sprite_gfx_table_init_trampoline(base: u32, count: u32) {
264
+ sprite_gfx_table_init(base as *mut u8, count);
265
+ }
266
+
267
+ // ─── RingBuffer__Init (0x541060) ────────────────────────────────────────────
268
+ // Convention: usercall(EAX=capacity, ESI=struct_ptr), plain RET.
269
+
270
+ extern "cdecl" fn impl_ring_buffer_init(struct_ptr: u32, capacity: u32) {
271
+ unsafe { ring_buffer_init(struct_ptr as *mut u8, capacity) }
272
+ }
273
+
274
+ #[unsafe(naked)]
275
+ unsafe extern "C" fn ring_buffer_init_trampoline() {
276
+ core::arch::naked_asm!(
277
+ "push edx",
278
+ "push eax", // capacity (EAX)
279
+ "push esi", // struct_ptr (ESI)
280
+ "call {impl_fn}",
281
+ "add esp, 8",
282
+ "pop edx",
283
+ "ret",
284
+ impl_fn = sym impl_ring_buffer_init,
285
+ );
286
+ }
287
+
288
+ // ─── CGameTask__InitTeamScoring (0x528510) ──────────────────────────────────
289
+ // Convention: fastcall(ECX=wrapper), plain RET.
290
+
291
+ unsafe extern "fastcall" fn init_team_scoring_trampoline(wrapper: u32, _edx: u32) {
292
+ init_team_scoring(wrapper as *mut u8);
293
+ }
294
+
295
+ // ─── CGameTask__InitAllianceData (0x5262D0) ─────────────────────────────────
296
+ // Convention: usercall(EAX=wrapper), plain RET.
297
+
298
+ extern "cdecl" fn impl_init_alliance_data(wrapper: u32) {
299
+ unsafe { init_alliance_data(wrapper as *mut u8) }
300
+ }
301
+
302
+ #[unsafe(naked)]
303
+ unsafe extern "C" fn init_alliance_data_trampoline() {
304
+ core::arch::naked_asm!(
305
+ "push edx",
306
+ "push eax", // wrapper (EAX)
307
+ "call {impl_fn}",
308
+ "add esp, 4",
309
+ "pop edx",
310
+ "ret",
311
+ impl_fn = sym impl_init_alliance_data,
312
+ );
313
+ }
314
+
315
+ // ─── CGameTask__InitTurnState (0x528690) ────────────────────────────────────
316
+ // Convention: usercall(EAX=wrapper), plain RET.
317
+
318
+ extern "cdecl" fn impl_init_turn_state(wrapper: u32) {
319
+ unsafe { init_turn_state(wrapper as *mut u8) }
320
+ }
321
+
322
+ #[unsafe(naked)]
323
+ unsafe extern "C" fn init_turn_state_trampoline() {
324
+ core::arch::naked_asm!(
325
+ "push edx",
326
+ "push eax", // wrapper (EAX)
327
+ "call {impl_fn}",
328
+ "add esp, 4",
329
+ "pop edx",
330
+ "ret",
331
+ impl_fn = sym impl_init_turn_state,
332
+ );
333
+ }
334
+
335
+ // ─── DDGame__CheckWeaponAvail (0x53FFC0) ────────────────────────────────────
336
+ // Convention: fastcall(ECX=ddgame) + unaff_ESI=weapon_index, plain RET.
337
+ // Returns i32 in EAX.
338
+
339
+ extern "cdecl" fn impl_check_weapon_avail(ddgame: u32, weapon_index: u32) -> i32 {
340
+ unsafe { check_weapon_avail(ddgame as *mut u8, weapon_index) }
341
+ }
342
+
343
+ #[unsafe(naked)]
344
+ unsafe extern "C" fn check_weapon_avail_trampoline() {
345
+ core::arch::naked_asm!(
346
+ "push edx",
347
+ "push esi", // weapon_index (ESI)
348
+ "push ecx", // ddgame (ECX)
349
+ "call {impl_fn}",
350
+ "add esp, 8",
351
+ "pop edx",
352
+ "ret",
353
+ impl_fn = sym impl_check_weapon_avail,
354
+ );
355
+ }
356
+
357
+ // ─── CGameTask__InitLandscapeFlags (0x528480) ───────────────────────────────
358
+ // Convention: usercall(EAX=wrapper), plain RET.
359
+
360
+ extern "cdecl" fn impl_init_landscape_flags(wrapper: u32) {
361
+ unsafe { init_landscape_flags(wrapper as *mut u8) }
362
+ }
363
+
364
+ #[unsafe(naked)]
365
+ unsafe extern "C" fn init_landscape_flags_trampoline() {
366
+ core::arch::naked_asm!(
367
+ "push edx",
368
+ "push eax", // wrapper (EAX)
369
+ "call {impl_fn}",
370
+ "add esp, 4",
371
+ "pop edx",
372
+ "ret",
373
+ impl_fn = sym impl_init_landscape_flags,
374
+ );
375
+ }
376
+
377
+ // ─── DDGame__IsSuperWeapon (0x565960) ───────────────────────────────────────
378
+ // Convention: usercall(EAX=weapon_index) + 1 stack param (param_1: u8), plain RET.
379
+ // Returns u8 in AL.
380
+
381
+ extern "cdecl" fn impl_is_super_weapon(weapon_index: u32, param_1: u32) -> u32 {
382
+ unsafe { is_super_weapon(weapon_index, param_1 as u8) as u32 }
383
+ }
384
+
385
+ #[unsafe(naked)]
386
+ unsafe extern "C" fn is_super_weapon_trampoline() {
387
+ core::arch::naked_asm!(
388
+ "push ecx",
389
+ "push edx",
390
+ "push [esp+12]", // stack param_1 (shifted by push ecx + push edx)
391
+ "push eax", // weapon_index (EAX)
392
+ "call {impl_fn}",
393
+ "add esp, 8",
394
+ "pop edx",
395
+ "pop ecx",
396
+ "ret 0x4", // callee cleans 1 stack param
397
+ impl_fn = sym impl_is_super_weapon,
398
+ );
399
+ }
crates/openwa-wormkit/src/replacements/game_state_hooks.rs ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Passthrough hooks for DDGame__InitGameState sub-functions.
2
+ //!
3
+ //! These hooks intercept stdcall constructors called during InitGameState,
4
+ //! log their parameters, call the original, and return. This validates
5
+ //! calling conventions and prepares for future Rust replacements.
6
+
7
+ use crate::hook;
8
+ use crate::log_line;
9
+ use openwa_core::address::va;
10
+
11
+ // ─── Trampoline storage ─────────────────────────────────────────────────────
12
+
13
+ static mut HUD_PANEL_ORIG: *const () = core::ptr::null();
14
+ static mut INIT_WEAPON_TABLE_ORIG: *const () = core::ptr::null();
15
+ static mut INIT_TEAMS_ORIG: *const () = core::ptr::null();
16
+ static mut TEAM_MANAGER_ORIG: *const () = core::ptr::null();
17
+ static mut TURN_GAME_ORIG: *const () = core::ptr::null();
18
+ static mut GAME_STATE_ORIG: *const () = core::ptr::null();
19
+ static mut DISPLAYGFX_FULL_ORIG: *const () = core::ptr::null();
20
+ static mut TEXTBOX_ORIG: *const () = core::ptr::null();
21
+ static mut WEAPON_PANEL_ORIG: *const () = core::ptr::null();
22
+ static mut BUFFER_OBJ_ORIG: *const () = core::ptr::null();
23
+ static mut STREAM_INIT_ORIG: *const () = core::ptr::null();
24
+ static mut DISPLAY_OBJ_ORIG: *const () = core::ptr::null();
25
+
26
+ // ─── Passthrough hooks ──────────────────────────────────────────────────────
27
+
28
+ // HudPanel__Constructor (0x524070): stdcall(this), RET 0x4
29
+ unsafe extern "stdcall" fn hook_hud_panel(this: u32) -> u32 {
30
+ let _ = log_line(&format!(
31
+ "[InitGameState] HudPanel::Constructor this=0x{this:08X}"
32
+ ));
33
+ let orig: unsafe extern "stdcall" fn(u32) -> u32 = core::mem::transmute(HUD_PANEL_ORIG);
34
+ orig(this)
35
+ }
36
+
37
+ // InitWeaponTable (0x53CAB0): stdcall(wrapper), RET 0x4
38
+ unsafe extern "stdcall" fn hook_init_weapon_table(wrapper: u32) -> u32 {
39
+ let _ = log_line(&format!(
40
+ "[InitGameState] InitWeaponTable wrapper=0x{wrapper:08X}"
41
+ ));
42
+ let orig: unsafe extern "stdcall" fn(u32) -> u32 = core::mem::transmute(INIT_WEAPON_TABLE_ORIG);
43
+ orig(wrapper)
44
+ }
45
+
46
+ // DDGame__InitTeamsFromSetup (0x5220B0): stdcall(team_arena, setup_data), RET 0x8
47
+ unsafe extern "stdcall" fn hook_init_teams(team_arena: u32, setup_data: u32) -> u32 {
48
+ let _ = log_line(&format!(
49
+ "[InitGameState] InitTeamsFromSetup arena=0x{team_arena:08X} setup=0x{setup_data:08X}"
50
+ ));
51
+ let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(INIT_TEAMS_ORIG);
52
+ orig(team_arena, setup_data)
53
+ }
54
+
55
+ // TeamManager__Constructor (0x563D40): stdcall(this, wrapper), RET 0x8
56
+ unsafe extern "stdcall" fn hook_team_manager(this: u32, wrapper: u32) -> u32 {
57
+ let _ = log_line(&format!(
58
+ "[InitGameState] TeamManager::Constructor this=0x{this:08X} wrapper=0x{wrapper:08X}"
59
+ ));
60
+ let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(TEAM_MANAGER_ORIG);
61
+ orig(this, wrapper)
62
+ }
63
+
64
+ // CTaskTurnGame__Constructor (0x55B280): stdcall(this, setup_data), RET 0x8
65
+ // No logging β€” format! clobbers ECX which the post-call code may depend on
66
+ unsafe extern "stdcall" fn hook_turn_game(this: u32, setup_data: u32) -> u32 {
67
+ let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(TURN_GAME_ORIG);
68
+ orig(this, setup_data)
69
+ }
70
+
71
+ // CTaskGameState__Constructor (0x532330): stdcall(this, param), RET 0x8
72
+ // No logging β€” may clobber registers the caller depends on
73
+ unsafe extern "stdcall" fn hook_game_state(this: u32, param: u32) -> u32 {
74
+ let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(GAME_STATE_ORIG);
75
+ orig(this, param)
76
+ }
77
+
78
+ // ─── Group F: Display objects ────────────────────────────────────────────────
79
+
80
+ // DisplayGfx__ConstructFull (0x563FC0): stdcall(5 params), RET 0x14
81
+ unsafe extern "stdcall" fn hook_displaygfx_full(
82
+ p1: u32,
83
+ p2: u32,
84
+ p3: u32,
85
+ p4: u32,
86
+ p5: u32,
87
+ ) -> u32 {
88
+ let orig: unsafe extern "stdcall" fn(u32, u32, u32, u32, u32) -> u32 =
89
+ core::mem::transmute(DISPLAYGFX_FULL_ORIG);
90
+ orig(p1, p2, p3, p4, p5)
91
+ }
92
+
93
+ // DDDisplay__ConstructTextbox (0x4FAF00): stdcall(3 params), RET 0xC
94
+ unsafe extern "stdcall" fn hook_textbox(p1: u32, p2: u32, p3: u32) -> u32 {
95
+ let orig: unsafe extern "stdcall" fn(u32, u32, u32) -> u32 = core::mem::transmute(TEXTBOX_ORIG);
96
+ orig(p1, p2, p3)
97
+ }
98
+
99
+ // DDGame__InitWeaponPanel (0x567770): stdcall(1 param = wrapper), RET 0x4
100
+ unsafe extern "stdcall" fn hook_weapon_panel(wrapper: u32) -> u32 {
101
+ let orig: unsafe extern "stdcall" fn(u32) -> u32 = core::mem::transmute(WEAPON_PANEL_ORIG);
102
+ orig(wrapper)
103
+ }
104
+
105
+ // ─── Group G: Buffer/stream objects ─────────────────────────────────────────
106
+
107
+ // BufferObject__Constructor (0x545FD0): stdcall(3 params), RET 0xC
108
+ unsafe extern "stdcall" fn hook_buffer_obj(p1: u32, p2: u32, p3: u32) -> u32 {
109
+ let orig: unsafe extern "stdcall" fn(u32, u32, u32) -> u32 =
110
+ core::mem::transmute(BUFFER_OBJ_ORIG);
111
+ orig(p1, p2, p3)
112
+ }
113
+
114
+ // GameStateStream__Init (0x4FB490): stdcall(1 param), RET 0x4
115
+ unsafe extern "stdcall" fn hook_stream_init(p1: u32) -> u32 {
116
+ let orig: unsafe extern "stdcall" fn(u32) -> u32 = core::mem::transmute(STREAM_INIT_ORIG);
117
+ orig(p1)
118
+ }
119
+
120
+ // DisplayObject__Constructor (0x540440): stdcall(2 params), RET 0x8
121
+ unsafe extern "stdcall" fn hook_display_obj(p1: u32, p2: u32) -> u32 {
122
+ let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(DISPLAY_OBJ_ORIG);
123
+ orig(p1, p2)
124
+ }
125
+
126
+ // ─── Hook installation ──────────────────────────────────────────────────────
127
+
128
+ pub fn install() -> Result<(), String> {
129
+ unsafe {
130
+ HUD_PANEL_ORIG = hook::install(
131
+ "HudPanel__Constructor",
132
+ va::HUD_PANEL_CONSTRUCTOR,
133
+ hook_hud_panel as *const (),
134
+ )? as *const ();
135
+
136
+ INIT_WEAPON_TABLE_ORIG = hook::install(
137
+ "InitWeaponTable",
138
+ va::INIT_WEAPON_TABLE,
139
+ hook_init_weapon_table as *const (),
140
+ )? as *const ();
141
+
142
+ INIT_TEAMS_ORIG = hook::install(
143
+ "DDGame__InitTeamsFromSetup",
144
+ va::INIT_TEAMS_FROM_SETUP,
145
+ hook_init_teams as *const (),
146
+ )? as *const ();
147
+
148
+ TEAM_MANAGER_ORIG = hook::install(
149
+ "TeamManager__Constructor",
150
+ va::TEAM_MANAGER_CONSTRUCTOR,
151
+ hook_team_manager as *const (),
152
+ )? as *const ();
153
+
154
+ TURN_GAME_ORIG = hook::install(
155
+ "CTaskTurnGame__Constructor",
156
+ va::TURN_GAME_CONSTRUCTOR,
157
+ hook_turn_game as *const (),
158
+ )? as *const ();
159
+ GAME_STATE_ORIG = hook::install(
160
+ "CTaskGameState__Constructor",
161
+ va::GAME_STATE_CONSTRUCTOR,
162
+ hook_game_state as *const (),
163
+ )? as *const ();
164
+
165
+ // Group F: Display objects
166
+ DISPLAYGFX_FULL_ORIG = hook::install(
167
+ "DisplayGfx__ConstructFull",
168
+ va::DISPLAYGFX_CONSTRUCT_FULL,
169
+ hook_displaygfx_full as *const (),
170
+ )? as *const ();
171
+
172
+ TEXTBOX_ORIG = hook::install(
173
+ "DDDisplay__ConstructTextbox",
174
+ va::CONSTRUCT_TEXTBOX,
175
+ hook_textbox as *const (),
176
+ )? as *const ();
177
+
178
+ WEAPON_PANEL_ORIG = hook::install(
179
+ "DDGame__InitWeaponPanel",
180
+ va::FUN_567770,
181
+ hook_weapon_panel as *const (),
182
+ )? as *const ();
183
+
184
+ // Group G: Buffer/stream objects
185
+ BUFFER_OBJ_ORIG = hook::install(
186
+ "BufferObject__Constructor",
187
+ va::BUFFER_OBJECT_CONSTRUCTOR,
188
+ hook_buffer_obj as *const (),
189
+ )? as *const ();
190
+
191
+ STREAM_INIT_ORIG = hook::install(
192
+ "GameStateStream__Init",
193
+ va::GAME_STATE_STREAM_INIT,
194
+ hook_stream_init as *const (),
195
+ )? as *const ();
196
+
197
+ DISPLAY_OBJ_ORIG = hook::install(
198
+ "DisplayObject__Constructor",
199
+ va::DISPLAY_OBJECT_CONSTRUCTOR,
200
+ hook_display_obj as *const (),
201
+ )? as *const ();
202
+ }
203
+
204
+ Ok(())
205
+ }
crates/openwa-wormkit/src/replacements/mod.rs CHANGED
@@ -3,6 +3,7 @@ mod ddgame_init;
3
  mod display;
4
  mod frontend;
5
  mod game_session;
 
6
  mod hardware_init;
7
  mod headless;
8
  pub(crate) mod input;
@@ -28,5 +29,6 @@ pub fn install_all() -> Result<(), String> {
28
  speech::install()?;
29
  input::install()?;
30
  ddgame_init::install()?;
 
31
  Ok(())
32
  }
 
3
  mod display;
4
  mod frontend;
5
  mod game_session;
6
+ mod game_state_hooks;
7
  mod hardware_init;
8
  mod headless;
9
  pub(crate) mod input;
 
29
  speech::install()?;
30
  input::install()?;
31
  ddgame_init::install()?;
32
+ game_state_hooks::install()?;
33
  Ok(())
34
  }