paavohtl Claude Opus 4.6 (1M context) commited on
Commit
314b694
Β·
1 Parent(s): cbcd5f7

refactor: replace raw pointer arithmetic with typed field access in game_state_init

Browse files

Eliminate ~120 raw pointer arithmetic expressions (`ddgame_raw.add(offset)`,
`w.add(offset)`) in favor of named struct field access through typed pointers.

- Split DDGameWrapper._unknown_0f4 into 7 named team scoring arrays
- Split DDGameWrapper._unknown_2b4 to expose team_score_array_6
- Retype DDGameWrapper._zeroed_054 to team_task_ptrs: [*mut u8; 13]
- Split DDGame._unknown_77ac to add _field_77b0
- Change all function signatures from *mut u8 to *mut DDGameWrapper/*mut DDGame
- Replace wa_malloc + write_bytes with wa_malloc_zeroed (~12 sites)
- Propagate typed pointers (DisplayGfx, DSSound, PCLandscape) to vtable calls
- Update DLL trampoline shims to cast to typed pointers
- Remove unused helper parameters (_ddgame_raw, _wrapper)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -351,8 +351,12 @@ pub struct DDGame {
351
  pub level_bound_min_y: Fixed,
352
  /// 0x77A8: Level bound max Y (Fixed16.16).
353
  pub level_bound_max_y: Fixed,
354
- /// 0x77AC-0x77B7: Unknown
355
- pub _unknown_77ac: [u8; 0x77B8 - 0x77AC],
 
 
 
 
356
  /// 0x77B8: Level width for 3D sound distance computation (pixels, not fixed-point).
357
  /// Read by ComputeDistanceParams, shifted left 16 before passing to Distance3D_Attenuation.
358
  pub level_width_sound: i32,
 
351
  pub level_bound_min_y: Fixed,
352
  /// 0x77A8: Level bound max Y (Fixed16.16).
353
  pub level_bound_max_y: Fixed,
354
+ /// 0x77AC-0x77AF: Unknown
355
+ pub _unknown_77ac: u32,
356
+ /// 0x77B0: Unknown (zeroed by InitGameState).
357
+ pub _field_77b0: u32,
358
+ /// 0x77B4: Unknown
359
+ pub _unknown_77b4: u32,
360
  /// 0x77B8: Level width for 3D sound distance computation (pixels, not fixed-point).
361
  /// Read by ComputeDistanceParams, shifted left 16 before passing to Distance3D_Attenuation.
362
  pub level_width_sound: i32,
crates/openwa-core/src/engine/ddgame_wrapper.rs CHANGED
@@ -74,10 +74,10 @@ pub struct DDGameWrapper {
74
  /// 0x050: RingBuffer C (capacity 0x1000)
75
  pub ring_buffer_c: *mut u8,
76
 
77
- // ===== 0x054-0x087: Timer/counter block =====
78
- /// 0x054-0x087: Zeroed by InitGameState. Team task pointers and counters.
79
- /// Accessed via pointer arithmetic in game_state_init.rs (0x054 = team CTask ptrs).
80
- pub _zeroed_054: [u32; 13],
81
 
82
  // ===== 0x088-0x090: PaletteContext pointers =====
83
  /// 0x088: PaletteContext A (allocated 0x72C bytes)
@@ -103,12 +103,21 @@ pub struct DDGameWrapper {
103
  /// 0x0F0: Init flag (set to 1 by InitGameState early)
104
  pub init_flag: u32,
105
 
106
- // ===== 0x0F4-0x25F: Team arrays and game configuration =====
107
- /// 0x0F4-0x25F: Large region containing team data arrays.
108
- /// Key sub-regions accessed via pointer arithmetic:
109
- /// 0x128: Team scoring base array (13 elements Γ— 7 parallel arrays)
110
- /// 0x22C: Team activity flags
111
- pub _unknown_0f4: [u8; 0x260 - 0x0F4],
 
 
 
 
 
 
 
 
 
112
 
113
  // ===== 0x260-0x267: Game config =====
114
  /// 0x260: Health display precision (initialized to 500)
@@ -142,8 +151,12 @@ pub struct DDGameWrapper {
142
  pub team_count_config: i32,
143
  /// 0x2B0: Maximum team render index (0xC or 0x10)
144
  pub max_team_render_index: i32,
145
- /// 0x2B4-0x34F: Unknown (includes team arrays accessed by init_alliance_data at 0x350)
146
- pub _unknown_2b4: [u8; 0x350 - 0x2B4],
 
 
 
 
147
  /// 0x350-0x383: Alliance bitmask arrays (13 Γ— u32, accessed by init_alliance_data)
148
  pub _alliance_bitmasks: [u32; 13],
149
  /// 0x384: Screen offset (computed from screen height and team count)
 
74
  /// 0x050: RingBuffer C (capacity 0x1000)
75
  pub ring_buffer_c: *mut u8,
76
 
77
+ // ===== 0x054-0x087: Per-team CTask pointers =====
78
+ /// 0x054: Per-team CTask pointers (13 slots). Zeroed by InitGameState.
79
+ /// Sub-fields +0x08..+0x18 cleared if non-null during InitTeamScoring.
80
+ pub team_task_ptrs: [*mut u8; 13],
81
 
82
  // ===== 0x088-0x090: PaletteContext pointers =====
83
  /// 0x088: PaletteContext A (allocated 0x72C bytes)
 
103
  /// 0x0F0: Init flag (set to 1 by InitGameState early)
104
  pub init_flag: u32,
105
 
106
+ // ===== 0x0F4-0x25F: Team scoring arrays (7 parallel arrays of 13 u32s) =====
107
+ /// 0x0F4: Team score array 0 β€” zeroed by InitTeamScoring.
108
+ pub team_score_array_0: [u32; 13],
109
+ /// 0x128: Starting team marker β€” 1 for starting team, 0 for others.
110
+ pub team_starting_marker: [u32; 13],
111
+ /// 0x15C: Team scoring A β€” initialized to scoring_param_a Γ— 50.
112
+ pub team_scoring_a: [u32; 13],
113
+ /// 0x190: Team score array 3 β€” zeroed by InitTeamScoring.
114
+ pub team_score_array_3: [u32; 13],
115
+ /// 0x1C4: Team scoring B β€” initialized to scoring_param_b Γ— 50.
116
+ pub team_scoring_b: [u32; 13],
117
+ /// 0x1F8: Team scoring C β€” initialized to scoring_param_b Γ— 50.
118
+ pub team_scoring_c: [u32; 13],
119
+ /// 0x22C: Team activity flags β€” -1 (normal), -2 (training), 0 (inactive), 1 (starting).
120
+ pub team_activity_flags: [u32; 13],
121
 
122
  // ===== 0x260-0x267: Game config =====
123
  /// 0x260: Health display precision (initialized to 500)
 
151
  pub team_count_config: i32,
152
  /// 0x2B0: Maximum team render index (0xC or 0x10)
153
  pub max_team_render_index: i32,
154
+ /// 0x2B4-0x2BB: Unknown.
155
+ pub _unknown_2b4: [u8; 8],
156
+ /// 0x2BC: Team score array 6 β€” set to 1 by InitTeamScoring.
157
+ pub team_score_array_6: [u32; 13],
158
+ /// 0x2F0-0x34F: Unknown.
159
+ pub _unknown_2f0: [u8; 0x350 - 0x2F0],
160
  /// 0x350-0x383: Alliance bitmask arrays (13 Γ— u32, accessed by init_alliance_data)
161
  pub _alliance_bitmasks: [u32; 13],
162
  /// 0x384: Screen offset (computed from screen height and team count)
crates/openwa-core/src/engine/game_state_init.rs CHANGED
@@ -4,11 +4,13 @@
4
  //! Each sub-function is hooked individually so it works regardless of whether
5
  //! InitGameState itself is Rust or the original WA code.
6
 
 
7
  use crate::engine::ddgame::DDGame;
8
  use crate::engine::ddgame_wrapper::DDGameWrapper;
9
  use crate::engine::game_info::GameInfo;
10
  use crate::fixed::Fixed;
11
- use crate::wa_alloc::wa_malloc;
 
12
 
13
  /// Pure Rust implementation of SpriteGfxTable__Init (0x541620).
14
  ///
@@ -40,11 +42,9 @@ pub unsafe fn sprite_gfx_table_init(base: *mut u8, count: u32) {
40
  /// - `[1]`: capacity
41
  /// - `[2]`-`[6]`: zeroed (head, tail, count, etc.)
42
  pub unsafe fn ring_buffer_init(struct_ptr: *mut u8, capacity: u32) {
 
43
  let alloc_size = ((capacity + 3) & !3) + 0x20;
44
- let data = wa_malloc(alloc_size);
45
- if !data.is_null() {
46
- core::ptr::write_bytes(data, 0, capacity as usize);
47
- }
48
 
49
  let s = struct_ptr as *mut u32;
50
  *s.add(0) = data as u32; // data pointer
@@ -63,18 +63,15 @@ const MAX_TEAMS: usize = 13;
63
  const TEAM_ENTRY_STRIDE: usize = 3000;
64
 
65
  /// Helper: read the GameInfo pointer from a DDGameWrapper pointer.
66
- ///
67
- /// Chain: wrapper+0x488 β†’ DDGame, DDGame+0x24 β†’ GameInfo.
68
  #[inline]
69
- unsafe fn game_info_from_wrapper(wrapper: *mut u8) -> *mut GameInfo {
70
- let ddgame = *(wrapper.add(0x488) as *const *mut DDGame);
71
- (*ddgame).game_info
72
  }
73
 
74
  /// Helper: read the DDGame pointer from a DDGameWrapper pointer.
75
  #[inline]
76
- unsafe fn ddgame_from_wrapper(wrapper: *mut u8) -> *mut DDGame {
77
- *(wrapper.add(0x488) as *const *mut DDGame)
78
  }
79
 
80
  /// Pure Rust implementation of CGameTask_InitTeamScoring_Maybe (0x528510).
@@ -98,89 +95,68 @@ unsafe fn ddgame_from_wrapper(wrapper: *mut u8) -> *mut DDGame {
98
  /// - +0x128[starting_team]: set to 1 (starting team marker in array_1)
99
  /// - +0x22C[0..team_count]: team activity flags (-1 normal, -2 training)
100
  /// - +0x054[0..team_count]: CTask pointers β€” sub-fields zeroed if non-null
101
- pub unsafe fn init_team_scoring(wrapper: *mut u8) {
102
  let game_info = game_info_from_wrapper(wrapper);
103
 
104
  let scoring_param_a = (*game_info).scoring_param_a as u32;
105
  let scoring_param_b = (*game_info).scoring_param_b as u32;
106
- let value_a = scoring_param_a * 50; // uVar1 * 0x32
107
- let value_b = scoring_param_b * 50; // iVar4
108
 
109
  // Initialize 7 parallel arrays (13 elements each).
110
- // puVar3 starts at wrapper+0x128, indexed relative to that.
111
- let base = wrapper.add(0x128) as *mut u32;
112
  for i in 0..MAX_TEAMS {
113
- let p = base.add(i);
114
- *p.sub(0xd) = 0; // +0x0F4[i]: array_0 β€” zeroed
115
- *p = 0; // +0x128[i]: array_1 β€” zeroed
116
- *p.add(0xd) = value_a; // +0x15C[i]: array_2 β€” scoring_param_a * 50
117
- *p.add(0x27) = value_b; // +0x1C4[i]: array_4 β€” scoring_param_b * 50
118
- *p.add(0x34) = value_b; // +0x1F8[i]: array_5 β€” scoring_param_b * 50
119
- *p.add(0x1a) = 0; // +0x190[i]: array_3 β€” zeroed
120
- *p.add(99) = 1; // +0x2BC[i]: array_6 β€” set to 1
121
  }
122
 
123
- // Mark the starting team in array_1: wrapper+0x128[starting_team] = 1
124
- let starting_team = (*game_info).starting_team_index as i32;
125
- *(base.add(starting_team as usize)) = 1;
126
 
127
- // GameInfo+0xD9DD: mode flag (i8, negative = training/replay mode)
128
  let mode_flag = (*game_info).game_mode_flag;
129
-
130
- // GameInfo+0x0000: first byte = total team count (num_teams, u8)
131
  let num_teams = (*game_info).num_teams as i32;
132
 
133
- // Team activity flags at wrapper+0x22C (one u32 per team)
134
- let flags_base = wrapper.add(0x22C) as *mut u32;
135
-
136
  if mode_flag < 0 {
137
  // Training/replay mode: set all flags to -2 (0xFFFFFFFE)
138
- if num_teams > 0 {
139
- for i in 0..num_teams as usize {
140
- *flags_base.add(i) = 0xFFFF_FFFE;
141
- }
142
  }
143
 
144
  // For each active team slot in GameInfo, clear the flag to 0
145
- // GameInfo+0x44C: team count for alliance data (u8)
146
  let game_info2 = game_info_from_wrapper(wrapper);
147
  let alliance_team_count = (*game_info2).speech_team_count as i32;
148
  if alliance_team_count > 0 {
149
  let mut offset: usize = 0;
150
  for _ in 0..alliance_team_count {
151
- // GameInfo+0x450 + team_index*3000: alliance group (i8)
152
  let alliance_group =
153
  *((game_info2 as *const u8).add(0x450 + offset) as *const i8) as i32;
154
  if alliance_group >= 0 {
155
- *flags_base.add(alliance_group as usize) = 0;
156
  }
157
- // Re-read game_info in case pointer chain changed (matches original)
158
- let gi = game_info_from_wrapper(wrapper);
159
- let _ = gi; // original re-reads but we already have the value
160
  offset += TEAM_ENTRY_STRIDE;
161
  }
162
  }
163
  } else {
164
  // Normal mode: set all flags to -1 (0xFFFFFFFF)
165
- if num_teams > 0 {
166
- for i in 0..num_teams as usize {
167
- *flags_base.add(i) = 0xFFFF_FFFF;
168
- }
169
  }
170
 
171
  // Set starting team's flag to 1
172
- // GameInfo+0xD9DD: starting team index for flags (i8, reuse game_mode_flag which is the same field)
173
  let starting_flag_team = (*game_info_from_wrapper(wrapper)).game_mode_flag as i32;
174
- *flags_base.add(starting_flag_team as usize) = 1;
175
  }
176
 
177
  // Zero CTask sub-object fields for each team's task pointer
178
- // wrapper+0x54[i] = CTask pointer, clear offsets +0x08..+0x18 (5 DWORDs)
179
  let num_teams2 = (*game_info_from_wrapper(wrapper)).num_teams as i32;
180
  if num_teams2 > 0 {
181
- let task_ptrs = wrapper.add(0x54) as *mut *mut u8;
182
  for i in 0..num_teams2 as usize {
183
- let task = *task_ptrs.add(i);
184
  if !task.is_null() {
185
  *(task.add(0x18) as *mut u32) = 0;
186
  *(task.add(0x14) as *mut u32) = 0;
@@ -213,21 +189,15 @@ pub unsafe fn init_team_scoring(wrapper: *mut u8) {
213
  /// - starting_team_index
214
  ///
215
  /// - wrapper+0x490: first byte used as scoring override value
216
- pub unsafe fn init_alliance_data(wrapper: *mut u8) {
217
- // Zero 13 alliance bitmasks at wrapper+0x350..+0x384
218
- for i in 0..MAX_TEAMS {
219
- *(wrapper.add(0x350) as *mut u32).add(i) = 0;
220
- }
221
 
222
  // Build alliance bitmasks: for each team, set bit (alliance_id & 0x1F)
223
  // in bitmask[alliance_group]
224
  let game_info = game_info_from_wrapper(wrapper);
225
  let num_teams = (*game_info).num_teams as i32;
226
 
227
- // Global DAT_007a087c+4 = 0 (static variable, offset 0x7A0880)
228
- // We skip this β€” it's a global we don't own. The original sets it but
229
- // it appears unused in the init path.
230
-
231
  let alliance_team_count = (*game_info).speech_team_count as i32;
232
  if num_teams != 0 && alliance_team_count > 0 {
233
  let mut offset: usize = 0;
@@ -237,8 +207,8 @@ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
237
  let alliance_id = *((gi as *const u8).add(0x451 + offset));
238
 
239
  if alliance_group >= 0 {
240
- let bitmask = (wrapper.add(0x350) as *mut u32).add(alliance_group as usize);
241
- *bitmask |= 1u32 << (alliance_id & 0x1F);
242
  }
243
 
244
  offset += TEAM_ENTRY_STRIDE;
@@ -254,8 +224,8 @@ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
254
  let alliance_team_count = (*game_info).speech_team_count as i32;
255
 
256
  // Local tracking: which alliance IDs we've already added to auxiliary array
257
- let mut seen: [u8; 6] = [0; 6]; // local_8[0..6], indexed by alliance_id
258
- let mut aux_count: usize = 0; // index into wrapper+0x3AC auxiliary array
259
 
260
  if alliance_team_count > 0 {
261
  let mut team_offset: usize = 0;
@@ -270,38 +240,32 @@ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
270
  core::ptr::addr_of_mut!((*ddgame).team_scoring_flags[team_idx as usize]);
271
 
272
  if alliance_group < 0 {
273
- // No alliance: flag = value from wrapper+0x490 (i8 sign-extended)
274
- let override_val = *(wrapper.add(0x490) as *const i8) as i32;
275
  *scoring_flag = override_val as u32;
276
  } else {
277
  // Check if this team is allied with the starting team
278
  let gi = game_info_from_wrapper(wrapper);
279
  let num_teams_byte = (*gi).num_teams;
280
- let override_byte = *(wrapper.add(0x490) as *const u8);
281
 
282
  if num_teams_byte == 0 || override_byte != 0 {
283
- // No teams or override set: flag = 1
284
  *scoring_flag = 1;
285
  } else {
286
  let game_version = (*gi).game_version;
287
  let starting_team = (*gi).starting_team_index as i32;
288
 
289
  let team_bitmask: u32 = if game_version < 0x83 {
290
- // Old version: use full alliance bitmask
291
- *((wrapper.add(0x350) as *const u32).add(alliance_group as usize))
292
  } else {
293
- // New version: just the single team's bit
294
  1u32 << (alliance_id & 0x1F)
295
  };
296
 
297
- let starting_bitmask =
298
- *((wrapper.add(0x350) as *const u32).add(starting_team as usize));
299
 
300
  if (starting_bitmask & team_bitmask) != 0 {
301
- // Same alliance as starting team: flag = 1
302
  *scoring_flag = 1;
303
  }
304
- // else: flag stays 0 (already zeroed above)
305
  }
306
 
307
  // Build auxiliary array for non-starting-team alliances
@@ -309,12 +273,9 @@ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
309
  let starting_team2 = (*gi2).starting_team_index as i32;
310
 
311
  if alliance_group != starting_team2 && seen[alliance_id as usize] == 0 {
312
- let starting_bitmask2 =
313
- *((wrapper.add(0x350) as *const u32).add(starting_team2 as usize));
314
  if (starting_bitmask2 & (1u32 << (alliance_id & 0x1F))) != 0 {
315
- // Store alliance_id + 0x10 into auxiliary array
316
- let aux_ptr = (wrapper.add(0x3AC) as *mut u32).add(aux_count);
317
- *aux_ptr = alliance_id + 0x10;
318
  aux_count += 1;
319
  seen[alliance_id as usize] = 1;
320
  }
@@ -418,10 +379,10 @@ pub unsafe fn check_weapon_avail(ddgame: *mut DDGame, weapon_index: u32) -> i32
418
  }
419
 
420
  /// Bridge to DDGame__InitFeatureFlags (0x524700): stdcall(wrapper), RET 0x4.
421
- unsafe fn wa_init_feature_flags(wrapper: *mut u8) {
422
- let f: unsafe extern "stdcall" fn(*mut u8) = core::mem::transmute(crate::rebase::rb(
423
- crate::address::va::DDGAME_INIT_FEATURE_FLAGS,
424
- ) as usize);
425
  f(wrapper);
426
  }
427
 
@@ -433,14 +394,13 @@ unsafe fn wa_init_feature_flags(wrapper: *mut u8) {
433
  /// structs. Zeroes camera state, timing fields, per-team flags, and calls
434
  /// DDGame__InitFeatureFlags. Also dispatches a vtable call on the landscape
435
  /// object.
436
- pub unsafe fn init_turn_state(wrapper: *mut u8) {
437
  let ddgame = ddgame_from_wrapper(wrapper);
438
  let game_info = (*ddgame).game_info;
439
 
440
- // wrapper+0x458 = -1, wrapper+0x450 = 0, wrapper+0x454 = 0
441
- *(wrapper.add(0x458) as *mut u32) = 0xFFFF_FFFF;
442
- *(wrapper.add(0x450) as *mut u32) = 0;
443
- *(wrapper.add(0x454) as *mut u32) = 0;
444
 
445
  // DDGame+0x72E0/E4 = -1, DDGame+0x72E8 = 0
446
  (*ddgame)._unknown_72e0 = 0xFFFF_FFFF;
@@ -550,7 +510,7 @@ pub unsafe fn init_turn_state(wrapper: *mut u8) {
550
  ///
551
  /// Checks game_info.landscape_scheme_flag and dispatches landscape vtable slot 6 with
552
  /// appropriate parameters, then updates DDGame.level_width_raw.
553
- pub unsafe fn init_landscape_flags(wrapper: *mut u8) {
554
  let ddgame = ddgame_from_wrapper(wrapper);
555
  let game_info = (*ddgame).game_info;
556
 
@@ -597,11 +557,9 @@ pub unsafe fn init_landscape_flags(wrapper: *mut u8) {
597
  pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
598
  use crate::address::va;
599
  use crate::rebase::rb;
600
- use crate::wa_alloc::wa_malloc;
601
 
602
- let w = wrapper as *mut u8;
603
  let ddgame = (*wrapper).ddgame;
604
- let ddgame_raw = ddgame as *mut u8;
605
 
606
  // ===== Copy replay mode flags from GameInfo =====
607
  let game_info = (*ddgame).game_info;
@@ -609,17 +567,16 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
609
  (*wrapper).replay_flag_b = ((*game_info).invisibility_mode >> 8) as u8;
610
 
611
  // ===== SpriteGfxTable__Init =====
612
- // ECX = DDGame+0x600, EDX = version-dependent count
613
  {
614
  let game_version = (*game_info).game_version;
615
  let count: u32 = if game_version >= 0x3C { 0x400 } else { 0x100 };
616
- sprite_gfx_table_init(ddgame_raw.add(0x600) as *mut u8, count);
 
617
  }
618
 
619
  // ===== Allocate HudPanel (0x940 bytes) =====
620
  {
621
- let mem = wa_malloc(0x940);
622
- core::ptr::write_bytes(mem, 0, 0x920);
623
  let result = if mem.is_null() {
624
  core::ptr::null_mut()
625
  } else {
@@ -627,14 +584,13 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
627
  core::mem::transmute(rb(va::HUD_PANEL_CONSTRUCTOR) as usize);
628
  ctor(mem)
629
  };
630
- *(ddgame_raw.add(0x534) as *mut *mut u8) = result;
631
  }
632
 
633
  // ===== Allocate weapon table buffer (0x80D0 bytes) =====
634
  {
635
- let mem = wa_malloc(0x80D0);
636
- core::ptr::write_bytes(mem, 0, 0x80B0);
637
- *(ddgame_raw.add(0x510) as *mut *mut u8) = mem;
638
  }
639
 
640
  // ===== Allocate unknown 0x2C object =====
@@ -648,64 +604,60 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
648
  *(mem.add(0x20) as *mut u32) = 0;
649
  *(mem.add(0x24) as *mut u32) = 0;
650
  }
651
- *(ddgame_raw.add(0x51C) as *mut *mut u8) = mem;
652
  }
653
 
654
  // ===== Allocate RenderQueue (0x12028 bytes) =====
655
  {
656
- let mem = wa_malloc(0x12028) as *mut u32;
657
- core::ptr::write_bytes(mem as *mut u8, 0, 0x12008);
658
  if !mem.is_null() {
659
  *mem.add(0x4001) = 0; // mem[0x10004] = 0
660
  *mem = 0x10000; // mem[0] = 0x10000
661
  }
662
- *(ddgame_raw.add(0x524) as *mut *mut u32) = mem;
663
  }
664
 
665
  // ===== Allocate GameStateStream (0x264 bytes) =====
666
  {
667
- let mem = wa_malloc(0x264) as *mut u32;
668
- core::ptr::write_bytes(mem as *mut u8, 0, 0x244);
669
  if !mem.is_null() {
670
  *mem = rb(0x664194); // GameStateStream vtable
671
  let init: unsafe extern "stdcall" fn(*mut u32) =
672
  core::mem::transmute(rb(va::GAME_STATE_STREAM_INIT) as usize);
673
  init(mem.add(1)); // Init sub-object at offset +4
674
  *mem.add(1) = 0;
675
- *mem.add(0x90) = ddgame as u32; // puVar5[0x90] = uVar3 (DDGame ptr)
676
  *mem.add(0x8B) = 0;
677
  *mem.add(0x8C) = 0;
678
  }
679
- *(ddgame_raw.add(0x528) as *mut *mut u32) = mem;
680
  }
681
 
682
  // ===== Allocate unknown 0x4A74 object =====
683
  {
684
- let mem = wa_malloc(0x4A74);
685
- core::ptr::write_bytes(mem, 0, 0x4A54);
686
  if !mem.is_null() {
687
  *(mem.add(0x4A50) as *mut u32) = ddgame as u32;
688
  }
689
- *(ddgame_raw.add(0x52C) as *mut *mut u8) = mem;
690
  }
691
 
692
- // ===== Virtual call: display->vtable[1](ddgame+0x77B8, ddgame+0x77BC) =====
693
- // Ghidra: (**(code **)(**(int **)(iVar4 + 4) + 4))(iVar4 + 0x77b8, iVar4 + 0x77bc)
694
- // DDGame+4 = display ptr, deref for vtable, slot 1
695
  {
696
- let display_ptr = *(ddgame_raw.add(4) as *const *const u8);
697
- let vtable = *(display_ptr as *const *const u32);
698
- let vmethod: unsafe extern "thiscall" fn(*const u8, u32, u32) =
699
  core::mem::transmute(*vtable.add(1));
700
- vmethod(display_ptr, ddgame as u32 + 0x77B8, ddgame as u32 + 0x77BC);
 
 
 
 
701
  }
702
- *(ddgame_raw.add(0x77B0) as *mut u32) = 0;
703
 
704
  // ===== Allocate BufferObject (main_buffer at wrapper+0x0C) =====
705
- {
706
- let buf = allocate_buffer_object(ddgame_raw, game_info);
707
- (*wrapper).main_buffer = buf;
708
- }
709
 
710
  // ===== Allocate RingBuffer A (wrapper+0x3C, capacity 0x2000) =====
711
  (*wrapper).ring_buffer_a = allocate_ring_buffer_raw(0x3C, 0x2000);
@@ -721,12 +673,12 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
721
 
722
  // ===== wrapper+0x44: network ring buffer (conditional) =====
723
  (*wrapper).network_ring_buffer = core::ptr::null_mut();
724
- if *(ddgame_raw.add(0x1C) as *const u32) != 0 {
725
- (*wrapper).network_ring_buffer = allocate_ring_buffer_init(wrapper as *mut u8);
726
  }
727
 
728
  // ===== Allocate state buffer (wrapper+0x48) =====
729
- (*wrapper).state_buffer = allocate_buffer_object(ddgame_raw, game_info);
730
 
731
  // ===== Allocate statistics object (0xB94 bytes, wrapper+0x4C) =====
732
  {
@@ -752,15 +704,13 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
752
  (*wrapper)._field_498 = 0;
753
  (*wrapper)._field_493 = 0;
754
 
755
- // Zero 0x54-0x84 (13 fields)
756
- for i in 0..13u32 {
757
- *(w.add(0x54 + i as usize * 4) as *mut u32) = 0;
758
- }
759
 
760
  // ===== Allocate 3 PaletteContexts (0x72C bytes each) =====
761
- (*wrapper).palette_ctx_a = allocate_palette_context(ddgame_raw);
762
- (*wrapper).palette_ctx_b = allocate_palette_context(ddgame_raw);
763
- (*wrapper).palette_ctx_c = allocate_palette_context(ddgame_raw);
764
 
765
  // ===== Select vector normalize function based on game version =====
766
  {
@@ -773,75 +723,55 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
773
  }
774
 
775
  // ===== Initialize sentinel fields =====
776
- *(w.add(0x468) as *mut i32) = -1;
777
- *(w.add(0x46C) as *mut i32) = -1;
778
- *(w.add(0x470) as *mut i32) = -1;
779
- *(w.add(0x484) as *mut u32) = 0;
780
- *(w.add(0x480) as *mut u32) = 0;
781
- *(w.add(0x264) as *mut u32) = 0;
782
- *(w.add(0x268) as *mut u32) = 0;
783
- *(w.add(0x270) as *mut u32) = 0;
784
- *(w.add(0x278) as *mut u32) = 0;
785
- *(w.add(0x27C) as *mut u32) = 0;
786
- *(w.add(0x3FC) as *mut u32) = 0;
787
- *(w.add(0x3F4) as *mut i32) = -1;
788
- *(w.add(0x3F8) as *mut i32) = -1;
789
 
790
  // ===== Resolution-dependent team render indices =====
791
  {
792
  let screen_height = (*ddgame).screen_height_pixels;
793
  if screen_height < 0x2D0 {
794
  // < 720px: smaller layout
795
- *(w.add(0x2B0) as *mut i32) = 0xC;
796
- *(w.add(0x280) as *mut i32) = 8;
797
- *(w.add(0x284) as *mut i32) = 7;
798
- *(w.add(0x288) as *mut i32) = 5;
799
- *(w.add(0x28C) as *mut i32) = 2;
800
- *(w.add(0x290) as *mut i32) = 4;
801
- *(w.add(0x294) as *mut i32) = 3;
802
- *(w.add(0x298) as *mut i32) = 1;
803
- *(w.add(0x29C) as *mut i32) = 6;
804
  } else {
805
  // >= 720px: larger layout
806
- *(w.add(0x2B0) as *mut i32) = 0x10;
807
- *(w.add(0x280) as *mut i32) = 0x10;
808
- *(w.add(0x284) as *mut i32) = 0xF;
809
- *(w.add(0x288) as *mut i32) = 0xD;
810
- *(w.add(0x28C) as *mut i32) = 10;
811
- *(w.add(0x290) as *mut i32) = 0xC;
812
- *(w.add(0x294) as *mut i32) = 0xB;
813
- *(w.add(0x298) as *mut i32) = 9;
814
- *(w.add(0x29C) as *mut i32) = 0xE;
815
  }
816
 
817
- if screen_height < 600 {
818
- *(w.add(0x2AC) as *mut i32) = 7;
819
- } else {
820
- *(w.add(0x2AC) as *mut i32) = 10;
821
- }
822
  }
823
 
824
  // ===== Game mode and sentinel arrays =====
825
- *(w.add(0x3F0) as *mut i32) = 1;
826
 
827
- // Fill team slot mapping with -1 sentinels (0x38C-0x3EC = 25 entries)
828
- for i in 0..25u32 {
829
- *(w.add(0x38C + i as usize * 4) as *mut i32) = -1;
830
- }
831
 
832
  // ===== Team-to-slot mapping (conditional on network/replay mode) =====
833
- if *(ddgame_raw.add(0x1C) as *const u32) != 0 || (*wrapper).replay_flag_a != 0 {
834
  let team_count = (*game_info).num_teams as i32;
835
  let exclude_team = (*game_info).starting_team_index as i32;
836
- let mut slot_idx = 0i32;
837
- let pivar8_base = w.add(0x38C) as *mut i32;
838
- let pivar7_base = w.add(0x3BC) as *mut i32;
839
- // pivar7 (at 0x3BC) advances every iteration, indexed by team_id
840
- // pivar8 (at 0x38C) advances only for non-excluded teams, indexed by slot_idx
841
  for team_id in 0..team_count {
842
  if team_id != exclude_team {
843
- *pivar7_base.add(team_id as usize) = slot_idx;
844
- *pivar8_base.add(slot_idx as usize) = team_id;
845
  slot_idx += 1;
846
  } else {
847
  *pivar7_base.add(team_id as usize) = -1;
@@ -850,20 +780,20 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
850
  }
851
 
852
  // ===== Game logic initialization =====
853
- *(w.add(0x474) as *mut u32) = 0;
854
  (*wrapper).init_flag = 1;
855
 
856
- // Already-ported sub-functions (called via wrapper pointer as the existing hooks expect)
857
- init_team_scoring(wrapper as *mut u8);
858
- init_alliance_data(wrapper as *mut u8);
859
 
860
  // ===== Game speed/timing fields =====
861
  (*wrapper).health_precision = 500;
862
- *(w.add(0x45C) as *mut u32) = 0;
863
- *(w.add(0x464) as *mut u32) = 0;
864
- *(w.add(0x460) as *mut u32) = 0;
865
- *(w.add(0x478) as *mut u32) = 0;
866
- *(w.add(0x418) as *mut u32) = 0;
867
 
868
  // Turn percentage: (game_info.turn_percentage << 16) / 100
869
  let turn_pct_raw = (*game_info).turn_percentage_raw;
@@ -873,19 +803,14 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
873
  let is_headful = (*ddgame).sound_available != 0;
874
 
875
  if is_headful {
876
- // The headful display initialization involves complex usercall patterns
877
- // (BitGrid__Init with per-layer dimensions, DisplayGfx__ConstructFull with
878
- // ECX/EDX register params). For now, bridge to original display layer code
879
- // via the existing ported DisplayGfx constructors.
880
- // TODO: Port the headful display init with proper usercall bridges.
881
- init_game_state_display(wrapper, w, ddgame_raw, game_info);
882
  }
883
 
884
  // ===== Worm selection count and terrain config =====
885
  {
886
  let mut val = (*game_info).worm_select_cfg_a;
887
- let min_teams = *(w.add(0x2A8) as *const i32);
888
- let team_count_cfg = *(w.add(0x2AC) as *const i32);
889
 
890
  if val == 0 {
891
  val = team_count_cfg;
@@ -894,7 +819,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
894
  } else if val > 0x20 {
895
  val = 0x20;
896
  }
897
- *(w.add(0x2A0) as *mut i32) = val;
898
 
899
  val = (*game_info).worm_select_cfg_b;
900
  if val == -1 {
@@ -904,49 +829,50 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
904
  } else if val > 0x20 {
905
  val = 0x20;
906
  }
907
- *(w.add(0x2A4) as *mut i32) = val;
908
 
909
- *(w.add(0x414) as *mut u32) = ((*game_info)._field_f365 != 0) as u32;
910
 
911
  if is_headful {
912
- let palette_ptr = *(ddgame_raw as *const *mut u8);
913
- *(palette_ptr.add(0x10) as *mut u32) = ((*game_info)._field_f370 != 0) as u32;
 
914
  }
915
  }
916
 
917
  // Ensure worm_select_count >= worm_select_count_alt
918
- if *(w.add(0x2A0) as *const i32) < *(w.add(0x2A4) as *const i32) {
919
- *(w.add(0x2A0) as *mut i32) = *(w.add(0x2A4) as *const i32);
920
  }
921
 
922
  // ===== Zero state fields =====
923
- *(w.add(0x434) as *mut u32) = 0;
924
- *(w.add(0x438) as *mut i32) = -1;
925
- *(w.add(0x43C) as *mut i32) = -1;
926
- *(w.add(0x440) as *mut u32) = 0;
927
- *(w.add(0x444) as *mut u32) = 0;
928
- *(w.add(0x424) as *mut u32) = 0;
929
- *(w.add(0x428) as *mut u32) = 0;
930
- *(w.add(0x448) as *mut u32) = 0;
931
- *(w.add(0x44C) as *mut u32) = 0;
932
- *(w.add(0x42C) as *mut u32) = 0;
933
- *(w.add(0x430) as *mut u32) = 0;
934
- *(w.add(0x40C) as *mut u32) = 0;
935
- *(w.add(0x410) as *mut u32) = 0;
936
 
937
  // ===== Landscape flags =====
938
- init_landscape_flags(wrapper as *mut u8);
939
 
940
  // ===== Level bounds and camera initialization =====
941
  init_game_state_level_bounds(ddgame, game_info);
942
 
943
  // ===== Display objects for HUD (headful only) =====
944
  if is_headful {
945
- init_game_state_hud_objects(wrapper, ddgame_raw, game_info);
946
  }
947
 
948
  // ===== Team name validation =====
949
- init_game_state_team_names(ddgame_raw, game_info);
950
 
951
  // ===== Weapon and team initialization =====
952
  (*ddgame).hud_status_code = 0;
@@ -961,41 +887,42 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
961
 
962
  // DDGame__InitTeamsFromSetup
963
  {
 
 
964
  let f: unsafe extern "stdcall" fn(u32, u32) =
965
  core::mem::transmute(rb(va::INIT_TEAMS_FROM_SETUP) as usize);
966
- f(
967
- ddgame as u32 + 0x4628,
968
- *(ddgame_raw.add(0x24) as *const u32),
969
- );
970
  }
971
 
972
  // ===== Version-dependent stack/render config =====
973
- init_game_state_version_config(ddgame_raw, game_info);
974
 
975
  // ===== Water level and wind =====
976
  init_game_state_water_level(ddgame, game_info);
977
 
978
  // ===== Random bag initialization =====
979
- init_game_state_random_bags(ddgame_raw, game_info);
980
 
981
  // ===== Turn state =====
982
- init_turn_state(wrapper as *mut u8);
983
 
984
  // ===== Weapon availability loop =====
985
- init_game_state_weapon_avail(wrapper, ddgame_raw, game_info);
986
 
987
  // ===== Team/object tracking arrays =====
988
- init_game_state_tracking_arrays(ddgame_raw, game_info);
989
 
990
  // ===== Statistics counters =====
991
- for i in 0..10u32 {
992
- *(ddgame_raw.add(0x9890 + i as usize * 4) as *mut u32) = 0;
 
 
 
993
  }
994
 
995
  // ===== TeamManager constructor =====
996
  {
997
- let mem = wa_malloc(0x6C);
998
- core::ptr::write_bytes(mem, 0, 0x4C);
999
  let result = if mem.is_null() {
1000
  core::ptr::null_mut()
1001
  } else {
@@ -1003,36 +930,32 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1003
  core::mem::transmute(rb(va::TEAM_MANAGER_CONSTRUCTOR) as usize);
1004
  ctor(mem, ddgame as u32)
1005
  };
1006
- *(ddgame_raw.add(0x530) as *mut *mut u8) = result;
1007
  }
1008
 
1009
  // ===== Network sync callback =====
1010
- if *(ddgame_raw.add(8) as *const u32) != 0 {
1011
- let snd_ptr = *(ddgame_raw.add(8) as *const *const u8);
1012
- let vtable = *(snd_ptr as *const *const u32);
1013
- let vmethod: unsafe extern "thiscall" fn(*const u8) = core::mem::transmute(*vtable.add(1));
1014
- vmethod(snd_ptr);
 
1015
  }
1016
 
1017
  // ===== CTaskTurnGame constructor =====
1018
  {
1019
  let game_version = (*game_info).game_version;
 
1020
  if game_version == -2 {
1021
  // Online game: ECX = *net_bridge (dereferenced!) for the constructor
1022
- let mem = wa_malloc(0x324);
1023
- core::ptr::write_bytes(mem, 0, 0x304);
1024
  if mem.is_null() {
1025
  (*wrapper).task_turn_game = core::ptr::null_mut();
1026
  } else {
1027
- let net_bridge_ptr = *(w.add(0x48C) as *const *const u32);
1028
- let ecx_val = *net_bridge_ptr; // deref net_bridge to get ECX
1029
- call_ctor_with_ecx(
1030
- mem,
1031
- *(ddgame_raw.add(0x24) as *const u32),
1032
- ecx_val, // ECX = *net_bridge
1033
- rb(va::CTASK_TURNGAME_CTOR),
1034
- );
1035
- *(mem.add(0x300) as *mut u32) = net_bridge_ptr as u32;
1036
  // Override vtables for online mode
1037
  *(mem as *mut u32) = rb(0x669C28);
1038
  *(mem.add(0x30) as *mut u32) = rb(0x669C44);
@@ -1040,46 +963,38 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1040
  }
1041
  } else {
1042
  // Normal game: ECX must = DDGame for the constructor
1043
- let mem = wa_malloc(0x320);
1044
- core::ptr::write_bytes(mem, 0, 0x300);
1045
  let result = if mem.is_null() {
1046
  core::ptr::null_mut()
1047
  } else {
1048
- call_ctor_with_ecx(
1049
- mem,
1050
- *(ddgame_raw.add(0x24) as *const u32),
1051
- ddgame_raw as u32, // ECX = DDGame
1052
- rb(va::CTASK_TURNGAME_CTOR),
1053
- )
1054
  };
1055
  (*wrapper).task_turn_game = result;
1056
  }
1057
  }
1058
 
1059
  // ===== Zero state block (0x98-0xCC, 14 entries) =====
1060
- for i in 0..14u32 {
1061
- *(w.add(0x98 + i as usize * 4) as *mut u32) = 0;
1062
- }
1063
 
1064
  // ===== Replay/network mode flag =====
1065
  {
1066
  let replay_a = (*wrapper).replay_flag_a;
1067
  let result = if replay_a == 0
1068
- || ((*game_info).replay_config_flag != 0 && *(w.add(0x49C) as *const i32) > 0xC)
1069
  {
1070
  0u32
1071
  } else {
1072
  0xFFFF_FFFFu32
1073
  };
1074
- *(ddgame_raw.add(0x7EF0) as *mut u32) = result;
1075
  }
1076
 
1077
- *(w.add(0x4B0) as *mut i32) = -1;
1078
- *(w.add(0x4AC) as *mut u32) = 0;
1079
- *(w.add(0xE0) as *mut u32) = 0;
1080
 
1081
  // 0xEC: (game_info.f340 != 0) - 1 (i.e., 0 if nonzero, 0xFFFFFFFF if zero)
1082
- *(w.add(0xEC) as *mut u32) = (((*game_info)._field_f340 != 0) as u32).wrapping_sub(1);
1083
 
1084
  (*game_info)._field_f34c = -1;
1085
  (*wrapper).state_initialized = 1;
@@ -1093,7 +1008,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1093
 
1094
  // ===== Copy game state to statistics buffer =====
1095
  {
1096
- let src = ddgame_raw.add(0x8168) as *const u32;
1097
  let dst = (*wrapper).statistics as *mut u32;
1098
  core::ptr::copy_nonoverlapping(src, dst, 0x2E5);
1099
  }
@@ -1108,26 +1023,25 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1108
  }
1109
 
1110
  // Get frame counter via landscape vtable
1111
- let landscape_ptr = *(ddgame_raw.add(0x20) as *const *const u8);
1112
- let landscape_vtable = *(landscape_ptr as *const *const u32);
1113
- let get_frame: unsafe extern "thiscall" fn(*const u8) -> u32 =
1114
  core::mem::transmute(*landscape_vtable.add(0x12));
1115
- let frame = get_frame(landscape_ptr);
1116
 
1117
  (*wrapper).initial_checksum = frame.wrapping_add(hash);
1118
  }
1119
 
1120
  // ===== Weapon panel (headful only) =====
1121
  if is_headful {
1122
- let mem = wa_malloc(0x208);
1123
- core::ptr::write_bytes(mem, 0, 0x1E8);
1124
  let result = if mem.is_null() {
1125
  core::ptr::null_mut()
1126
  } else {
1127
  // usercall: ESI=this(mem), stack=(DDGame), RET 0x4
1128
  call_usercall_esi_stack1(mem, ddgame as u32, rb(va::INIT_WEAPON_PANEL))
1129
  };
1130
- *(ddgame_raw.add(0x548) as *mut *mut u8) = result;
1131
  }
1132
  }
1133
 
@@ -1137,16 +1051,12 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1137
 
1138
  /// Allocate a BufferObject (0x48 bytes) with size computed from game setup.
1139
  /// Used for main_buffer and state_buffer.
1140
- unsafe fn allocate_buffer_object(ddgame_raw: *mut u8, game_info: *const GameInfo) -> *mut u8 {
1141
  use crate::address::va;
1142
  use crate::rebase::rb;
1143
- use crate::wa_alloc::wa_malloc;
1144
 
1145
- let mem = wa_malloc(0x48) as *mut u32;
1146
- // Zero first 10 dwords
1147
- for i in 0..10 {
1148
- *mem.add(i) = 0;
1149
- }
1150
  if mem.is_null() {
1151
  return core::ptr::null_mut();
1152
  }
@@ -1157,7 +1067,7 @@ unsafe fn allocate_buffer_object(ddgame_raw: *mut u8, game_info: *const GameInfo
1157
 
1158
  let ctor: unsafe extern "stdcall" fn(*mut u32, u32, u32) -> *mut u8 =
1159
  core::mem::transmute(rb(va::BUFFER_OBJECT_CONSTRUCTOR) as usize);
1160
- ctor(mem, size, ddgame_raw as u32) // 3rd param is DDGame ptr as u32
1161
  }
1162
 
1163
  /// Allocate a raw ring-buffer-like object with manual field initialization.
@@ -1165,14 +1075,9 @@ unsafe fn allocate_buffer_object(ddgame_raw: *mut u8, game_info: *const GameInfo
1165
  /// `alloc_size` = total allocation size (0x3C or 0x48).
1166
  /// `capacity` = buffer capacity (0x1000, 0x2000, or 0x10000).
1167
  unsafe fn allocate_ring_buffer_raw(alloc_size: u32, capacity: u32) -> *mut u8 {
1168
- use crate::wa_alloc::wa_malloc;
1169
 
1170
- let n_dwords = alloc_size / 4;
1171
- let mem = wa_malloc(alloc_size) as *mut u32;
1172
- // Zero all dwords
1173
- for i in 0..n_dwords as usize {
1174
- *mem.add(i) = 0;
1175
- }
1176
  if mem.is_null() {
1177
  return core::ptr::null_mut();
1178
  }
@@ -1192,31 +1097,25 @@ unsafe fn allocate_ring_buffer_raw(alloc_size: u32, capacity: u32) -> *mut u8 {
1192
 
1193
  /// Allocate a RingBuffer using the already-ported ring_buffer_init function.
1194
  /// Used for the conditional network ring buffer.
1195
- unsafe fn allocate_ring_buffer_init(_wrapper: *mut u8) -> *mut u8 {
1196
- use crate::wa_alloc::wa_malloc;
1197
 
1198
- let mem = wa_malloc(0x3C) as *mut u32;
1199
- for i in 0..7 {
1200
- *mem.add(i) = 0;
1201
- }
1202
  if mem.is_null() {
1203
  return core::ptr::null_mut();
1204
  }
1205
- ring_buffer_init(mem as *mut u8, 0x2000);
1206
- mem as *mut u8
1207
  }
1208
 
1209
  /// Allocate and initialize a PaletteContext (0x72C bytes).
1210
  /// Sets dirty_range_min=1, dirty_range_max=0xFF, then calls PaletteContext__Init.
1211
- unsafe fn allocate_palette_context(
1212
- _ddgame_raw: *mut u8,
1213
- ) -> *mut crate::render::palette::PaletteContext {
1214
  use crate::address::va;
1215
  use crate::rebase::rb;
1216
- use crate::wa_alloc::wa_malloc;
1217
 
1218
- let mem = wa_malloc(0x72C) as *mut u16;
1219
- core::ptr::write_bytes(mem as *mut u8, 0, 0x70C);
1220
  if mem.is_null() {
1221
  return core::ptr::null_mut();
1222
  }
@@ -1292,26 +1191,24 @@ unsafe extern "C" fn call_usercall_esi_stack1(
1292
  /// Creates DisplayGfx layers, camera objects, and the main display surface.
1293
  unsafe fn init_game_state_display(
1294
  wrapper: *mut DDGameWrapper,
1295
- w: *mut u8,
1296
- ddgame_raw: *mut u8,
1297
  game_info: *const GameInfo,
1298
  ) {
1299
  use crate::address::va;
1300
  use crate::rebase::rb;
1301
- use crate::wa_alloc::wa_malloc;
1302
 
1303
- let max_team_render = *(w.add(0x2B0) as *const i32);
1304
 
1305
  // Display virtual call: display->vtable[1](&display_width, &display_height)
1306
- // Returns display dimensions via output pointers.
1307
  let mut display_width: u32 = 0;
1308
  let mut display_height: u32 = 0;
1309
  {
1310
- let display_ptr = *(ddgame_raw.add(4) as *const *const u8);
1311
- let vtable = *(display_ptr as *const *const u32);
1312
- let vmethod: unsafe extern "thiscall" fn(*const u8, *mut u32, *mut u32) =
1313
  core::mem::transmute(*vtable.add(1));
1314
- vmethod(display_ptr, &mut display_width, &mut display_height);
1315
  }
1316
 
1317
  // Screen height for HUD: 0x12C (300) if wide layout, 0x8C (140) if narrow
@@ -1320,36 +1217,36 @@ unsafe fn init_game_state_display(
1320
  } else {
1321
  0x8C // 140
1322
  };
1323
- *(w.add(0x388) as *mut i32) = screen_height;
1324
 
1325
  // Count active teams from slot_to_team array
1326
  let team_count = (*game_info).num_teams as u32;
1327
- let count_active = |w: *mut u8, base: u32| -> u32 {
1328
  let mut n = base;
1329
- if *(w.add(0x3AC) as *const i32) >= 0 {
1330
- let mut p = w.add(0x3AC) as *const i32;
1331
  loop {
1332
- p = p.add(1);
1333
  n += 1;
1334
- if *p < 0 {
1335
  break;
1336
  }
 
1337
  }
1338
  }
1339
  n
1340
  };
1341
- let active_count = count_active(w, team_count);
1342
 
1343
  // min_teams: (int)(active_count - 1) < 2 ? 1 : active_count - 1
1344
  let min_teams: i32 = if (active_count.wrapping_sub(1) as i32) < 2 {
1345
  1
1346
  } else {
1347
- count_active(w, team_count) as i32 - 1
1348
  };
1349
- *(w.add(0x2A8) as *mut i32) = min_teams;
1350
 
1351
  // screen_offset = display_width - screen_height
1352
- *(w.add(0x384) as *mut i32) = display_width as i32 - screen_height;
1353
  let screen_offset = display_width as i32 - screen_height;
1354
 
1355
  // ===== Layer 1 (wrapper+0x18): BitGrid(8, max_team*64+2, screen_offset-7) =====
@@ -1373,23 +1270,21 @@ unsafe fn init_game_state_display(
1373
  }
1374
 
1375
  // ===== ConstructFull for main display (wrapper+0x24) =====
1376
- // usercall: ECX=DDGame+0x7328, EDX=max_team_render_index
1377
  // stdcall stack: (this, display, team_idx, screen_offset-3, render_param)
1378
  {
1379
- let mem = wa_malloc(0x468);
1380
- core::ptr::write_bytes(mem, 0, 0x448);
1381
  let result = if mem.is_null() {
1382
  core::ptr::null_mut()
1383
  } else {
1384
  {
1385
- let p_display = *(ddgame_raw.add(4) as *const u32);
1386
- let p_team = *(w.add(0x280) as *const u32);
1387
  let p_offset = screen_offset - 3;
1388
- let p_render = *(ddgame_raw.add(0x732C) as *const u32);
1389
- let p_ecx = *(ddgame_raw.add(0x7328) as *const u32);
1390
  let p_edx = max_team_render as u32;
1391
  let target = rb(va::DISPLAYGFX_CONSTRUCT_FULL);
1392
- // Use fastcall directly: ECX=ecx_val, EDX=edx_val, 5 stack params
1393
  let f: unsafe extern "fastcall" fn(
1394
  u32,
1395
  u32,
@@ -1408,15 +1303,15 @@ unsafe fn init_game_state_display(
1408
  // Turn timer
1409
  {
1410
  let timer_val = max_team_render << 5;
1411
- *(w.add(0x400) as *mut i32) = timer_val;
1412
- *(w.add(0x408) as *mut i32) = timer_val;
1413
- *(w.add(0x404) as *mut i32) = 0;
1414
  }
1415
 
1416
  // Dirty rect / clipping calls on layers A, B, C
1417
- call_display_gfx_dirty_rect((*wrapper).display_gfx_a, ddgame_raw);
1418
- call_display_gfx_dirty_rect((*wrapper).display_gfx_b, ddgame_raw);
1419
- call_display_gfx_dirty_rect((*wrapper).display_gfx_c, ddgame_raw);
1420
 
1421
  // ===== Layer 4 (wrapper+0x2C): BitGrid(8, 0x100, 0x154) β€” constant =====
1422
  (*wrapper).display_gfx_d = create_display_gfx_layer_sized(0x100, 0x154);
@@ -1425,8 +1320,8 @@ unsafe fn init_game_state_display(
1425
  (*wrapper).display_gfx_e = create_display_gfx_layer_sized(0x30, 0xC0);
1426
 
1427
  // Create 2 camera objects (wrapper+0x30, +0x38)
1428
- (*wrapper).camera_a = create_camera_object(wrapper, ddgame_raw, 0x2C);
1429
- (*wrapper).camera_b = create_camera_object(wrapper, ddgame_raw, 0x34);
1430
  }
1431
 
1432
  /// Create a DisplayGfx layer (0x4C bytes): malloc + memset + BitGrid__Init + vtable.
@@ -1434,10 +1329,9 @@ unsafe fn init_game_state_display(
1434
  unsafe fn create_display_gfx_layer_sized(height: u32, width: u32) -> *mut u8 {
1435
  use crate::bitgrid::{BitGrid, BIT_GRID_DISPLAY_VTABLE};
1436
  use crate::rebase::rb;
1437
- use crate::wa_alloc::wa_malloc;
1438
 
1439
- let mem = wa_malloc(0x4C) as *mut u32;
1440
- core::ptr::write_bytes(mem as *mut u8, 0, 0x4C);
1441
  if mem.is_null() {
1442
  return core::ptr::null_mut();
1443
  }
@@ -1449,7 +1343,7 @@ unsafe fn create_display_gfx_layer_sized(height: u32, width: u32) -> *mut u8 {
1449
  }
1450
 
1451
  /// Call the DisplayGfx dirty rect/clipping vtable dispatch (slot 0).
1452
- unsafe fn call_display_gfx_dirty_rect(gfx: *mut u8, ddgame_raw: *mut u8) {
1453
  if gfx.is_null() {
1454
  return;
1455
  }
@@ -1484,43 +1378,37 @@ unsafe fn call_display_gfx_dirty_rect(gfx: *mut u8, ddgame_raw: *mut u8) {
1484
  let vtable = *(gfx as *const *const u32);
1485
  let vmethod: unsafe extern "thiscall" fn(*mut u8, i32, i32, i32, i32, u32) =
1486
  core::mem::transmute(*vtable);
1487
- vmethod(gfx, x1, y1, x2, y2, *(ddgame_raw.add(0x7328) as *const u32));
1488
  }
1489
 
1490
  /// Create a camera/display object (0x3D4 bytes).
1491
  unsafe fn create_camera_object(
1492
  wrapper: *mut DDGameWrapper,
1493
- ddgame_raw: *mut u8,
1494
  display_gfx_offset: usize,
1495
  ) -> *mut u8 {
1496
- use crate::wa_alloc::wa_malloc;
1497
 
1498
- let mem = wa_malloc(0x3D4) as *mut i32;
1499
- core::ptr::write_bytes(mem as *mut u8, 0, 0x3B4);
1500
  if mem.is_null() {
1501
  return core::ptr::null_mut();
1502
  }
1503
 
1504
- let w = wrapper as *mut u8;
1505
- let display_gfx = *(w.add(display_gfx_offset) as *const i32);
1506
 
1507
- *mem.add(1) = *(ddgame_raw.add(4) as *const i32); // display
1508
- *mem.add(3) = *(ddgame_raw.add(0x730C) as *const i32);
1509
- *mem.add(2) = *(ddgame_raw.add(0x7328) as *const i32);
1510
- *mem = display_gfx;
1511
 
1512
- let w_val = *((display_gfx as *const u8).add(0x14) as *const i32);
1513
- let h_val = *((display_gfx as *const u8).add(0x18) as *const i32);
1514
 
1515
  *mem.add(9) = w_val;
1516
  *mem.add(4) = w_val / 2;
1517
- *mem.add(6) = 0;
1518
- *mem.add(7) = 0;
1519
- *mem.add(8) = 0;
1520
  *mem.add(10) = h_val;
1521
  *mem.add(5) = h_val / 2;
1522
- *mem.add(0xB) = 0;
1523
- *mem.add(0xEC) = 0;
1524
 
1525
  mem as *mut u8
1526
  }
@@ -1575,60 +1463,47 @@ unsafe fn init_game_state_level_bounds(ddgame: *mut DDGame, game_info: *const Ga
1575
  }
1576
 
1577
  /// Create HUD display objects (headful only, conditional on is_headful).
1578
- unsafe fn init_game_state_hud_objects(
1579
- _wrapper: *mut DDGameWrapper,
1580
- ddgame_raw: *mut u8,
1581
- _game_info: *const GameInfo,
1582
- ) {
1583
  use crate::address::va;
1584
  use crate::rebase::rb;
1585
- use crate::wa_alloc::wa_malloc;
1586
 
1587
  // DisplayObject for HUD background
1588
- // usercall: ECX=DDGame+0x7324, EDX=DDGame+0x7328, stdcall stack=(this, DDGame+0x33C)
1589
  {
1590
- let mem = wa_malloc(0x58) as *mut u32;
1591
- core::ptr::write_bytes(mem as *mut u8, 0, 0x38);
1592
  let result = if mem.is_null() {
1593
  core::ptr::null_mut()
1594
  } else {
1595
  let ctor: unsafe extern "fastcall" fn(u32, u32, *mut u32, u32) -> *mut u8 =
1596
  core::mem::transmute(rb(va::DISPLAY_OBJECT_CONSTRUCTOR) as usize);
1597
  ctor(
1598
- *(ddgame_raw.add(0x7324) as *const u32), // ECX
1599
- *(ddgame_raw.add(0x7328) as *const u32), // EDX
1600
  mem,
1601
- ddgame_raw as u32 + 0x33C,
1602
  )
1603
  };
1604
- *(ddgame_raw.add(0x540) as *mut *mut u8) = result;
1605
  }
1606
 
1607
  // DisplayGfx textbox for HUD text
1608
  // thiscall: ECX=DDGame.display, stack=(this, 0x13, 2)
1609
  {
1610
- let mem = wa_malloc(0x158) as *mut u32;
1611
- core::ptr::write_bytes(mem as *mut u8, 0, 0x138);
1612
  let result = if mem.is_null() {
1613
  core::ptr::null_mut()
1614
  } else {
1615
- let ctor: unsafe extern "thiscall" fn(u32, *mut u32, u32, u32) -> *mut u8 =
1616
  core::mem::transmute(rb(va::CONSTRUCT_TEXTBOX) as usize);
1617
- ctor(
1618
- *(ddgame_raw.add(4) as *const u32), // ECX = DDGame.display
1619
- mem,
1620
- 0x13,
1621
- 2,
1622
- )
1623
  };
1624
- *(ddgame_raw.add(0x544) as *mut *mut u8) = result;
1625
  }
1626
  }
1627
 
1628
  /// Validate team names β€” check for CPU team markers.
1629
- unsafe fn init_game_state_team_names(ddgame_raw: *mut u8, game_info: *mut GameInfo) {
1630
- let ddgame = ddgame_raw as *mut DDGame;
1631
-
1632
  // Set initial team color from game_info
1633
  (*ddgame).team_color = (*game_info).team_color_source as u32;
1634
 
@@ -1648,8 +1523,7 @@ unsafe fn init_game_state_team_names(ddgame_raw: *mut u8, game_info: *mut GameIn
1648
  }
1649
 
1650
  /// Version-dependent configuration (stack size, rendering constants).
1651
- unsafe fn init_game_state_version_config(ddgame_raw: *mut u8, game_info: *const GameInfo) {
1652
- let ddgame = ddgame_raw as *mut DDGame;
1653
  let game_version = (*game_info).game_version;
1654
 
1655
  // Stack height: game_version < -1 β†’ 0x700, >= -1 β†’ 0x800
@@ -1658,7 +1532,7 @@ unsafe fn init_game_state_version_config(ddgame_raw: *mut u8, game_info: *const
1658
  // Copy RNG seed
1659
  let rng_seed = (*game_info).rng_seed;
1660
  (*ddgame).game_rng = rng_seed;
1661
- *(ddgame_raw.add(0x45F0) as *mut u32) = rng_seed;
1662
 
1663
  // Zero various fields
1664
  (*ddgame).frame_counter = 0;
@@ -1668,10 +1542,11 @@ unsafe fn init_game_state_version_config(ddgame_raw: *mut u8, game_info: *const
1668
  (*ddgame)._field_7e4c = 0;
1669
  (*ddgame)._field_5d0 = 0;
1670
 
1671
- // Zero counter arrays (6 entries each Γ— 3 blocks)
1672
- for block_base in [0x7EA8u32, 0x7EC0, 0x7ED8] {
1673
- for i in 0..6u32 {
1674
- *(ddgame_raw.add((block_base + i * 4) as usize) as *mut u32) = 0;
 
1675
  }
1676
  }
1677
 
@@ -1731,7 +1606,8 @@ unsafe fn init_game_state_water_level(ddgame: *mut DDGame, game_info: *mut GameI
1731
  }
1732
 
1733
  /// Random bag initialization (land, mine, barrel, weapon).
1734
- unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info: *mut GameInfo) {
 
1735
  // Random bag at DDGame+0x360C (5 zeroes + 1 one)
1736
  {
1737
  let write_idx_ptr = ddgame_raw.add(0x379C) as *mut i32;
@@ -1864,15 +1740,12 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info: *mut GameI
1864
  }
1865
 
1866
  /// Weapon availability loop using already-ported check_weapon_avail.
1867
- unsafe fn init_game_state_weapon_avail(
1868
- _wrapper: *mut DDGameWrapper,
1869
- ddgame_raw: *mut u8,
1870
- game_info: *const GameInfo,
1871
- ) {
1872
  let game_version = (*game_info).game_version;
1873
 
1874
  for weapon_id in 1..0x47i32 {
1875
- let avail = check_weapon_avail(ddgame_raw as *mut DDGame, weapon_id as u32);
1876
  let is_avail = if game_version < 0x4D {
1877
  avail != 0
1878
  } else {
@@ -1891,11 +1764,9 @@ unsafe fn init_game_state_weapon_avail(
1891
  }
1892
 
1893
  /// Allocate team/object tracking arrays and initialize the 0x51C object.
1894
- unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info: *const GameInfo) {
1895
  use crate::wa_alloc::wa_malloc;
1896
 
1897
- let ddgame = ddgame_raw as *mut DDGame;
1898
-
1899
  // Game speed
1900
  (*ddgame).game_speed = Fixed::ONE;
1901
 
@@ -1937,7 +1808,7 @@ unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info: *const
1937
  let count_a = (*game_info).team_slot_count;
1938
  let size = count_a.wrapping_mul(4);
1939
  let arr = wa_malloc(size);
1940
- *(ddgame_raw.add(0x514) as *mut *mut u8) = arr;
1941
  for i in 0..count_a as usize {
1942
  *(arr as *mut u32).add(i) = 0;
1943
  }
@@ -1946,7 +1817,7 @@ unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info: *const
1946
  let count_b = (*game_info).object_slot_count;
1947
  let size = count_b.wrapping_mul(4);
1948
  let arr = wa_malloc(size);
1949
- *(ddgame_raw.add(0x518) as *mut *mut u8) = arr;
1950
  for i in 0..count_b as usize {
1951
  *(arr as *mut u32).add(i) = 0;
1952
  }
@@ -1954,7 +1825,7 @@ unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info: *const
1954
 
1955
  // Initialize the 0x51C object (vector-like structure)
1956
  {
1957
- let obj = *(ddgame_raw.add(0x51C) as *const *mut u8);
1958
  if !obj.is_null() {
1959
  *(obj as *mut u32) = 0; // first field = 0
1960
  // The vector operations (FUN_005370c0, etc.) are complex.
 
4
  //! Each sub-function is hooked individually so it works regardless of whether
5
  //! InitGameState itself is Rust or the original WA code.
6
 
7
+ use crate::audio::dssound::DSSound;
8
  use crate::engine::ddgame::DDGame;
9
  use crate::engine::ddgame_wrapper::DDGameWrapper;
10
  use crate::engine::game_info::GameInfo;
11
  use crate::fixed::Fixed;
12
+ use crate::render::display::gfx::DisplayGfx;
13
+ use crate::render::landscape::PCLandscape;
14
 
15
  /// Pure Rust implementation of SpriteGfxTable__Init (0x541620).
16
  ///
 
42
  /// - `[1]`: capacity
43
  /// - `[2]`-`[6]`: zeroed (head, tail, count, etc.)
44
  pub unsafe fn ring_buffer_init(struct_ptr: *mut u8, capacity: u32) {
45
+ use crate::wa_alloc::wa_malloc_zeroed;
46
  let alloc_size = ((capacity + 3) & !3) + 0x20;
47
+ let data = wa_malloc_zeroed(alloc_size);
 
 
 
48
 
49
  let s = struct_ptr as *mut u32;
50
  *s.add(0) = data as u32; // data pointer
 
63
  const TEAM_ENTRY_STRIDE: usize = 3000;
64
 
65
  /// Helper: read the GameInfo pointer from a DDGameWrapper pointer.
 
 
66
  #[inline]
67
+ unsafe fn game_info_from_wrapper(wrapper: *mut DDGameWrapper) -> *mut GameInfo {
68
+ (*(*wrapper).ddgame).game_info
 
69
  }
70
 
71
  /// Helper: read the DDGame pointer from a DDGameWrapper pointer.
72
  #[inline]
73
+ unsafe fn ddgame_from_wrapper(wrapper: *mut DDGameWrapper) -> *mut DDGame {
74
+ (*wrapper).ddgame
75
  }
76
 
77
  /// Pure Rust implementation of CGameTask_InitTeamScoring_Maybe (0x528510).
 
95
  /// - +0x128[starting_team]: set to 1 (starting team marker in array_1)
96
  /// - +0x22C[0..team_count]: team activity flags (-1 normal, -2 training)
97
  /// - +0x054[0..team_count]: CTask pointers β€” sub-fields zeroed if non-null
98
+ pub unsafe fn init_team_scoring(wrapper: *mut DDGameWrapper) {
99
  let game_info = game_info_from_wrapper(wrapper);
100
 
101
  let scoring_param_a = (*game_info).scoring_param_a as u32;
102
  let scoring_param_b = (*game_info).scoring_param_b as u32;
103
+ let value_a = scoring_param_a * 50;
104
+ let value_b = scoring_param_b * 50;
105
 
106
  // Initialize 7 parallel arrays (13 elements each).
 
 
107
  for i in 0..MAX_TEAMS {
108
+ (*wrapper).team_score_array_0[i] = 0;
109
+ (*wrapper).team_starting_marker[i] = 0;
110
+ (*wrapper).team_scoring_a[i] = value_a;
111
+ (*wrapper).team_score_array_3[i] = 0;
112
+ (*wrapper).team_scoring_b[i] = value_b;
113
+ (*wrapper).team_scoring_c[i] = value_b;
114
+ (*wrapper).team_score_array_6[i] = 1;
 
115
  }
116
 
117
+ // Mark the starting team
118
+ let starting_team = (*game_info).starting_team_index as usize;
119
+ (*wrapper).team_starting_marker[starting_team] = 1;
120
 
 
121
  let mode_flag = (*game_info).game_mode_flag;
 
 
122
  let num_teams = (*game_info).num_teams as i32;
123
 
 
 
 
124
  if mode_flag < 0 {
125
  // Training/replay mode: set all flags to -2 (0xFFFFFFFE)
126
+ for i in 0..num_teams as usize {
127
+ (*wrapper).team_activity_flags[i] = 0xFFFF_FFFE;
 
 
128
  }
129
 
130
  // For each active team slot in GameInfo, clear the flag to 0
 
131
  let game_info2 = game_info_from_wrapper(wrapper);
132
  let alliance_team_count = (*game_info2).speech_team_count as i32;
133
  if alliance_team_count > 0 {
134
  let mut offset: usize = 0;
135
  for _ in 0..alliance_team_count {
 
136
  let alliance_group =
137
  *((game_info2 as *const u8).add(0x450 + offset) as *const i8) as i32;
138
  if alliance_group >= 0 {
139
+ (*wrapper).team_activity_flags[alliance_group as usize] = 0;
140
  }
 
 
 
141
  offset += TEAM_ENTRY_STRIDE;
142
  }
143
  }
144
  } else {
145
  // Normal mode: set all flags to -1 (0xFFFFFFFF)
146
+ for i in 0..num_teams as usize {
147
+ (*wrapper).team_activity_flags[i] = 0xFFFF_FFFF;
 
 
148
  }
149
 
150
  // Set starting team's flag to 1
 
151
  let starting_flag_team = (*game_info_from_wrapper(wrapper)).game_mode_flag as i32;
152
+ (*wrapper).team_activity_flags[starting_flag_team as usize] = 1;
153
  }
154
 
155
  // Zero CTask sub-object fields for each team's task pointer
 
156
  let num_teams2 = (*game_info_from_wrapper(wrapper)).num_teams as i32;
157
  if num_teams2 > 0 {
 
158
  for i in 0..num_teams2 as usize {
159
+ let task = (*wrapper).team_task_ptrs[i];
160
  if !task.is_null() {
161
  *(task.add(0x18) as *mut u32) = 0;
162
  *(task.add(0x14) as *mut u32) = 0;
 
189
  /// - starting_team_index
190
  ///
191
  /// - wrapper+0x490: first byte used as scoring override value
192
+ pub unsafe fn init_alliance_data(wrapper: *mut DDGameWrapper) {
193
+ // Zero 13 alliance bitmasks
194
+ (*wrapper)._alliance_bitmasks = [0u32; 13];
 
 
195
 
196
  // Build alliance bitmasks: for each team, set bit (alliance_id & 0x1F)
197
  // in bitmask[alliance_group]
198
  let game_info = game_info_from_wrapper(wrapper);
199
  let num_teams = (*game_info).num_teams as i32;
200
 
 
 
 
 
201
  let alliance_team_count = (*game_info).speech_team_count as i32;
202
  if num_teams != 0 && alliance_team_count > 0 {
203
  let mut offset: usize = 0;
 
207
  let alliance_id = *((gi as *const u8).add(0x451 + offset));
208
 
209
  if alliance_group >= 0 {
210
+ (*wrapper)._alliance_bitmasks[alliance_group as usize] |=
211
+ 1u32 << (alliance_id & 0x1F);
212
  }
213
 
214
  offset += TEAM_ENTRY_STRIDE;
 
224
  let alliance_team_count = (*game_info).speech_team_count as i32;
225
 
226
  // Local tracking: which alliance IDs we've already added to auxiliary array
227
+ let mut seen: [u8; 6] = [0; 6];
228
+ let mut aux_count: usize = 0;
229
 
230
  if alliance_team_count > 0 {
231
  let mut team_offset: usize = 0;
 
240
  core::ptr::addr_of_mut!((*ddgame).team_scoring_flags[team_idx as usize]);
241
 
242
  if alliance_group < 0 {
243
+ // No alliance: flag = value from replay_flag_a (i8 sign-extended)
244
+ let override_val = (*wrapper).replay_flag_a as i8 as i32;
245
  *scoring_flag = override_val as u32;
246
  } else {
247
  // Check if this team is allied with the starting team
248
  let gi = game_info_from_wrapper(wrapper);
249
  let num_teams_byte = (*gi).num_teams;
250
+ let override_byte = (*wrapper).replay_flag_a;
251
 
252
  if num_teams_byte == 0 || override_byte != 0 {
 
253
  *scoring_flag = 1;
254
  } else {
255
  let game_version = (*gi).game_version;
256
  let starting_team = (*gi).starting_team_index as i32;
257
 
258
  let team_bitmask: u32 = if game_version < 0x83 {
259
+ (*wrapper)._alliance_bitmasks[alliance_group as usize]
 
260
  } else {
 
261
  1u32 << (alliance_id & 0x1F)
262
  };
263
 
264
+ let starting_bitmask = (*wrapper)._alliance_bitmasks[starting_team as usize];
 
265
 
266
  if (starting_bitmask & team_bitmask) != 0 {
 
267
  *scoring_flag = 1;
268
  }
 
269
  }
270
 
271
  // Build auxiliary array for non-starting-team alliances
 
273
  let starting_team2 = (*gi2).starting_team_index as i32;
274
 
275
  if alliance_group != starting_team2 && seen[alliance_id as usize] == 0 {
276
+ let starting_bitmask2 = (*wrapper)._alliance_bitmasks[starting_team2 as usize];
 
277
  if (starting_bitmask2 & (1u32 << (alliance_id & 0x1F))) != 0 {
278
+ (*wrapper).slot_to_team[aux_count] = (alliance_id + 0x10) as i32;
 
 
279
  aux_count += 1;
280
  seen[alliance_id as usize] = 1;
281
  }
 
379
  }
380
 
381
  /// Bridge to DDGame__InitFeatureFlags (0x524700): stdcall(wrapper), RET 0x4.
382
+ unsafe fn wa_init_feature_flags(wrapper: *mut DDGameWrapper) {
383
+ let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) = core::mem::transmute(
384
+ crate::rebase::rb(crate::address::va::DDGAME_INIT_FEATURE_FLAGS) as usize,
385
+ );
386
  f(wrapper);
387
  }
388
 
 
394
  /// structs. Zeroes camera state, timing fields, per-team flags, and calls
395
  /// DDGame__InitFeatureFlags. Also dispatches a vtable call on the landscape
396
  /// object.
397
+ pub unsafe fn init_turn_state(wrapper: *mut DDGameWrapper) {
398
  let ddgame = ddgame_from_wrapper(wrapper);
399
  let game_info = (*ddgame).game_info;
400
 
401
+ (*wrapper)._field_458 = 0xFFFF_FFFF;
402
+ (*wrapper)._field_450 = 0;
403
+ (*wrapper)._field_454 = 0;
 
404
 
405
  // DDGame+0x72E0/E4 = -1, DDGame+0x72E8 = 0
406
  (*ddgame)._unknown_72e0 = 0xFFFF_FFFF;
 
510
  ///
511
  /// Checks game_info.landscape_scheme_flag and dispatches landscape vtable slot 6 with
512
  /// appropriate parameters, then updates DDGame.level_width_raw.
513
+ pub unsafe fn init_landscape_flags(wrapper: *mut DDGameWrapper) {
514
  let ddgame = ddgame_from_wrapper(wrapper);
515
  let game_info = (*ddgame).game_info;
516
 
 
557
  pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
558
  use crate::address::va;
559
  use crate::rebase::rb;
560
+ use crate::wa_alloc::{wa_malloc, wa_malloc_zeroed};
561
 
 
562
  let ddgame = (*wrapper).ddgame;
 
563
 
564
  // ===== Copy replay mode flags from GameInfo =====
565
  let game_info = (*ddgame).game_info;
 
567
  (*wrapper).replay_flag_b = ((*game_info).invisibility_mode >> 8) as u8;
568
 
569
  // ===== SpriteGfxTable__Init =====
 
570
  {
571
  let game_version = (*game_info).game_version;
572
  let count: u32 = if game_version >= 0x3C { 0x400 } else { 0x100 };
573
+ let table_ptr = core::ptr::addr_of_mut!((*ddgame)._unknown_600) as *mut u8;
574
+ sprite_gfx_table_init(table_ptr, count);
575
  }
576
 
577
  // ===== Allocate HudPanel (0x940 bytes) =====
578
  {
579
+ let mem = wa_malloc_zeroed(0x940);
 
580
  let result = if mem.is_null() {
581
  core::ptr::null_mut()
582
  } else {
 
584
  core::mem::transmute(rb(va::HUD_PANEL_CONSTRUCTOR) as usize);
585
  ctor(mem)
586
  };
587
+ (*ddgame).hud_panel = result;
588
  }
589
 
590
  // ===== Allocate weapon table buffer (0x80D0 bytes) =====
591
  {
592
+ let mem = wa_malloc_zeroed(0x80D0);
593
+ (*ddgame).weapon_table = mem as *mut crate::game::weapon::WeaponTable;
 
594
  }
595
 
596
  // ===== Allocate unknown 0x2C object =====
 
604
  *(mem.add(0x20) as *mut u32) = 0;
605
  *(mem.add(0x24) as *mut u32) = 0;
606
  }
607
+ (*ddgame)._unknown_51c = mem;
608
  }
609
 
610
  // ===== Allocate RenderQueue (0x12028 bytes) =====
611
  {
612
+ let mem = wa_malloc_zeroed(0x12028) as *mut u32;
 
613
  if !mem.is_null() {
614
  *mem.add(0x4001) = 0; // mem[0x10004] = 0
615
  *mem = 0x10000; // mem[0] = 0x10000
616
  }
617
+ (*ddgame).render_queue = mem as *mut crate::render::queue::RenderQueue;
618
  }
619
 
620
  // ===== Allocate GameStateStream (0x264 bytes) =====
621
  {
622
+ let mem = wa_malloc_zeroed(0x264) as *mut u32;
 
623
  if !mem.is_null() {
624
  *mem = rb(0x664194); // GameStateStream vtable
625
  let init: unsafe extern "stdcall" fn(*mut u32) =
626
  core::mem::transmute(rb(va::GAME_STATE_STREAM_INIT) as usize);
627
  init(mem.add(1)); // Init sub-object at offset +4
628
  *mem.add(1) = 0;
629
+ *mem.add(0x90) = ddgame as u32; // DDGame ptr backref
630
  *mem.add(0x8B) = 0;
631
  *mem.add(0x8C) = 0;
632
  }
633
+ (*ddgame).game_state_stream = mem as *mut u8;
634
  }
635
 
636
  // ===== Allocate unknown 0x4A74 object =====
637
  {
638
+ let mem = wa_malloc_zeroed(0x4A74);
 
639
  if !mem.is_null() {
640
  *(mem.add(0x4A50) as *mut u32) = ddgame as u32;
641
  }
642
+ (*ddgame)._unknown_52c = mem;
643
  }
644
 
645
+ // ===== Virtual call: display->vtable[1](&level_width_sound, &screen_height_pixels) =====
 
 
646
  {
647
+ let display = (*ddgame).display;
648
+ let vtable = *(display as *const *const u32);
649
+ let vmethod: unsafe extern "thiscall" fn(*mut DisplayGfx, *mut i32, *mut i32) =
650
  core::mem::transmute(*vtable.add(1));
651
+ vmethod(
652
+ display,
653
+ core::ptr::addr_of_mut!((*ddgame).level_width_sound),
654
+ core::ptr::addr_of_mut!((*ddgame).screen_height_pixels),
655
+ );
656
  }
657
+ (*ddgame)._field_77b0 = 0;
658
 
659
  // ===== Allocate BufferObject (main_buffer at wrapper+0x0C) =====
660
+ (*wrapper).main_buffer = allocate_buffer_object(ddgame, game_info);
 
 
 
661
 
662
  // ===== Allocate RingBuffer A (wrapper+0x3C, capacity 0x2000) =====
663
  (*wrapper).ring_buffer_a = allocate_ring_buffer_raw(0x3C, 0x2000);
 
673
 
674
  // ===== wrapper+0x44: network ring buffer (conditional) =====
675
  (*wrapper).network_ring_buffer = core::ptr::null_mut();
676
+ if (*ddgame).network_ecx != 0 {
677
+ (*wrapper).network_ring_buffer = allocate_ring_buffer_init();
678
  }
679
 
680
  // ===== Allocate state buffer (wrapper+0x48) =====
681
+ (*wrapper).state_buffer = allocate_buffer_object(ddgame, game_info);
682
 
683
  // ===== Allocate statistics object (0xB94 bytes, wrapper+0x4C) =====
684
  {
 
704
  (*wrapper)._field_498 = 0;
705
  (*wrapper)._field_493 = 0;
706
 
707
+ // Zero team task pointers (13 entries)
708
+ (*wrapper).team_task_ptrs = [core::ptr::null_mut(); 13];
 
 
709
 
710
  // ===== Allocate 3 PaletteContexts (0x72C bytes each) =====
711
+ (*wrapper).palette_ctx_a = allocate_palette_context();
712
+ (*wrapper).palette_ctx_b = allocate_palette_context();
713
+ (*wrapper).palette_ctx_c = allocate_palette_context();
714
 
715
  // ===== Select vector normalize function based on game version =====
716
  {
 
723
  }
724
 
725
  // ===== Initialize sentinel fields =====
726
+ (*wrapper)._field_468 = -1;
727
+ (*wrapper)._field_46c = -1;
728
+ (*wrapper)._field_470 = -1;
729
+ (*wrapper).state_initialized = 0;
730
+ (*wrapper)._field_480 = 0;
731
+ (*wrapper)._field_264 = 0;
732
+ (*wrapper).sync_checksum_a = 0;
733
+ (*wrapper).checksum_valid = 0;
734
+ (*wrapper)._field_278 = 0;
735
+ (*wrapper)._field_27c = 0;
736
+ (*wrapper)._field_3fc = 0;
737
+ (*wrapper)._field_3f4 = -1;
738
+ (*wrapper)._field_3f8 = -1;
739
 
740
  // ===== Resolution-dependent team render indices =====
741
  {
742
  let screen_height = (*ddgame).screen_height_pixels;
743
  if screen_height < 0x2D0 {
744
  // < 720px: smaller layout
745
+ (*wrapper).max_team_render_index = 0xC;
746
+ (*wrapper).team_render_indices = [8, 7, 5, 2, 4, 3, 1, 6];
 
 
 
 
 
 
 
747
  } else {
748
  // >= 720px: larger layout
749
+ (*wrapper).max_team_render_index = 0x10;
750
+ (*wrapper).team_render_indices = [0x10, 0xF, 0xD, 10, 0xC, 0xB, 9, 0xE];
 
 
 
 
 
 
 
751
  }
752
 
753
+ (*wrapper).team_count_config = if screen_height < 600 { 7 } else { 10 };
 
 
 
 
754
  }
755
 
756
  // ===== Game mode and sentinel arrays =====
757
+ (*wrapper).game_mode_flag = 1;
758
 
759
+ // Fill team slot mapping with -1 sentinels
760
+ (*wrapper).team_to_slot_a = [-1i32; 8];
761
+ (*wrapper).slot_to_team = [-1i32; 16];
762
+ (*wrapper)._field_3ec = -1;
763
 
764
  // ===== Team-to-slot mapping (conditional on network/replay mode) =====
765
+ if (*ddgame).network_ecx != 0 || (*wrapper).replay_flag_a != 0 {
766
  let team_count = (*game_info).num_teams as i32;
767
  let exclude_team = (*game_info).starting_team_index as i32;
768
+ let mut slot_idx = 0usize;
769
+ // slot_to_team[0..12] overlaps the reverse mapping area (0x3BC = slot_to_team[4])
770
+ let pivar7_base = core::ptr::addr_of_mut!((*wrapper).slot_to_team[4]) as *mut i32;
 
 
771
  for team_id in 0..team_count {
772
  if team_id != exclude_team {
773
+ *pivar7_base.add(team_id as usize) = slot_idx as i32;
774
+ (*wrapper).team_to_slot_a[slot_idx] = team_id;
775
  slot_idx += 1;
776
  } else {
777
  *pivar7_base.add(team_id as usize) = -1;
 
780
  }
781
 
782
  // ===== Game logic initialization =====
783
+ (*wrapper)._field_474 = 0;
784
  (*wrapper).init_flag = 1;
785
 
786
+ // Already-ported sub-functions
787
+ init_team_scoring(wrapper);
788
+ init_alliance_data(wrapper);
789
 
790
  // ===== Game speed/timing fields =====
791
  (*wrapper).health_precision = 500;
792
+ (*wrapper)._field_45c = 0;
793
+ (*wrapper)._field_464 = 0;
794
+ (*wrapper)._field_460 = 0;
795
+ (*wrapper)._field_478 = 0;
796
+ (*wrapper)._field_418 = 0;
797
 
798
  // Turn percentage: (game_info.turn_percentage << 16) / 100
799
  let turn_pct_raw = (*game_info).turn_percentage_raw;
 
803
  let is_headful = (*ddgame).sound_available != 0;
804
 
805
  if is_headful {
806
+ init_game_state_display(wrapper, ddgame, game_info);
 
 
 
 
 
807
  }
808
 
809
  // ===== Worm selection count and terrain config =====
810
  {
811
  let mut val = (*game_info).worm_select_cfg_a;
812
+ let min_teams = (*wrapper).min_active_teams;
813
+ let team_count_cfg = (*wrapper).team_count_config;
814
 
815
  if val == 0 {
816
  val = team_count_cfg;
 
819
  } else if val > 0x20 {
820
  val = 0x20;
821
  }
822
+ (*wrapper).worm_select_count = val;
823
 
824
  val = (*game_info).worm_select_cfg_b;
825
  if val == -1 {
 
829
  } else if val > 0x20 {
830
  val = 0x20;
831
  }
832
+ (*wrapper).worm_select_count_alt = val;
833
 
834
+ (*wrapper)._field_414 = ((*game_info)._field_f365 != 0) as u32;
835
 
836
  if is_headful {
837
+ // DDGame+0x000 = keyboard ptr; keyboard+0x10 = config field
838
+ let keyboard = (*ddgame).keyboard as *mut u8;
839
+ *(keyboard.add(0x10) as *mut u32) = ((*game_info)._field_f370 != 0) as u32;
840
  }
841
  }
842
 
843
  // Ensure worm_select_count >= worm_select_count_alt
844
+ if (*wrapper).worm_select_count < (*wrapper).worm_select_count_alt {
845
+ (*wrapper).worm_select_count = (*wrapper).worm_select_count_alt;
846
  }
847
 
848
  // ===== Zero state fields =====
849
+ (*wrapper)._field_434 = 0;
850
+ (*wrapper)._field_438 = -1;
851
+ (*wrapper)._field_43c = -1;
852
+ (*wrapper)._field_440 = 0;
853
+ (*wrapper)._field_444 = 0;
854
+ (*wrapper)._field_424 = 0;
855
+ (*wrapper)._field_428 = 0;
856
+ (*wrapper)._field_448 = 0;
857
+ (*wrapper)._field_44c = 0;
858
+ (*wrapper)._field_42c = 0;
859
+ (*wrapper)._field_430 = 0;
860
+ (*wrapper)._field_40c = 0;
861
+ (*wrapper)._field_410 = 0;
862
 
863
  // ===== Landscape flags =====
864
+ init_landscape_flags(wrapper);
865
 
866
  // ===== Level bounds and camera initialization =====
867
  init_game_state_level_bounds(ddgame, game_info);
868
 
869
  // ===== Display objects for HUD (headful only) =====
870
  if is_headful {
871
+ init_game_state_hud_objects(ddgame, game_info);
872
  }
873
 
874
  // ===== Team name validation =====
875
+ init_game_state_team_names(ddgame, game_info);
876
 
877
  // ===== Weapon and team initialization =====
878
  (*ddgame).hud_status_code = 0;
 
887
 
888
  // DDGame__InitTeamsFromSetup
889
  {
890
+ let team_arena = core::ptr::addr_of_mut!((*ddgame).team_arena) as u32;
891
+ let gi_ptr = (*ddgame).game_info as u32;
892
  let f: unsafe extern "stdcall" fn(u32, u32) =
893
  core::mem::transmute(rb(va::INIT_TEAMS_FROM_SETUP) as usize);
894
+ f(team_arena, gi_ptr);
 
 
 
895
  }
896
 
897
  // ===== Version-dependent stack/render config =====
898
+ init_game_state_version_config(ddgame, game_info);
899
 
900
  // ===== Water level and wind =====
901
  init_game_state_water_level(ddgame, game_info);
902
 
903
  // ===== Random bag initialization =====
904
+ init_game_state_random_bags(ddgame, game_info);
905
 
906
  // ===== Turn state =====
907
+ init_turn_state(wrapper);
908
 
909
  // ===== Weapon availability loop =====
910
+ init_game_state_weapon_avail(ddgame, game_info);
911
 
912
  // ===== Team/object tracking arrays =====
913
+ init_game_state_tracking_arrays(ddgame, game_info);
914
 
915
  // ===== Statistics counters =====
916
+ {
917
+ let base = core::ptr::addr_of_mut!((*ddgame)._unknown_9890) as *mut u32;
918
+ for i in 0..10usize {
919
+ *base.add(i) = 0;
920
+ }
921
  }
922
 
923
  // ===== TeamManager constructor =====
924
  {
925
+ let mem = wa_malloc_zeroed(0x6C);
 
926
  let result = if mem.is_null() {
927
  core::ptr::null_mut()
928
  } else {
 
930
  core::mem::transmute(rb(va::TEAM_MANAGER_CONSTRUCTOR) as usize);
931
  ctor(mem, ddgame as u32)
932
  };
933
+ (*ddgame).turn_order_widget = result as *mut _;
934
  }
935
 
936
  // ===== Network sync callback =====
937
+ let sound = (*ddgame).sound;
938
+ if !sound.is_null() {
939
+ let vtable = *(sound as *const *const u32);
940
+ let vmethod: unsafe extern "thiscall" fn(*mut DSSound) =
941
+ core::mem::transmute(*vtable.add(1));
942
+ vmethod(sound);
943
  }
944
 
945
  // ===== CTaskTurnGame constructor =====
946
  {
947
  let game_version = (*game_info).game_version;
948
+ let gi_ptr = (*ddgame).game_info as u32;
949
  if game_version == -2 {
950
  // Online game: ECX = *net_bridge (dereferenced!) for the constructor
951
+ let mem = wa_malloc_zeroed(0x324);
 
952
  if mem.is_null() {
953
  (*wrapper).task_turn_game = core::ptr::null_mut();
954
  } else {
955
+ let net_bridge = (*wrapper).net_bridge;
956
+ let ecx_val = *(net_bridge as *const u32); // deref net_bridge to get ECX
957
+ call_ctor_with_ecx(mem, gi_ptr, ecx_val, rb(va::CTASK_TURNGAME_CTOR));
958
+ *(mem.add(0x300) as *mut u32) = net_bridge as u32;
 
 
 
 
 
959
  // Override vtables for online mode
960
  *(mem as *mut u32) = rb(0x669C28);
961
  *(mem.add(0x30) as *mut u32) = rb(0x669C44);
 
963
  }
964
  } else {
965
  // Normal game: ECX must = DDGame for the constructor
966
+ let mem = wa_malloc_zeroed(0x320);
 
967
  let result = if mem.is_null() {
968
  core::ptr::null_mut()
969
  } else {
970
+ call_ctor_with_ecx(mem, gi_ptr, ddgame as u32, rb(va::CTASK_TURNGAME_CTOR))
 
 
 
 
 
971
  };
972
  (*wrapper).task_turn_game = result;
973
  }
974
  }
975
 
976
  // ===== Zero state block (0x98-0xCC, 14 entries) =====
977
+ (*wrapper)._zeroed_098 = [0u32; 14];
 
 
978
 
979
  // ===== Replay/network mode flag =====
980
  {
981
  let replay_a = (*wrapper).replay_flag_a;
982
  let result = if replay_a == 0
983
+ || ((*game_info).replay_config_flag != 0 && (*wrapper)._field_49c > 0xC)
984
  {
985
  0u32
986
  } else {
987
  0xFFFF_FFFFu32
988
  };
989
+ (*ddgame).field_7ef0 = result as i32;
990
  }
991
 
992
+ (*wrapper)._field_4b0 = 0xFFFF_FFFF;
993
+ (*wrapper)._field_4ac = 0;
994
+ (*wrapper)._field_0e0 = 0;
995
 
996
  // 0xEC: (game_info.f340 != 0) - 1 (i.e., 0 if nonzero, 0xFFFFFFFF if zero)
997
+ (*wrapper)._field_0ec = (((*game_info)._field_f340 != 0) as u32).wrapping_sub(1);
998
 
999
  (*game_info)._field_f34c = -1;
1000
  (*wrapper).state_initialized = 1;
 
1008
 
1009
  // ===== Copy game state to statistics buffer =====
1010
  {
1011
+ let src = core::ptr::addr_of!((*ddgame)._unknown_8168) as *const u32;
1012
  let dst = (*wrapper).statistics as *mut u32;
1013
  core::ptr::copy_nonoverlapping(src, dst, 0x2E5);
1014
  }
 
1023
  }
1024
 
1025
  // Get frame counter via landscape vtable
1026
+ let landscape = (*ddgame).landscape;
1027
+ let landscape_vtable = *(landscape as *const *const u32);
1028
+ let get_frame: unsafe extern "thiscall" fn(*mut PCLandscape) -> u32 =
1029
  core::mem::transmute(*landscape_vtable.add(0x12));
1030
+ let frame = get_frame(landscape);
1031
 
1032
  (*wrapper).initial_checksum = frame.wrapping_add(hash);
1033
  }
1034
 
1035
  // ===== Weapon panel (headful only) =====
1036
  if is_headful {
1037
+ let mem = wa_malloc_zeroed(0x208);
 
1038
  let result = if mem.is_null() {
1039
  core::ptr::null_mut()
1040
  } else {
1041
  // usercall: ESI=this(mem), stack=(DDGame), RET 0x4
1042
  call_usercall_esi_stack1(mem, ddgame as u32, rb(va::INIT_WEAPON_PANEL))
1043
  };
1044
+ (*ddgame).weapon_panel = result;
1045
  }
1046
  }
1047
 
 
1051
 
1052
  /// Allocate a BufferObject (0x48 bytes) with size computed from game setup.
1053
  /// Used for main_buffer and state_buffer.
1054
+ unsafe fn allocate_buffer_object(ddgame: *mut DDGame, game_info: *const GameInfo) -> *mut u8 {
1055
  use crate::address::va;
1056
  use crate::rebase::rb;
1057
+ use crate::wa_alloc::wa_malloc_zeroed;
1058
 
1059
+ let mem = wa_malloc_zeroed(0x48) as *mut u32;
 
 
 
 
1060
  if mem.is_null() {
1061
  return core::ptr::null_mut();
1062
  }
 
1067
 
1068
  let ctor: unsafe extern "stdcall" fn(*mut u32, u32, u32) -> *mut u8 =
1069
  core::mem::transmute(rb(va::BUFFER_OBJECT_CONSTRUCTOR) as usize);
1070
+ ctor(mem, size, ddgame as u32)
1071
  }
1072
 
1073
  /// Allocate a raw ring-buffer-like object with manual field initialization.
 
1075
  /// `alloc_size` = total allocation size (0x3C or 0x48).
1076
  /// `capacity` = buffer capacity (0x1000, 0x2000, or 0x10000).
1077
  unsafe fn allocate_ring_buffer_raw(alloc_size: u32, capacity: u32) -> *mut u8 {
1078
+ use crate::wa_alloc::{wa_malloc, wa_malloc_zeroed};
1079
 
1080
+ let mem = wa_malloc_zeroed(alloc_size) as *mut u32;
 
 
 
 
 
1081
  if mem.is_null() {
1082
  return core::ptr::null_mut();
1083
  }
 
1097
 
1098
  /// Allocate a RingBuffer using the already-ported ring_buffer_init function.
1099
  /// Used for the conditional network ring buffer.
1100
+ unsafe fn allocate_ring_buffer_init() -> *mut u8 {
1101
+ use crate::wa_alloc::wa_malloc_zeroed;
1102
 
1103
+ let mem = wa_malloc_zeroed(0x3C);
 
 
 
1104
  if mem.is_null() {
1105
  return core::ptr::null_mut();
1106
  }
1107
+ ring_buffer_init(mem, 0x2000);
1108
+ mem
1109
  }
1110
 
1111
  /// Allocate and initialize a PaletteContext (0x72C bytes).
1112
  /// Sets dirty_range_min=1, dirty_range_max=0xFF, then calls PaletteContext__Init.
1113
+ unsafe fn allocate_palette_context() -> *mut crate::render::palette::PaletteContext {
 
 
1114
  use crate::address::va;
1115
  use crate::rebase::rb;
1116
+ use crate::wa_alloc::wa_malloc_zeroed;
1117
 
1118
+ let mem = wa_malloc_zeroed(0x72C) as *mut u16;
 
1119
  if mem.is_null() {
1120
  return core::ptr::null_mut();
1121
  }
 
1191
  /// Creates DisplayGfx layers, camera objects, and the main display surface.
1192
  unsafe fn init_game_state_display(
1193
  wrapper: *mut DDGameWrapper,
1194
+ ddgame: *mut DDGame,
 
1195
  game_info: *const GameInfo,
1196
  ) {
1197
  use crate::address::va;
1198
  use crate::rebase::rb;
1199
+ use crate::wa_alloc::wa_malloc_zeroed;
1200
 
1201
+ let max_team_render = (*wrapper).max_team_render_index;
1202
 
1203
  // Display virtual call: display->vtable[1](&display_width, &display_height)
 
1204
  let mut display_width: u32 = 0;
1205
  let mut display_height: u32 = 0;
1206
  {
1207
+ let display = (*ddgame).display;
1208
+ let vtable = *(display as *const *const u32);
1209
+ let vmethod: unsafe extern "thiscall" fn(*mut DisplayGfx, *mut u32, *mut u32) =
1210
  core::mem::transmute(*vtable.add(1));
1211
+ vmethod(display, &mut display_width, &mut display_height);
1212
  }
1213
 
1214
  // Screen height for HUD: 0x12C (300) if wide layout, 0x8C (140) if narrow
 
1217
  } else {
1218
  0x8C // 140
1219
  };
1220
+ (*wrapper).screen_height_hud = screen_height;
1221
 
1222
  // Count active teams from slot_to_team array
1223
  let team_count = (*game_info).num_teams as u32;
1224
+ let count_active = |wrapper: *mut DDGameWrapper, base: u32| -> u32 {
1225
  let mut n = base;
1226
+ if (*wrapper).slot_to_team[0] >= 0 {
1227
+ let mut idx = 1;
1228
  loop {
 
1229
  n += 1;
1230
+ if (*wrapper).slot_to_team[idx] < 0 {
1231
  break;
1232
  }
1233
+ idx += 1;
1234
  }
1235
  }
1236
  n
1237
  };
1238
+ let active_count = count_active(wrapper, team_count);
1239
 
1240
  // min_teams: (int)(active_count - 1) < 2 ? 1 : active_count - 1
1241
  let min_teams: i32 = if (active_count.wrapping_sub(1) as i32) < 2 {
1242
  1
1243
  } else {
1244
+ count_active(wrapper, team_count) as i32 - 1
1245
  };
1246
+ (*wrapper).min_active_teams = min_teams;
1247
 
1248
  // screen_offset = display_width - screen_height
1249
+ (*wrapper).screen_offset = display_width as i32 - screen_height;
1250
  let screen_offset = display_width as i32 - screen_height;
1251
 
1252
  // ===== Layer 1 (wrapper+0x18): BitGrid(8, max_team*64+2, screen_offset-7) =====
 
1270
  }
1271
 
1272
  // ===== ConstructFull for main display (wrapper+0x24) =====
1273
+ // usercall: ECX=gfx_color_table[7], EDX=max_team_render_index
1274
  // stdcall stack: (this, display, team_idx, screen_offset-3, render_param)
1275
  {
1276
+ let mem = wa_malloc_zeroed(0x468);
 
1277
  let result = if mem.is_null() {
1278
  core::ptr::null_mut()
1279
  } else {
1280
  {
1281
+ let p_display = (*ddgame).display as u32;
1282
+ let p_team = (*wrapper).team_render_indices[0] as u32;
1283
  let p_offset = screen_offset - 3;
1284
+ let p_render = (*ddgame).gfx_color_table[8]; // [8] = 0x732C
1285
+ let p_ecx = (*ddgame).gfx_color_table[7]; // [7] = 0x7328
1286
  let p_edx = max_team_render as u32;
1287
  let target = rb(va::DISPLAYGFX_CONSTRUCT_FULL);
 
1288
  let f: unsafe extern "fastcall" fn(
1289
  u32,
1290
  u32,
 
1303
  // Turn timer
1304
  {
1305
  let timer_val = max_team_render << 5;
1306
+ (*wrapper).turn_timer_max = timer_val;
1307
+ (*wrapper).turn_timer_current = timer_val;
1308
+ (*wrapper)._field_404 = 0;
1309
  }
1310
 
1311
  // Dirty rect / clipping calls on layers A, B, C
1312
+ call_display_gfx_dirty_rect((*wrapper).display_gfx_a, ddgame);
1313
+ call_display_gfx_dirty_rect((*wrapper).display_gfx_b, ddgame);
1314
+ call_display_gfx_dirty_rect((*wrapper).display_gfx_c, ddgame);
1315
 
1316
  // ===== Layer 4 (wrapper+0x2C): BitGrid(8, 0x100, 0x154) β€” constant =====
1317
  (*wrapper).display_gfx_d = create_display_gfx_layer_sized(0x100, 0x154);
 
1320
  (*wrapper).display_gfx_e = create_display_gfx_layer_sized(0x30, 0xC0);
1321
 
1322
  // Create 2 camera objects (wrapper+0x30, +0x38)
1323
+ (*wrapper).camera_a = create_camera_object(wrapper, ddgame, 0x2C);
1324
+ (*wrapper).camera_b = create_camera_object(wrapper, ddgame, 0x34);
1325
  }
1326
 
1327
  /// Create a DisplayGfx layer (0x4C bytes): malloc + memset + BitGrid__Init + vtable.
 
1329
  unsafe fn create_display_gfx_layer_sized(height: u32, width: u32) -> *mut u8 {
1330
  use crate::bitgrid::{BitGrid, BIT_GRID_DISPLAY_VTABLE};
1331
  use crate::rebase::rb;
1332
+ use crate::wa_alloc::wa_malloc_zeroed;
1333
 
1334
+ let mem = wa_malloc_zeroed(0x4C) as *mut u32;
 
1335
  if mem.is_null() {
1336
  return core::ptr::null_mut();
1337
  }
 
1343
  }
1344
 
1345
  /// Call the DisplayGfx dirty rect/clipping vtable dispatch (slot 0).
1346
+ unsafe fn call_display_gfx_dirty_rect(gfx: *mut u8, ddgame: *mut DDGame) {
1347
  if gfx.is_null() {
1348
  return;
1349
  }
 
1378
  let vtable = *(gfx as *const *const u32);
1379
  let vmethod: unsafe extern "thiscall" fn(*mut u8, i32, i32, i32, i32, u32) =
1380
  core::mem::transmute(*vtable);
1381
+ vmethod(gfx, x1, y1, x2, y2, (*ddgame).gfx_color_table[7]);
1382
  }
1383
 
1384
  /// Create a camera/display object (0x3D4 bytes).
1385
  unsafe fn create_camera_object(
1386
  wrapper: *mut DDGameWrapper,
1387
+ ddgame: *mut DDGame,
1388
  display_gfx_offset: usize,
1389
  ) -> *mut u8 {
1390
+ use crate::wa_alloc::wa_malloc_zeroed;
1391
 
1392
+ let mem = wa_malloc_zeroed(0x3D4) as *mut i32;
 
1393
  if mem.is_null() {
1394
  return core::ptr::null_mut();
1395
  }
1396
 
1397
+ let w = wrapper as *const u8;
1398
+ let display_gfx = *(w.add(display_gfx_offset) as *const *const u8);
1399
 
1400
+ *mem.add(1) = (*ddgame).display as i32;
1401
+ *mem.add(3) = (*ddgame).gfx_color_table[0] as i32;
1402
+ *mem.add(2) = (*ddgame).gfx_color_table[7] as i32;
1403
+ *mem = display_gfx as i32;
1404
 
1405
+ let w_val = *(display_gfx.add(0x14) as *const i32);
1406
+ let h_val = *(display_gfx.add(0x18) as *const i32);
1407
 
1408
  *mem.add(9) = w_val;
1409
  *mem.add(4) = w_val / 2;
 
 
 
1410
  *mem.add(10) = h_val;
1411
  *mem.add(5) = h_val / 2;
 
 
1412
 
1413
  mem as *mut u8
1414
  }
 
1463
  }
1464
 
1465
  /// Create HUD display objects (headful only, conditional on is_headful).
1466
+ unsafe fn init_game_state_hud_objects(ddgame: *mut DDGame, _game_info: *const GameInfo) {
 
 
 
 
1467
  use crate::address::va;
1468
  use crate::rebase::rb;
1469
+ use crate::wa_alloc::wa_malloc_zeroed;
1470
 
1471
  // DisplayObject for HUD background
1472
+ // usercall: ECX=gfx_color_table[6], EDX=gfx_color_table[7], stdcall stack=(this, DDGame+0x33C)
1473
  {
1474
+ let mem = wa_malloc_zeroed(0x58) as *mut u32;
 
1475
  let result = if mem.is_null() {
1476
  core::ptr::null_mut()
1477
  } else {
1478
  let ctor: unsafe extern "fastcall" fn(u32, u32, *mut u32, u32) -> *mut u8 =
1479
  core::mem::transmute(rb(va::DISPLAY_OBJECT_CONSTRUCTOR) as usize);
1480
  ctor(
1481
+ (*ddgame).gfx_color_table[6], // ECX
1482
+ (*ddgame).gfx_color_table[7], // EDX
1483
  mem,
1484
+ core::ptr::addr_of!((*ddgame).sprite_cache[128]) as u32,
1485
  )
1486
  };
1487
+ (*ddgame)._unknown_540 = result;
1488
  }
1489
 
1490
  // DisplayGfx textbox for HUD text
1491
  // thiscall: ECX=DDGame.display, stack=(this, 0x13, 2)
1492
  {
1493
+ let mem = wa_malloc_zeroed(0x158) as *mut u32;
 
1494
  let result = if mem.is_null() {
1495
  core::ptr::null_mut()
1496
  } else {
1497
+ let ctor: unsafe extern "thiscall" fn(*mut DisplayGfx, *mut u32, u32, u32) -> *mut u8 =
1498
  core::mem::transmute(rb(va::CONSTRUCT_TEXTBOX) as usize);
1499
+ ctor((*ddgame).display, mem, 0x13, 2)
 
 
 
 
 
1500
  };
1501
+ (*ddgame)._unknown_544 = result;
1502
  }
1503
  }
1504
 
1505
  /// Validate team names β€” check for CPU team markers.
1506
+ unsafe fn init_game_state_team_names(ddgame: *mut DDGame, game_info: *mut GameInfo) {
 
 
1507
  // Set initial team color from game_info
1508
  (*ddgame).team_color = (*game_info).team_color_source as u32;
1509
 
 
1523
  }
1524
 
1525
  /// Version-dependent configuration (stack size, rendering constants).
1526
+ unsafe fn init_game_state_version_config(ddgame: *mut DDGame, game_info: *const GameInfo) {
 
1527
  let game_version = (*game_info).game_version;
1528
 
1529
  // Stack height: game_version < -1 β†’ 0x700, >= -1 β†’ 0x800
 
1532
  // Copy RNG seed
1533
  let rng_seed = (*game_info).rng_seed;
1534
  (*ddgame).game_rng = rng_seed;
1535
+ (*ddgame).team_health_ratio[0] = rng_seed as i32;
1536
 
1537
  // Zero various fields
1538
  (*ddgame).frame_counter = 0;
 
1542
  (*ddgame)._field_7e4c = 0;
1543
  (*ddgame)._field_5d0 = 0;
1544
 
1545
+ // Zero counter arrays (6 entries each Γ— 3 blocks starting at turn_time_limit)
1546
+ {
1547
+ let base = core::ptr::addr_of_mut!((*ddgame).turn_time_limit) as *mut u32;
1548
+ for i in 0..18usize {
1549
+ *base.add(i) = 0;
1550
  }
1551
  }
1552
 
 
1606
  }
1607
 
1608
  /// Random bag initialization (land, mine, barrel, weapon).
1609
+ unsafe fn init_game_state_random_bags(ddgame: *mut DDGame, game_info: *mut GameInfo) {
1610
+ let ddgame_raw = ddgame as *mut u8;
1611
  // Random bag at DDGame+0x360C (5 zeroes + 1 one)
1612
  {
1613
  let write_idx_ptr = ddgame_raw.add(0x379C) as *mut i32;
 
1740
  }
1741
 
1742
  /// Weapon availability loop using already-ported check_weapon_avail.
1743
+ unsafe fn init_game_state_weapon_avail(ddgame: *mut DDGame, game_info: *const GameInfo) {
1744
+ let ddgame_raw = ddgame as *mut u8;
 
 
 
1745
  let game_version = (*game_info).game_version;
1746
 
1747
  for weapon_id in 1..0x47i32 {
1748
+ let avail = check_weapon_avail(ddgame, weapon_id as u32);
1749
  let is_avail = if game_version < 0x4D {
1750
  avail != 0
1751
  } else {
 
1764
  }
1765
 
1766
  /// Allocate team/object tracking arrays and initialize the 0x51C object.
1767
+ unsafe fn init_game_state_tracking_arrays(ddgame: *mut DDGame, game_info: *const GameInfo) {
1768
  use crate::wa_alloc::wa_malloc;
1769
 
 
 
1770
  // Game speed
1771
  (*ddgame).game_speed = Fixed::ONE;
1772
 
 
1808
  let count_a = (*game_info).team_slot_count;
1809
  let size = count_a.wrapping_mul(4);
1810
  let arr = wa_malloc(size);
1811
+ (*ddgame)._unknown_514 = arr;
1812
  for i in 0..count_a as usize {
1813
  *(arr as *mut u32).add(i) = 0;
1814
  }
 
1817
  let count_b = (*game_info).object_slot_count;
1818
  let size = count_b.wrapping_mul(4);
1819
  let arr = wa_malloc(size);
1820
+ (*ddgame)._unknown_518 = arr;
1821
  for i in 0..count_b as usize {
1822
  *(arr as *mut u32).add(i) = 0;
1823
  }
 
1825
 
1826
  // Initialize the 0x51C object (vector-like structure)
1827
  {
1828
+ let obj = (*ddgame)._unknown_51c;
1829
  if !obj.is_null() {
1830
  *(obj as *mut u32) = 0; // first field = 0
1831
  // The vector operations (FUN_005370c0, etc.) are complex.
crates/openwa-dll/src/replacements/ddgame_init.rs CHANGED
@@ -34,8 +34,8 @@ hook::usercall_trampoline!(
34
  reg = edi
35
  );
36
 
37
- extern "cdecl" fn impl_init_fields(ddgame: u32) -> u32 {
38
- unsafe { ddgame_init_fields(ddgame as *mut DDGame) }
39
  ddgame // Original: MOV EAX, EDI; RET
40
  }
41
 
@@ -90,8 +90,8 @@ hook::usercall_trampoline!(
90
  reg = esi
91
  );
92
 
93
- extern "cdecl" fn impl_display_layer_init(wrapper: u32) -> u32 {
94
- unsafe { display_layer_color_init(wrapper as *mut DDGameWrapper) }
95
  wrapper
96
  }
97
 
@@ -303,15 +303,15 @@ unsafe extern "C" fn ring_buffer_init_trampoline() {
303
  // ─── CGameTask__InitTeamScoring (0x528510) ──────────────────────────────────
304
  // Convention: fastcall(ECX=wrapper), plain RET.
305
 
306
- unsafe extern "fastcall" fn init_team_scoring_trampoline(wrapper: u32, _edx: u32) {
307
- init_team_scoring(wrapper as *mut u8);
308
  }
309
 
310
  // ─── CGameTask__InitAllianceData (0x5262D0) ─────────────────────────────────
311
  // Convention: usercall(EAX=wrapper), plain RET.
312
 
313
- extern "cdecl" fn impl_init_alliance_data(wrapper: u32) {
314
- unsafe { init_alliance_data(wrapper as *mut u8) }
315
  }
316
 
317
  #[unsafe(naked)]
@@ -330,8 +330,8 @@ unsafe extern "C" fn init_alliance_data_trampoline() {
330
  // ─── CGameTask__InitTurnState (0x528690) ────────────────────────────────────
331
  // Convention: usercall(EAX=wrapper), plain RET.
332
 
333
- extern "cdecl" fn impl_init_turn_state(wrapper: u32) {
334
- unsafe { init_turn_state(wrapper as *mut u8) }
335
  }
336
 
337
  #[unsafe(naked)]
@@ -351,8 +351,8 @@ unsafe extern "C" fn init_turn_state_trampoline() {
351
  // Convention: fastcall(ECX=ddgame) + unaff_ESI=weapon_index, plain RET.
352
  // Returns i32 in EAX.
353
 
354
- extern "cdecl" fn impl_check_weapon_avail(ddgame: u32, weapon_index: u32) -> i32 {
355
- unsafe { check_weapon_avail(ddgame as *mut DDGame, weapon_index) }
356
  }
357
 
358
  #[unsafe(naked)]
@@ -372,8 +372,8 @@ unsafe extern "C" fn check_weapon_avail_trampoline() {
372
  // ─── CGameTask__InitLandscapeFlags (0x528480) ───────────────────────────────
373
  // Convention: usercall(EAX=wrapper), plain RET.
374
 
375
- extern "cdecl" fn impl_init_landscape_flags(wrapper: u32) {
376
- unsafe { init_landscape_flags(wrapper as *mut u8) }
377
  }
378
 
379
  #[unsafe(naked)]
 
34
  reg = edi
35
  );
36
 
37
+ extern "cdecl" fn impl_init_fields(ddgame: *mut DDGame) -> *mut DDGame {
38
+ unsafe { ddgame_init_fields(ddgame) }
39
  ddgame // Original: MOV EAX, EDI; RET
40
  }
41
 
 
90
  reg = esi
91
  );
92
 
93
+ extern "cdecl" fn impl_display_layer_init(wrapper: *mut DDGameWrapper) -> *mut DDGameWrapper {
94
+ unsafe { display_layer_color_init(wrapper) }
95
  wrapper
96
  }
97
 
 
303
  // ─── CGameTask__InitTeamScoring (0x528510) ──────────────────────────────────
304
  // Convention: fastcall(ECX=wrapper), plain RET.
305
 
306
+ unsafe extern "fastcall" fn init_team_scoring_trampoline(wrapper: *mut DDGameWrapper, _edx: u32) {
307
+ init_team_scoring(wrapper);
308
  }
309
 
310
  // ─── CGameTask__InitAllianceData (0x5262D0) ─────────────────────────────────
311
  // Convention: usercall(EAX=wrapper), plain RET.
312
 
313
+ extern "cdecl" fn impl_init_alliance_data(wrapper: *mut DDGameWrapper) {
314
+ unsafe { init_alliance_data(wrapper) }
315
  }
316
 
317
  #[unsafe(naked)]
 
330
  // ─── CGameTask__InitTurnState (0x528690) ────────────────────────────────────
331
  // Convention: usercall(EAX=wrapper), plain RET.
332
 
333
+ extern "cdecl" fn impl_init_turn_state(wrapper: *mut DDGameWrapper) {
334
+ unsafe { init_turn_state(wrapper) }
335
  }
336
 
337
  #[unsafe(naked)]
 
351
  // Convention: fastcall(ECX=ddgame) + unaff_ESI=weapon_index, plain RET.
352
  // Returns i32 in EAX.
353
 
354
+ extern "cdecl" fn impl_check_weapon_avail(ddgame: *mut DDGame, weapon_index: u32) -> i32 {
355
+ unsafe { check_weapon_avail(ddgame, weapon_index) }
356
  }
357
 
358
  #[unsafe(naked)]
 
372
  // ─── CGameTask__InitLandscapeFlags (0x528480) ───────────────────────────────
373
  // Convention: usercall(EAX=wrapper), plain RET.
374
 
375
+ extern "cdecl" fn impl_init_landscape_flags(wrapper: *mut DDGameWrapper) {
376
+ unsafe { init_landscape_flags(wrapper) }
377
  }
378
 
379
  #[unsafe(naked)]