refactor: replace raw pointer arithmetic with typed field access in game_state_init
Browse filesEliminate ~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>
|
@@ -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-
|
| 355 |
-
pub _unknown_77ac:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
|
@@ -74,10 +74,10 @@ pub struct DDGameWrapper {
|
|
| 74 |
/// 0x050: RingBuffer C (capacity 0x1000)
|
| 75 |
pub ring_buffer_c: *mut u8,
|
| 76 |
|
| 77 |
-
// ===== 0x054-0x087:
|
| 78 |
-
/// 0x054
|
| 79 |
-
///
|
| 80 |
-
pub
|
| 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
|
| 107 |
-
/// 0x0F4
|
| 108 |
-
|
| 109 |
-
///
|
| 110 |
-
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-
|
| 146 |
-
pub _unknown_2b4: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
|
@@ -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::
|
|
|
|
| 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 =
|
| 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
|
| 70 |
-
|
| 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
|
| 77 |
-
|
| 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
|
| 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;
|
| 107 |
-
let value_b = scoring_param_b * 50;
|
| 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 |
-
|
| 114 |
-
|
| 115 |
-
*
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
*p.add(99) = 1; // +0x2BC[i]: array_6 β set to 1
|
| 121 |
}
|
| 122 |
|
| 123 |
-
// Mark the starting team
|
| 124 |
-
let starting_team = (*game_info).starting_team_index as
|
| 125 |
-
|
| 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 |
-
|
| 139 |
-
|
| 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 |
-
*
|
| 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 |
-
|
| 166 |
-
|
| 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 |
-
*
|
| 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 = *
|
| 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
|
| 217 |
-
// Zero 13 alliance bitmasks
|
| 218 |
-
|
| 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 |
-
|
| 241 |
-
|
| 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];
|
| 258 |
-
let mut aux_count: usize = 0;
|
| 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
|
| 274 |
-
let override_val =
|
| 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 =
|
| 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 |
-
|
| 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 |
-
|
| 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
|
| 422 |
-
let f: unsafe extern "stdcall" fn(*mut
|
| 423 |
-
crate::address::va::DDGAME_INIT_FEATURE_FLAGS,
|
| 424 |
-
)
|
| 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
|
| 437 |
let ddgame = ddgame_from_wrapper(wrapper);
|
| 438 |
let game_info = (*ddgame).game_info;
|
| 439 |
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 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
|
| 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 |
-
|
|
|
|
| 617 |
}
|
| 618 |
|
| 619 |
// ===== Allocate HudPanel (0x940 bytes) =====
|
| 620 |
{
|
| 621 |
-
let mem =
|
| 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 |
-
|
| 631 |
}
|
| 632 |
|
| 633 |
// ===== Allocate weapon table buffer (0x80D0 bytes) =====
|
| 634 |
{
|
| 635 |
-
let mem =
|
| 636 |
-
|
| 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 |
-
|
| 652 |
}
|
| 653 |
|
| 654 |
// ===== Allocate RenderQueue (0x12028 bytes) =====
|
| 655 |
{
|
| 656 |
-
let mem =
|
| 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 |
-
|
| 663 |
}
|
| 664 |
|
| 665 |
// ===== Allocate GameStateStream (0x264 bytes) =====
|
| 666 |
{
|
| 667 |
-
let mem =
|
| 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; //
|
| 676 |
*mem.add(0x8B) = 0;
|
| 677 |
*mem.add(0x8C) = 0;
|
| 678 |
}
|
| 679 |
-
|
| 680 |
}
|
| 681 |
|
| 682 |
// ===== Allocate unknown 0x4A74 object =====
|
| 683 |
{
|
| 684 |
-
let mem =
|
| 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 |
-
|
| 690 |
}
|
| 691 |
|
| 692 |
-
// ===== Virtual call: display->vtable[1](
|
| 693 |
-
// Ghidra: (**(code **)(**(int **)(iVar4 + 4) + 4))(iVar4 + 0x77b8, iVar4 + 0x77bc)
|
| 694 |
-
// DDGame+4 = display ptr, deref for vtable, slot 1
|
| 695 |
{
|
| 696 |
-
let
|
| 697 |
-
let vtable = *(
|
| 698 |
-
let vmethod: unsafe extern "thiscall" fn(*
|
| 699 |
core::mem::transmute(*vtable.add(1));
|
| 700 |
-
vmethod(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 701 |
}
|
| 702 |
-
|
| 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
|
| 725 |
-
(*wrapper).network_ring_buffer = allocate_ring_buffer_init(
|
| 726 |
}
|
| 727 |
|
| 728 |
// ===== Allocate state buffer (wrapper+0x48) =====
|
| 729 |
-
(*wrapper).state_buffer = allocate_buffer_object(
|
| 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
|
| 756 |
-
|
| 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(
|
| 762 |
-
(*wrapper).palette_ctx_b = allocate_palette_context(
|
| 763 |
-
(*wrapper).palette_ctx_c = allocate_palette_context(
|
| 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 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 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 |
-
|
| 796 |
-
|
| 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 |
-
|
| 807 |
-
|
| 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 |
-
|
| 826 |
|
| 827 |
-
// Fill team slot mapping with -1 sentinels
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
|
| 832 |
// ===== Team-to-slot mapping (conditional on network/replay mode) =====
|
| 833 |
-
if
|
| 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 =
|
| 837 |
-
|
| 838 |
-
let pivar7_base =
|
| 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 |
-
*
|
| 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 |
-
|
| 854 |
(*wrapper).init_flag = 1;
|
| 855 |
|
| 856 |
-
// Already-ported sub-functions
|
| 857 |
-
init_team_scoring(wrapper
|
| 858 |
-
init_alliance_data(wrapper
|
| 859 |
|
| 860 |
// ===== Game speed/timing fields =====
|
| 861 |
(*wrapper).health_precision = 500;
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 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 |
-
|
| 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 =
|
| 888 |
-
let team_count_cfg =
|
| 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 |
-
|
| 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 |
-
|
| 908 |
|
| 909 |
-
|
| 910 |
|
| 911 |
if is_headful {
|
| 912 |
-
|
| 913 |
-
|
|
|
|
| 914 |
}
|
| 915 |
}
|
| 916 |
|
| 917 |
// Ensure worm_select_count >= worm_select_count_alt
|
| 918 |
-
if
|
| 919 |
-
|
| 920 |
}
|
| 921 |
|
| 922 |
// ===== Zero state fields =====
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
|
| 937 |
// ===== Landscape flags =====
|
| 938 |
-
init_landscape_flags(wrapper
|
| 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(
|
| 946 |
}
|
| 947 |
|
| 948 |
// ===== Team name validation =====
|
| 949 |
-
init_game_state_team_names(
|
| 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(
|
| 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(
|
| 980 |
|
| 981 |
// ===== Turn state =====
|
| 982 |
-
init_turn_state(wrapper
|
| 983 |
|
| 984 |
// ===== Weapon availability loop =====
|
| 985 |
-
init_game_state_weapon_avail(
|
| 986 |
|
| 987 |
// ===== Team/object tracking arrays =====
|
| 988 |
-
init_game_state_tracking_arrays(
|
| 989 |
|
| 990 |
// ===== Statistics counters =====
|
| 991 |
-
|
| 992 |
-
|
|
|
|
|
|
|
|
|
|
| 993 |
}
|
| 994 |
|
| 995 |
// ===== TeamManager constructor =====
|
| 996 |
{
|
| 997 |
-
let mem =
|
| 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 |
-
|
| 1007 |
}
|
| 1008 |
|
| 1009 |
// ===== Network sync callback =====
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
let vtable = *(
|
| 1013 |
-
let vmethod: unsafe extern "thiscall" fn(*
|
| 1014 |
-
|
|
|
|
| 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 =
|
| 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
|
| 1028 |
-
let ecx_val = *
|
| 1029 |
-
call_ctor_with_ecx(
|
| 1030 |
-
|
| 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 =
|
| 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 |
-
|
| 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 &&
|
| 1069 |
{
|
| 1070 |
0u32
|
| 1071 |
} else {
|
| 1072 |
0xFFFF_FFFFu32
|
| 1073 |
};
|
| 1074 |
-
|
| 1075 |
}
|
| 1076 |
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
-
|
| 1080 |
|
| 1081 |
// 0xEC: (game_info.f340 != 0) - 1 (i.e., 0 if nonzero, 0xFFFFFFFF if zero)
|
| 1082 |
-
|
| 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 =
|
| 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
|
| 1112 |
-
let landscape_vtable = *(
|
| 1113 |
-
let get_frame: unsafe extern "thiscall" fn(*
|
| 1114 |
core::mem::transmute(*landscape_vtable.add(0x12));
|
| 1115 |
-
let frame = get_frame(
|
| 1116 |
|
| 1117 |
(*wrapper).initial_checksum = frame.wrapping_add(hash);
|
| 1118 |
}
|
| 1119 |
|
| 1120 |
// ===== Weapon panel (headful only) =====
|
| 1121 |
if is_headful {
|
| 1122 |
-
let mem =
|
| 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 |
-
|
| 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(
|
| 1141 |
use crate::address::va;
|
| 1142 |
use crate::rebase::rb;
|
| 1143 |
-
use crate::wa_alloc::
|
| 1144 |
|
| 1145 |
-
let mem =
|
| 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,
|
| 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
|
| 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(
|
| 1196 |
-
use crate::wa_alloc::
|
| 1197 |
|
| 1198 |
-
let mem =
|
| 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
|
| 1206 |
-
mem
|
| 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::
|
| 1217 |
|
| 1218 |
-
let mem =
|
| 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 |
-
|
| 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::
|
| 1302 |
|
| 1303 |
-
let max_team_render =
|
| 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
|
| 1311 |
-
let vtable = *(
|
| 1312 |
-
let vmethod: unsafe extern "thiscall" fn(*
|
| 1313 |
core::mem::transmute(*vtable.add(1));
|
| 1314 |
-
vmethod(
|
| 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 |
-
|
| 1324 |
|
| 1325 |
// Count active teams from slot_to_team array
|
| 1326 |
let team_count = (*game_info).num_teams as u32;
|
| 1327 |
-
let count_active = |
|
| 1328 |
let mut n = base;
|
| 1329 |
-
if
|
| 1330 |
-
let mut
|
| 1331 |
loop {
|
| 1332 |
-
p = p.add(1);
|
| 1333 |
n += 1;
|
| 1334 |
-
if *
|
| 1335 |
break;
|
| 1336 |
}
|
|
|
|
| 1337 |
}
|
| 1338 |
}
|
| 1339 |
n
|
| 1340 |
};
|
| 1341 |
-
let active_count = count_active(
|
| 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(
|
| 1348 |
};
|
| 1349 |
-
|
| 1350 |
|
| 1351 |
// screen_offset = display_width - screen_height
|
| 1352 |
-
|
| 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=
|
| 1377 |
// stdcall stack: (this, display, team_idx, screen_offset-3, render_param)
|
| 1378 |
{
|
| 1379 |
-
let mem =
|
| 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 =
|
| 1386 |
-
let p_team =
|
| 1387 |
let p_offset = screen_offset - 3;
|
| 1388 |
-
let p_render =
|
| 1389 |
-
let p_ecx =
|
| 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 |
-
|
| 1412 |
-
|
| 1413 |
-
|
| 1414 |
}
|
| 1415 |
|
| 1416 |
// Dirty rect / clipping calls on layers A, B, C
|
| 1417 |
-
call_display_gfx_dirty_rect((*wrapper).display_gfx_a,
|
| 1418 |
-
call_display_gfx_dirty_rect((*wrapper).display_gfx_b,
|
| 1419 |
-
call_display_gfx_dirty_rect((*wrapper).display_gfx_c,
|
| 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,
|
| 1429 |
-
(*wrapper).camera_b = create_camera_object(wrapper,
|
| 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::
|
| 1438 |
|
| 1439 |
-
let mem =
|
| 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,
|
| 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,
|
| 1488 |
}
|
| 1489 |
|
| 1490 |
/// Create a camera/display object (0x3D4 bytes).
|
| 1491 |
unsafe fn create_camera_object(
|
| 1492 |
wrapper: *mut DDGameWrapper,
|
| 1493 |
-
|
| 1494 |
display_gfx_offset: usize,
|
| 1495 |
) -> *mut u8 {
|
| 1496 |
-
use crate::wa_alloc::
|
| 1497 |
|
| 1498 |
-
let mem =
|
| 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 *
|
| 1505 |
-
let display_gfx = *(w.add(display_gfx_offset) as *const
|
| 1506 |
|
| 1507 |
-
*mem.add(1) =
|
| 1508 |
-
*mem.add(3) =
|
| 1509 |
-
*mem.add(2) =
|
| 1510 |
-
*mem = display_gfx;
|
| 1511 |
|
| 1512 |
-
let w_val = *(
|
| 1513 |
-
let h_val = *(
|
| 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::
|
| 1586 |
|
| 1587 |
// DisplayObject for HUD background
|
| 1588 |
-
// usercall: ECX=
|
| 1589 |
{
|
| 1590 |
-
let mem =
|
| 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 |
-
|
| 1599 |
-
|
| 1600 |
mem,
|
| 1601 |
-
|
| 1602 |
)
|
| 1603 |
};
|
| 1604 |
-
|
| 1605 |
}
|
| 1606 |
|
| 1607 |
// DisplayGfx textbox for HUD text
|
| 1608 |
// thiscall: ECX=DDGame.display, stack=(this, 0x13, 2)
|
| 1609 |
{
|
| 1610 |
-
let mem =
|
| 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(
|
| 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 |
-
|
| 1625 |
}
|
| 1626 |
}
|
| 1627 |
|
| 1628 |
/// Validate team names β check for CPU team markers.
|
| 1629 |
-
unsafe fn init_game_state_team_names(
|
| 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(
|
| 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 |
-
|
| 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 |
-
|
| 1673 |
-
|
| 1674 |
-
|
|
|
|
| 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(
|
|
|
|
| 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 |
-
|
| 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(
|
| 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(
|
| 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 |
-
|
| 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 |
-
|
| 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 =
|
| 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.
|
|
@@ -34,8 +34,8 @@ hook::usercall_trampoline!(
|
|
| 34 |
reg = edi
|
| 35 |
);
|
| 36 |
|
| 37 |
-
extern "cdecl" fn impl_init_fields(ddgame:
|
| 38 |
-
unsafe { ddgame_init_fields(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:
|
| 94 |
-
unsafe { display_layer_color_init(wrapper
|
| 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:
|
| 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:
|
| 314 |
-
unsafe { init_alliance_data(wrapper
|
| 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:
|
| 334 |
-
unsafe { init_turn_state(wrapper
|
| 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:
|
| 355 |
-
unsafe { check_weapon_avail(ddgame
|
| 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:
|
| 376 |
-
unsafe { init_landscape_flags(wrapper
|
| 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)]
|