feat: port DDGame__InitGameState (0x526500) to Rust
Browse filesFull Rust reimplementation of the ~600-line game session initialization
function. Allocates ~20 sub-objects, initializes display layers, team
configuration, weapon tables, turn logic, random bags, and computes the
initial state serialization/checksum.
Key changes:
- game_state_init.rs: init_game_state() + 13 helper functions (~1400 LOC)
- ddgame_wrapper.rs: split _unknown blobs into ~60 named fields
- game_session.rs: replaced transmute bridge, added trap on original
- game_state_hooks.rs: removed passthrough hooks for usercall functions
(CTaskTurnGame, DisplayGfx__ConstructFull, ConstructTextbox,
DisplayObject, WeaponPanel) that drop ECX/EDX/ESI register params
- address.rs: added VectorNormalize addresses, renamed INIT_WEAPON_PANEL
Sub-constructors are called via typed bridges (transmute/fastcall/naked
asm). Already-ported functions (sprite_gfx_table_init, ring_buffer_init,
init_team_scoring, init_alliance_data, init_turn_state, etc.) are called
directly. Display layers use correct per-layer BitGrid dimensions with
DisplayBitGrid vtable (0x664144).
Bugs found during porting:
- Stack height condition was reversed (game_version < -1 β 0x700 not 0x800)
- CTaskTurnGame constructor is usercall (ECX=DDGame), not pure stdcall
- DisplayGfx__ConstructFull is fastcall (ECX+EDX), not stdcall
- DisplayBitGrid vtable (0x664144) was confused with DisplayGfx (0x66A218)
- BitGrid::init width/height param order was swapped in layer creation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -397,6 +397,10 @@ pub mod va {
|
|
| 397 |
fn PROJECTILE_FIRE_SINGLE = 0x0051_DCF0;
|
| 398 |
/// Sin lookup table (1024 entries of Fixed16.16). cos = sin + 256 entries.
|
| 399 |
global SIN_TABLE = 0x006A_1860;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
/// Load a string resource by ID. cdecl(resource_id) -> *const c_char.
|
| 401 |
fn/Cdecl LOAD_STRING_RESOURCE = 0x0059_3180;
|
| 402 |
}
|
|
@@ -539,7 +543,8 @@ pub mod va {
|
|
| 539 |
fn/Stdcall GAME_STATE_CONSTRUCTOR = 0x0053_2330;
|
| 540 |
/// DisplayGfx::ConstructTextbox
|
| 541 |
fn/Stdcall CONSTRUCT_TEXTBOX = 0x004F_AF00;
|
| 542 |
-
|
|
|
|
| 543 |
/// Buffer object constructor
|
| 544 |
fn/Stdcall BUFFER_OBJECT_CONSTRUCTOR = 0x0054_5FD0;
|
| 545 |
/// GameStateStream sub-init
|
|
|
|
| 397 |
fn PROJECTILE_FIRE_SINGLE = 0x0051_DCF0;
|
| 398 |
/// Sin lookup table (1024 entries of Fixed16.16). cos = sin + 256 entries.
|
| 399 |
global SIN_TABLE = 0x006A_1860;
|
| 400 |
+
/// VectorNormalize (simple version, used for game_version < 0x99)
|
| 401 |
+
fn VECTOR_NORMALIZE_SIMPLE = 0x0057_5590;
|
| 402 |
+
/// VectorNormalize (overflow-safe version, used for game_version >= 0x99)
|
| 403 |
+
fn VECTOR_NORMALIZE_OVERFLOW = 0x0057_55D0;
|
| 404 |
/// Load a string resource by ID. cdecl(resource_id) -> *const c_char.
|
| 405 |
fn/Cdecl LOAD_STRING_RESOURCE = 0x0059_3180;
|
| 406 |
}
|
|
|
|
| 543 |
fn/Stdcall GAME_STATE_CONSTRUCTOR = 0x0053_2330;
|
| 544 |
/// DisplayGfx::ConstructTextbox
|
| 545 |
fn/Stdcall CONSTRUCT_TEXTBOX = 0x004F_AF00;
|
| 546 |
+
/// DDGame__InitWeaponPanel
|
| 547 |
+
fn/Stdcall INIT_WEAPON_PANEL = 0x0056_7770;
|
| 548 |
/// Buffer object constructor
|
| 549 |
fn/Stdcall BUFFER_OBJECT_CONSTRUCTOR = 0x0054_5FD0;
|
| 550 |
/// GameStateStream sub-init
|
|
@@ -3,6 +3,7 @@ use crate::engine::ddgame::DDGame;
|
|
| 3 |
use crate::engine::net_bridge::NetBridge;
|
| 4 |
use crate::render::display::gfx::DisplayGfx;
|
| 5 |
use crate::render::landscape::PCLandscape;
|
|
|
|
| 6 |
use crate::render::sprite::gfx_dir::GfxDir;
|
| 7 |
use crate::FieldRegistry;
|
| 8 |
|
|
@@ -21,28 +22,240 @@ pub const SPEECH_NAME_TABLE_LEN: usize = 360;
|
|
| 21 |
/// Note: Ghidra shows DWORD-indexed offsets (param_2[0x122] etc.).
|
| 22 |
/// Byte offset = dword_index * 4.
|
| 23 |
///
|
| 24 |
-
/// PARTIAL: Only confirmed fields are defined.
|
|
|
|
|
|
|
| 25 |
#[derive(FieldRegistry)]
|
| 26 |
#[repr(C)]
|
| 27 |
pub struct DDGameWrapper {
|
|
|
|
| 28 |
/// 0x000: Vtable pointer (0x66A30C)
|
| 29 |
pub vtable: *mut u8,
|
| 30 |
-
/// 0x004
|
| 31 |
-
pub _unknown_004:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
/// 0x268: Network sync checksum A (written by GameFrameChecksumProcessor)
|
| 33 |
pub sync_checksum_a: u32,
|
| 34 |
/// 0x26C: Network sync checksum B (written by GameFrameChecksumProcessor)
|
| 35 |
pub sync_checksum_b: u32,
|
| 36 |
/// 0x270: Checksum validity flag (set to 1 after computation)
|
| 37 |
pub checksum_valid: u32,
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
/// 0x488: Pointer to DDGame allocation (DWORD index 0x122)
|
| 41 |
pub ddgame: *mut DDGame,
|
| 42 |
/// 0x48C: Network bridge object (0x2C bytes). Only set for online games (game_version == -2).
|
| 43 |
pub net_bridge: *mut NetBridge,
|
| 44 |
-
/// 0x490
|
| 45 |
-
pub
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
/// 0x4C0: Primary GfxDir β main sprite archive (Gfx.dir / Gfx0.dir / Gfx1.dir).
|
| 47 |
pub primary_gfx_dir: *mut GfxDir,
|
| 48 |
/// 0x4C4: Secondary GfxDir β supplemental sprites (GfxC_3_0.dir), conditional on game version.
|
|
@@ -61,6 +274,8 @@ pub struct DDGameWrapper {
|
|
| 61 |
pub loading_total: u32,
|
| 62 |
/// 0x4E0: Last displayed loading percentage (init -100 to force first update).
|
| 63 |
pub loading_last_pct: u32,
|
|
|
|
|
|
|
| 64 |
/// 0x4E4-0x14E7: Unknown fields
|
| 65 |
pub _unknown_4e4: [u8; 0x14E8 - 0x4E4],
|
| 66 |
/// 0x14E8: Speech name table β 360 entries of 0x40-byte C strings.
|
|
|
|
| 3 |
use crate::engine::net_bridge::NetBridge;
|
| 4 |
use crate::render::display::gfx::DisplayGfx;
|
| 5 |
use crate::render::landscape::PCLandscape;
|
| 6 |
+
use crate::render::palette::PaletteContext;
|
| 7 |
use crate::render::sprite::gfx_dir::GfxDir;
|
| 8 |
use crate::FieldRegistry;
|
| 9 |
|
|
|
|
| 22 |
/// Note: Ghidra shows DWORD-indexed offsets (param_2[0x122] etc.).
|
| 23 |
/// Byte offset = dword_index * 4.
|
| 24 |
///
|
| 25 |
+
/// PARTIAL: Only confirmed fields are defined. Many fields in the
|
| 26 |
+
/// `_unknown_*` blobs are accessed via pointer arithmetic in game_state_init.rs
|
| 27 |
+
/// (team arrays at 0x054/0x128/0x22C, alliance data at 0x350/0x3AC, etc.).
|
| 28 |
#[derive(FieldRegistry)]
|
| 29 |
#[repr(C)]
|
| 30 |
pub struct DDGameWrapper {
|
| 31 |
+
// ===== 0x000: Vtable =====
|
| 32 |
/// 0x000: Vtable pointer (0x66A30C)
|
| 33 |
pub vtable: *mut u8,
|
| 34 |
+
/// 0x004: Unknown (4 bytes gap)
|
| 35 |
+
pub _unknown_004: u32,
|
| 36 |
+
|
| 37 |
+
// ===== 0x008-0x050: Sub-object pointers (allocated by InitGameState) =====
|
| 38 |
+
/// 0x008: CTaskTurnGame or CTaskGameState pointer
|
| 39 |
+
pub task_turn_game: *mut u8,
|
| 40 |
+
/// 0x00C: Main serialization BufferObject
|
| 41 |
+
pub main_buffer: *mut u8,
|
| 42 |
+
/// 0x010: Unknown pointer
|
| 43 |
+
pub _field_010: u32,
|
| 44 |
+
/// 0x014: Render BufferObject B (capacity 0x10000)
|
| 45 |
+
pub render_buffer_b: *mut u8,
|
| 46 |
+
/// 0x018: DisplayGfx layer A
|
| 47 |
+
pub display_gfx_a: *mut u8,
|
| 48 |
+
/// 0x01C: DisplayGfx layer B
|
| 49 |
+
pub display_gfx_b: *mut u8,
|
| 50 |
+
/// 0x020: DisplayGfx layer C
|
| 51 |
+
pub display_gfx_c: *mut u8,
|
| 52 |
+
/// 0x024: DisplayGfx main (ConstructFull result)
|
| 53 |
+
pub display_gfx_main: *mut u8,
|
| 54 |
+
/// 0x028: RingBuffer B (capacity 0x2000)
|
| 55 |
+
pub ring_buffer_b: *mut u8,
|
| 56 |
+
/// 0x02C: DisplayGfx D
|
| 57 |
+
pub display_gfx_d: *mut u8,
|
| 58 |
+
/// 0x030: Camera object A (0x3D4 bytes)
|
| 59 |
+
pub camera_a: *mut u8,
|
| 60 |
+
/// 0x034: DisplayGfx E
|
| 61 |
+
pub display_gfx_e: *mut u8,
|
| 62 |
+
/// 0x038: Camera object B (0x3D4 bytes)
|
| 63 |
+
pub camera_b: *mut u8,
|
| 64 |
+
/// 0x03C: RingBuffer A (capacity 0x2000)
|
| 65 |
+
pub ring_buffer_a: *mut u8,
|
| 66 |
+
/// 0x040: Render BufferObject A (capacity 0x10000)
|
| 67 |
+
pub render_buffer_a: *mut u8,
|
| 68 |
+
/// 0x044: Network RingBuffer (conditional on online mode)
|
| 69 |
+
pub network_ring_buffer: *mut u8,
|
| 70 |
+
/// 0x048: State serialization BufferObject
|
| 71 |
+
pub state_buffer: *mut u8,
|
| 72 |
+
/// 0x04C: Statistics object (0xB94 bytes)
|
| 73 |
+
pub statistics: *mut u8,
|
| 74 |
+
/// 0x050: RingBuffer C (capacity 0x1000)
|
| 75 |
+
pub ring_buffer_c: *mut u8,
|
| 76 |
+
|
| 77 |
+
// ===== 0x054-0x087: Timer/counter block =====
|
| 78 |
+
/// 0x054-0x087: Zeroed by InitGameState. Team task pointers and counters.
|
| 79 |
+
/// Accessed via pointer arithmetic in game_state_init.rs (0x054 = team CTask ptrs).
|
| 80 |
+
pub _zeroed_054: [u32; 13],
|
| 81 |
+
|
| 82 |
+
// ===== 0x088-0x090: PaletteContext pointers =====
|
| 83 |
+
/// 0x088: PaletteContext A (allocated 0x72C bytes)
|
| 84 |
+
pub palette_ctx_a: *mut PaletteContext,
|
| 85 |
+
/// 0x08C: PaletteContext B (allocated 0x72C bytes)
|
| 86 |
+
pub palette_ctx_b: *mut PaletteContext,
|
| 87 |
+
/// 0x090: PaletteContext C (allocated 0x72C bytes)
|
| 88 |
+
pub palette_ctx_c: *mut PaletteContext,
|
| 89 |
+
|
| 90 |
+
// ===== 0x094-0x0F3: State fields =====
|
| 91 |
+
/// 0x094: Unknown gap
|
| 92 |
+
pub _unknown_094: u32,
|
| 93 |
+
/// 0x098-0x0CF: State block (14 u32s zeroed by InitGameState)
|
| 94 |
+
pub _zeroed_098: [u32; 14],
|
| 95 |
+
/// 0x0D0-0x0DF: Unknown
|
| 96 |
+
pub _unknown_0d0: [u8; 0x10],
|
| 97 |
+
/// 0x0E0: State flag (zeroed by InitGameState)
|
| 98 |
+
pub _field_0e0: u32,
|
| 99 |
+
/// 0x0E4-0x0EB: Unknown
|
| 100 |
+
pub _unknown_0e4: [u8; 8],
|
| 101 |
+
/// 0x0EC: State flag
|
| 102 |
+
pub _field_0ec: u32,
|
| 103 |
+
/// 0x0F0: Init flag (set to 1 by InitGameState early)
|
| 104 |
+
pub init_flag: u32,
|
| 105 |
+
|
| 106 |
+
// ===== 0x0F4-0x25F: Team arrays and game configuration =====
|
| 107 |
+
/// 0x0F4-0x25F: Large region containing team data arrays.
|
| 108 |
+
/// Key sub-regions accessed via pointer arithmetic:
|
| 109 |
+
/// 0x128: Team scoring base array (13 elements Γ 7 parallel arrays)
|
| 110 |
+
/// 0x22C: Team activity flags
|
| 111 |
+
pub _unknown_0f4: [u8; 0x260 - 0x0F4],
|
| 112 |
+
|
| 113 |
+
// ===== 0x260-0x267: Game config =====
|
| 114 |
+
/// 0x260: Health display precision (initialized to 500)
|
| 115 |
+
pub health_precision: i32,
|
| 116 |
+
/// 0x264: Zeroed by InitGameState
|
| 117 |
+
pub _field_264: u32,
|
| 118 |
+
|
| 119 |
+
// ===== 0x268-0x273: Sync checksums (already defined) =====
|
| 120 |
/// 0x268: Network sync checksum A (written by GameFrameChecksumProcessor)
|
| 121 |
pub sync_checksum_a: u32,
|
| 122 |
/// 0x26C: Network sync checksum B (written by GameFrameChecksumProcessor)
|
| 123 |
pub sync_checksum_b: u32,
|
| 124 |
/// 0x270: Checksum validity flag (set to 1 after computation)
|
| 125 |
pub checksum_valid: u32,
|
| 126 |
+
|
| 127 |
+
// ===== 0x274-0x487: Game state fields =====
|
| 128 |
+
/// 0x274: Initial state checksum (computed at end of InitGameState)
|
| 129 |
+
pub initial_checksum: u32,
|
| 130 |
+
/// 0x278-0x27F: Zeroed
|
| 131 |
+
pub _field_278: u32,
|
| 132 |
+
pub _field_27c: u32,
|
| 133 |
+
/// 0x280-0x29C: Team render order indices (resolution-dependent, 8 entries)
|
| 134 |
+
pub team_render_indices: [i32; 8],
|
| 135 |
+
/// 0x2A0: Worm selection count
|
| 136 |
+
pub worm_select_count: i32,
|
| 137 |
+
/// 0x2A4: Worm selection count (alternate)
|
| 138 |
+
pub worm_select_count_alt: i32,
|
| 139 |
+
/// 0x2A8: Minimum number of active teams
|
| 140 |
+
pub min_active_teams: i32,
|
| 141 |
+
/// 0x2AC: Team count configuration (7 or 10)
|
| 142 |
+
pub team_count_config: i32,
|
| 143 |
+
/// 0x2B0: Maximum team render index (0xC or 0x10)
|
| 144 |
+
pub max_team_render_index: i32,
|
| 145 |
+
/// 0x2B4-0x34F: Unknown (includes team arrays accessed by init_alliance_data at 0x350)
|
| 146 |
+
pub _unknown_2b4: [u8; 0x350 - 0x2B4],
|
| 147 |
+
/// 0x350-0x383: Alliance bitmask arrays (13 Γ u32, accessed by init_alliance_data)
|
| 148 |
+
pub _alliance_bitmasks: [u32; 13],
|
| 149 |
+
/// 0x384: Screen offset (computed from screen height and team count)
|
| 150 |
+
pub screen_offset: i32,
|
| 151 |
+
/// 0x388: Screen height for HUD
|
| 152 |
+
pub screen_height_hud: i32,
|
| 153 |
+
/// 0x38C-0x3AB: Team-to-slot mapping (first half, 8 entries, filled with team indices)
|
| 154 |
+
pub team_to_slot_a: [i32; 8],
|
| 155 |
+
/// 0x3AC-0x3EB: Slot-to-team reverse mapping (16 entries, sentinel -1 init)
|
| 156 |
+
pub slot_to_team: [i32; 16],
|
| 157 |
+
/// 0x3EC: Last entry of sentinel block
|
| 158 |
+
pub _field_3ec: i32,
|
| 159 |
+
/// 0x3F0: Game mode flag (initialized to 1)
|
| 160 |
+
pub game_mode_flag: i32,
|
| 161 |
+
/// 0x3F4: Sentinel -1
|
| 162 |
+
pub _field_3f4: i32,
|
| 163 |
+
/// 0x3F8: Sentinel -1
|
| 164 |
+
pub _field_3f8: i32,
|
| 165 |
+
/// 0x3FC: Zeroed
|
| 166 |
+
pub _field_3fc: i32,
|
| 167 |
+
/// 0x400: Game turn timer (max_team_render_index << 5)
|
| 168 |
+
pub turn_timer_max: i32,
|
| 169 |
+
/// 0x404: Zeroed
|
| 170 |
+
pub _field_404: i32,
|
| 171 |
+
/// 0x408: Game turn timer copy
|
| 172 |
+
pub turn_timer_current: i32,
|
| 173 |
+
/// 0x40C-0x413: Zeroed
|
| 174 |
+
pub _field_40c: i32,
|
| 175 |
+
pub _field_410: i32,
|
| 176 |
+
/// 0x414: Config bool (from game_info)
|
| 177 |
+
pub _field_414: u32,
|
| 178 |
+
/// 0x418: Zeroed
|
| 179 |
+
pub _field_418: i32,
|
| 180 |
+
/// 0x41C: Unknown
|
| 181 |
+
pub _unknown_41c: u32,
|
| 182 |
+
/// 0x420: Turn percentage (from game_info, fixed-point)
|
| 183 |
+
pub turn_percentage: i32,
|
| 184 |
+
/// 0x424-0x44F: State fields (mix of zeroes and values)
|
| 185 |
+
pub _field_424: i32,
|
| 186 |
+
pub _field_428: i32,
|
| 187 |
+
pub _field_42c: i32,
|
| 188 |
+
pub _field_430: i32,
|
| 189 |
+
/// 0x434: Zeroed
|
| 190 |
+
pub _field_434: i32,
|
| 191 |
+
/// 0x438: Sentinel -1
|
| 192 |
+
pub _field_438: i32,
|
| 193 |
+
/// 0x43C: Sentinel -1
|
| 194 |
+
pub _field_43c: i32,
|
| 195 |
+
/// 0x440: Zeroed
|
| 196 |
+
pub _field_440: i32,
|
| 197 |
+
/// 0x444: Zeroed
|
| 198 |
+
pub _field_444: i32,
|
| 199 |
+
/// 0x448: Zeroed
|
| 200 |
+
pub _field_448: i32,
|
| 201 |
+
/// 0x44C: Zeroed
|
| 202 |
+
pub _field_44c: i32,
|
| 203 |
+
/// 0x450-0x45B: Turn state fields (written by init_turn_state)
|
| 204 |
+
pub _field_450: u32,
|
| 205 |
+
pub _field_454: u32,
|
| 206 |
+
pub _field_458: u32,
|
| 207 |
+
/// 0x45C: Zeroed
|
| 208 |
+
pub _field_45c: i32,
|
| 209 |
+
/// 0x460: Zeroed
|
| 210 |
+
pub _field_460: i32,
|
| 211 |
+
/// 0x464: Zeroed
|
| 212 |
+
pub _field_464: i32,
|
| 213 |
+
/// 0x468: Sentinel -1
|
| 214 |
+
pub _field_468: i32,
|
| 215 |
+
/// 0x46C: Sentinel -1
|
| 216 |
+
pub _field_46c: i32,
|
| 217 |
+
/// 0x470: Sentinel -1
|
| 218 |
+
pub _field_470: i32,
|
| 219 |
+
/// 0x474: Zeroed
|
| 220 |
+
pub _field_474: u32,
|
| 221 |
+
/// 0x478: Zeroed
|
| 222 |
+
pub _field_478: u32,
|
| 223 |
+
/// 0x47C: Unknown
|
| 224 |
+
pub _unknown_47c: u32,
|
| 225 |
+
/// 0x480: Zeroed
|
| 226 |
+
pub _field_480: u32,
|
| 227 |
+
/// 0x484: State initialized flag (set to 1 at end of InitGameState)
|
| 228 |
+
pub state_initialized: u32,
|
| 229 |
+
|
| 230 |
+
// ===== 0x488-0x4BF: Core pointers and flags =====
|
| 231 |
/// 0x488: Pointer to DDGame allocation (DWORD index 0x122)
|
| 232 |
pub ddgame: *mut DDGame,
|
| 233 |
/// 0x48C: Network bridge object (0x2C bytes). Only set for online games (game_version == -2).
|
| 234 |
pub net_bridge: *mut NetBridge,
|
| 235 |
+
/// 0x490: Replay flag A (from game_info)
|
| 236 |
+
pub replay_flag_a: u8,
|
| 237 |
+
/// 0x491: Replay flag B (from game_info)
|
| 238 |
+
pub replay_flag_b: u8,
|
| 239 |
+
/// 0x492: Unknown byte
|
| 240 |
+
pub _unknown_492: u8,
|
| 241 |
+
/// 0x493: Zeroed by InitGameState
|
| 242 |
+
pub _field_493: u8,
|
| 243 |
+
/// 0x494: Zeroed by InitGameState
|
| 244 |
+
pub _field_494: u32,
|
| 245 |
+
/// 0x498: Zeroed by InitGameState
|
| 246 |
+
pub _field_498: u32,
|
| 247 |
+
/// 0x49C: Version counter (read by InitGameState, not written)
|
| 248 |
+
pub _field_49c: u32,
|
| 249 |
+
/// 0x4A0-0x4AB: Unknown
|
| 250 |
+
pub _unknown_4a0: [u8; 0x0C],
|
| 251 |
+
/// 0x4AC: State field
|
| 252 |
+
pub _field_4ac: u32,
|
| 253 |
+
/// 0x4B0: Sentinel -1
|
| 254 |
+
pub _field_4b0: u32,
|
| 255 |
+
/// 0x4B4-0x4BF: Unknown
|
| 256 |
+
pub _unknown_4b4: [u8; 0x0C],
|
| 257 |
+
|
| 258 |
+
// ===== 0x4C0-0x4E3: Graphics/display/sound pointers =====
|
| 259 |
/// 0x4C0: Primary GfxDir β main sprite archive (Gfx.dir / Gfx0.dir / Gfx1.dir).
|
| 260 |
pub primary_gfx_dir: *mut GfxDir,
|
| 261 |
/// 0x4C4: Secondary GfxDir β supplemental sprites (GfxC_3_0.dir), conditional on game version.
|
|
|
|
| 274 |
pub loading_total: u32,
|
| 275 |
/// 0x4E0: Last displayed loading percentage (init -100 to force first update).
|
| 276 |
pub loading_last_pct: u32,
|
| 277 |
+
|
| 278 |
+
// ===== 0x4E4-0x6F0F: Remaining fields =====
|
| 279 |
/// 0x4E4-0x14E7: Unknown fields
|
| 280 |
pub _unknown_4e4: [u8; 0x14E8 - 0x4E4],
|
| 281 |
/// 0x14E8: Speech name table β 360 entries of 0x40-byte C strings.
|
|
@@ -1,9 +1,11 @@
|
|
| 1 |
-
//! Pure Rust implementations of DDGame__InitGameState sub-functions
|
|
|
|
| 2 |
//!
|
| 3 |
-
//! Each function is hooked individually so it works regardless of whether
|
| 4 |
//! InitGameState itself is Rust or the original WA code.
|
| 5 |
|
| 6 |
use crate::engine::ddgame::DDGame;
|
|
|
|
| 7 |
use crate::engine::game_info::GameInfo;
|
| 8 |
use crate::wa_alloc::wa_malloc;
|
| 9 |
|
|
@@ -579,3 +581,1399 @@ pub unsafe fn init_landscape_flags(wrapper: *mut u8) {
|
|
| 579 |
);
|
| 580 |
}
|
| 581 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Pure Rust implementations of DDGame__InitGameState sub-functions,
|
| 2 |
+
//! and the top-level InitGameState itself.
|
| 3 |
//!
|
| 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::wa_alloc::wa_malloc;
|
| 11 |
|
|
|
|
| 581 |
);
|
| 582 |
}
|
| 583 |
}
|
| 584 |
+
|
| 585 |
+
// =============================================================================
|
| 586 |
+
// Top-level DDGame__InitGameState (0x526500) β Rust port
|
| 587 |
+
// =============================================================================
|
| 588 |
+
|
| 589 |
+
/// Pure Rust implementation of DDGame__InitGameState (0x526500).
|
| 590 |
+
///
|
| 591 |
+
/// Convention: stdcall(this=DDGameWrapper*), RET 0x4.
|
| 592 |
+
///
|
| 593 |
+
/// Called once per game session from the DDGameWrapper constructor to initialize
|
| 594 |
+
/// all game state: sub-objects, display layers, team configuration, weapon tables,
|
| 595 |
+
/// turn logic, and the initial state serialization/checksum.
|
| 596 |
+
pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
| 597 |
+
use crate::address::va;
|
| 598 |
+
use crate::rebase::rb;
|
| 599 |
+
use crate::wa_alloc::wa_malloc;
|
| 600 |
+
|
| 601 |
+
let w = wrapper as *mut u8;
|
| 602 |
+
let ddgame = (*wrapper).ddgame;
|
| 603 |
+
let ddgame_raw = ddgame as *mut u8;
|
| 604 |
+
|
| 605 |
+
// ===== Copy replay mode flags from GameInfo =====
|
| 606 |
+
let game_info_sub = *(ddgame_raw.add(0x24) as *const *const u8);
|
| 607 |
+
(*wrapper).replay_flag_a = *game_info_sub.add(0xDB08);
|
| 608 |
+
(*wrapper).replay_flag_b = *game_info_sub.add(0xDB09);
|
| 609 |
+
|
| 610 |
+
// ===== SpriteGfxTable__Init =====
|
| 611 |
+
// ECX = DDGame+0x600, EDX = version-dependent count
|
| 612 |
+
{
|
| 613 |
+
let game_version = *(game_info_sub.add(0xD778) as *const i32);
|
| 614 |
+
let count: u32 = if game_version >= 0x3C { 0x400 } else { 0x100 };
|
| 615 |
+
sprite_gfx_table_init(ddgame_raw.add(0x600) as *mut u8, count);
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
// ===== Allocate HudPanel (0x940 bytes) =====
|
| 619 |
+
{
|
| 620 |
+
let mem = wa_malloc(0x940);
|
| 621 |
+
core::ptr::write_bytes(mem, 0, 0x920);
|
| 622 |
+
let result = if mem.is_null() {
|
| 623 |
+
core::ptr::null_mut()
|
| 624 |
+
} else {
|
| 625 |
+
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
| 626 |
+
core::mem::transmute(rb(va::HUD_PANEL_CONSTRUCTOR) as usize);
|
| 627 |
+
ctor(mem)
|
| 628 |
+
};
|
| 629 |
+
*(ddgame_raw.add(0x534) as *mut *mut u8) = result;
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
// ===== Allocate weapon table buffer (0x80D0 bytes) =====
|
| 633 |
+
{
|
| 634 |
+
let mem = wa_malloc(0x80D0);
|
| 635 |
+
core::ptr::write_bytes(mem, 0, 0x80B0);
|
| 636 |
+
*(ddgame_raw.add(0x510) as *mut *mut u8) = mem;
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
// ===== Allocate unknown 0x2C object =====
|
| 640 |
+
{
|
| 641 |
+
let mem = wa_malloc(0x2C);
|
| 642 |
+
if !mem.is_null() {
|
| 643 |
+
*(mem.add(0x08) as *mut u32) = 0;
|
| 644 |
+
*(mem.add(0x0C) as *mut u32) = 0;
|
| 645 |
+
*(mem.add(0x10) as *mut u32) = 0;
|
| 646 |
+
*(mem.add(0x1C) as *mut u32) = 0;
|
| 647 |
+
*(mem.add(0x20) as *mut u32) = 0;
|
| 648 |
+
*(mem.add(0x24) as *mut u32) = 0;
|
| 649 |
+
}
|
| 650 |
+
*(ddgame_raw.add(0x51C) as *mut *mut u8) = mem;
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
// ===== Allocate RenderQueue (0x12028 bytes) =====
|
| 654 |
+
{
|
| 655 |
+
let mem = wa_malloc(0x12028) as *mut u32;
|
| 656 |
+
core::ptr::write_bytes(mem as *mut u8, 0, 0x12008);
|
| 657 |
+
if !mem.is_null() {
|
| 658 |
+
*mem.add(0x4001) = 0; // mem[0x10004] = 0
|
| 659 |
+
*mem = 0x10000; // mem[0] = 0x10000
|
| 660 |
+
}
|
| 661 |
+
*(ddgame_raw.add(0x524) as *mut *mut u32) = mem;
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
// ===== Allocate GameStateStream (0x264 bytes) =====
|
| 665 |
+
{
|
| 666 |
+
let mem = wa_malloc(0x264) as *mut u32;
|
| 667 |
+
core::ptr::write_bytes(mem as *mut u8, 0, 0x244);
|
| 668 |
+
if !mem.is_null() {
|
| 669 |
+
*mem = rb(0x664194); // GameStateStream vtable
|
| 670 |
+
let init: unsafe extern "stdcall" fn(*mut u32) =
|
| 671 |
+
core::mem::transmute(rb(va::GAME_STATE_STREAM_INIT) as usize);
|
| 672 |
+
init(mem.add(1)); // Init sub-object at offset +4
|
| 673 |
+
*mem.add(1) = 0;
|
| 674 |
+
*mem.add(0x90) = ddgame as u32; // puVar5[0x90] = uVar3 (DDGame ptr)
|
| 675 |
+
*mem.add(0x8B) = 0;
|
| 676 |
+
*mem.add(0x8C) = 0;
|
| 677 |
+
}
|
| 678 |
+
*(ddgame_raw.add(0x528) as *mut *mut u32) = mem;
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
// ===== Allocate unknown 0x4A74 object =====
|
| 682 |
+
{
|
| 683 |
+
let mem = wa_malloc(0x4A74);
|
| 684 |
+
core::ptr::write_bytes(mem, 0, 0x4A54);
|
| 685 |
+
if !mem.is_null() {
|
| 686 |
+
*(mem.add(0x4A50) as *mut u32) = ddgame as u32;
|
| 687 |
+
}
|
| 688 |
+
*(ddgame_raw.add(0x52C) as *mut *mut u8) = mem;
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
// ===== Virtual call: display->vtable[1](ddgame+0x77B8, ddgame+0x77BC) =====
|
| 692 |
+
// Ghidra: (**(code **)(**(int **)(iVar4 + 4) + 4))(iVar4 + 0x77b8, iVar4 + 0x77bc)
|
| 693 |
+
// DDGame+4 = display ptr, deref for vtable, slot 1
|
| 694 |
+
{
|
| 695 |
+
let display_ptr = *(ddgame_raw.add(4) as *const *const u8);
|
| 696 |
+
let vtable = *(display_ptr as *const *const u32);
|
| 697 |
+
let vmethod: unsafe extern "thiscall" fn(*const u8, u32, u32) =
|
| 698 |
+
core::mem::transmute(*vtable.add(1));
|
| 699 |
+
vmethod(display_ptr, ddgame as u32 + 0x77B8, ddgame as u32 + 0x77BC);
|
| 700 |
+
}
|
| 701 |
+
*(ddgame_raw.add(0x77B0) as *mut u32) = 0;
|
| 702 |
+
|
| 703 |
+
// ===== Allocate BufferObject (main_buffer at wrapper+0x0C) =====
|
| 704 |
+
{
|
| 705 |
+
let buf = allocate_buffer_object(ddgame_raw, game_info_sub);
|
| 706 |
+
(*wrapper).main_buffer = buf;
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
// ===== Allocate RingBuffer A (wrapper+0x3C, capacity 0x2000) =====
|
| 710 |
+
(*wrapper).ring_buffer_a = allocate_ring_buffer_raw(0x3C, 0x2000);
|
| 711 |
+
|
| 712 |
+
// ===== Allocate RingBuffer B (wrapper+0x28, capacity 0x2000) =====
|
| 713 |
+
(*wrapper).ring_buffer_b = allocate_ring_buffer_raw(0x3C, 0x2000);
|
| 714 |
+
|
| 715 |
+
// ===== Allocate render buffer A (wrapper+0x40, capacity 0x10000) =====
|
| 716 |
+
(*wrapper).render_buffer_a = allocate_ring_buffer_raw(0x48, 0x10000);
|
| 717 |
+
|
| 718 |
+
// ===== Allocate render buffer B (wrapper+0x14, capacity 0x10000) =====
|
| 719 |
+
(*wrapper).render_buffer_b = allocate_ring_buffer_raw(0x48, 0x10000);
|
| 720 |
+
|
| 721 |
+
// ===== wrapper+0x44: network ring buffer (conditional) =====
|
| 722 |
+
(*wrapper).network_ring_buffer = core::ptr::null_mut();
|
| 723 |
+
if *(ddgame_raw.add(0x1C) as *const u32) != 0 {
|
| 724 |
+
(*wrapper).network_ring_buffer = allocate_ring_buffer_init(wrapper as *mut u8);
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
// ===== Allocate state buffer (wrapper+0x48) =====
|
| 728 |
+
(*wrapper).state_buffer = allocate_buffer_object(ddgame_raw, game_info_sub);
|
| 729 |
+
|
| 730 |
+
// ===== Allocate statistics object (0xB94 bytes, wrapper+0x4C) =====
|
| 731 |
+
{
|
| 732 |
+
let mem = wa_malloc(0xB94);
|
| 733 |
+
if !mem.is_null() {
|
| 734 |
+
*(mem.add(0xB54) as *mut u32) = 0;
|
| 735 |
+
*(mem.add(0xB58) as *mut u32) = 0;
|
| 736 |
+
*(mem.add(0xB64) as *mut u32) = 0;
|
| 737 |
+
*(mem.add(0xB68) as *mut u32) = 0;
|
| 738 |
+
*(mem.add(0xB74) as *mut u32) = 0;
|
| 739 |
+
*(mem.add(0xB78) as *mut u32) = 0;
|
| 740 |
+
*(mem.add(0xB84) as *mut u32) = 0;
|
| 741 |
+
*(mem.add(0xB88) as *mut u32) = 0;
|
| 742 |
+
}
|
| 743 |
+
(*wrapper).statistics = mem;
|
| 744 |
+
}
|
| 745 |
+
|
| 746 |
+
// ===== Allocate ring buffer C (wrapper+0x50, capacity 0x1000) =====
|
| 747 |
+
(*wrapper).ring_buffer_c = allocate_ring_buffer_raw(0x3C, 0x1000);
|
| 748 |
+
|
| 749 |
+
// ===== Zero out scalar fields =====
|
| 750 |
+
(*wrapper)._field_494 = 0;
|
| 751 |
+
(*wrapper)._field_498 = 0;
|
| 752 |
+
(*wrapper)._field_493 = 0;
|
| 753 |
+
|
| 754 |
+
// Zero 0x54-0x84 (13 fields)
|
| 755 |
+
for i in 0..13u32 {
|
| 756 |
+
*(w.add(0x54 + i as usize * 4) as *mut u32) = 0;
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
// ===== Allocate 3 PaletteContexts (0x72C bytes each) =====
|
| 760 |
+
(*wrapper).palette_ctx_a = allocate_palette_context(ddgame_raw);
|
| 761 |
+
(*wrapper).palette_ctx_b = allocate_palette_context(ddgame_raw);
|
| 762 |
+
(*wrapper).palette_ctx_c = allocate_palette_context(ddgame_raw);
|
| 763 |
+
|
| 764 |
+
// ===== Select vector normalize function based on game version =====
|
| 765 |
+
{
|
| 766 |
+
let game_version = *(game_info_sub.add(0xD778) as *const i32);
|
| 767 |
+
let func_ptr = if game_version < 0x99 {
|
| 768 |
+
rb(va::VECTOR_NORMALIZE_SIMPLE)
|
| 769 |
+
} else {
|
| 770 |
+
rb(va::VECTOR_NORMALIZE_OVERFLOW)
|
| 771 |
+
};
|
| 772 |
+
*(ddgame_raw.add(0x5C4) as *mut u32) = func_ptr;
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
// ===== Initialize sentinel fields =====
|
| 776 |
+
*(w.add(0x468) as *mut i32) = -1;
|
| 777 |
+
*(w.add(0x46C) as *mut i32) = -1;
|
| 778 |
+
*(w.add(0x470) as *mut i32) = -1;
|
| 779 |
+
*(w.add(0x484) as *mut u32) = 0;
|
| 780 |
+
*(w.add(0x480) as *mut u32) = 0;
|
| 781 |
+
*(w.add(0x264) as *mut u32) = 0;
|
| 782 |
+
*(w.add(0x268) as *mut u32) = 0;
|
| 783 |
+
*(w.add(0x270) as *mut u32) = 0;
|
| 784 |
+
*(w.add(0x278) as *mut u32) = 0;
|
| 785 |
+
*(w.add(0x27C) as *mut u32) = 0;
|
| 786 |
+
*(w.add(0x3FC) as *mut u32) = 0;
|
| 787 |
+
*(w.add(0x3F4) as *mut i32) = -1;
|
| 788 |
+
*(w.add(0x3F8) as *mut i32) = -1;
|
| 789 |
+
|
| 790 |
+
// ===== Resolution-dependent team render indices =====
|
| 791 |
+
{
|
| 792 |
+
let screen_height = *(ddgame_raw.add(0x77BC) as *const i32);
|
| 793 |
+
if screen_height < 0x2D0 {
|
| 794 |
+
// < 720px: smaller layout
|
| 795 |
+
*(w.add(0x2B0) as *mut i32) = 0xC;
|
| 796 |
+
*(w.add(0x280) as *mut i32) = 8;
|
| 797 |
+
*(w.add(0x284) as *mut i32) = 7;
|
| 798 |
+
*(w.add(0x288) as *mut i32) = 5;
|
| 799 |
+
*(w.add(0x28C) as *mut i32) = 2;
|
| 800 |
+
*(w.add(0x290) as *mut i32) = 4;
|
| 801 |
+
*(w.add(0x294) as *mut i32) = 3;
|
| 802 |
+
*(w.add(0x298) as *mut i32) = 1;
|
| 803 |
+
*(w.add(0x29C) as *mut i32) = 6;
|
| 804 |
+
} else {
|
| 805 |
+
// >= 720px: larger layout
|
| 806 |
+
*(w.add(0x2B0) as *mut i32) = 0x10;
|
| 807 |
+
*(w.add(0x280) as *mut i32) = 0x10;
|
| 808 |
+
*(w.add(0x284) as *mut i32) = 0xF;
|
| 809 |
+
*(w.add(0x288) as *mut i32) = 0xD;
|
| 810 |
+
*(w.add(0x28C) as *mut i32) = 10;
|
| 811 |
+
*(w.add(0x290) as *mut i32) = 0xC;
|
| 812 |
+
*(w.add(0x294) as *mut i32) = 0xB;
|
| 813 |
+
*(w.add(0x298) as *mut i32) = 9;
|
| 814 |
+
*(w.add(0x29C) as *mut i32) = 0xE;
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
+
if screen_height < 600 {
|
| 818 |
+
*(w.add(0x2AC) as *mut i32) = 7;
|
| 819 |
+
} else {
|
| 820 |
+
*(w.add(0x2AC) as *mut i32) = 10;
|
| 821 |
+
}
|
| 822 |
+
}
|
| 823 |
+
|
| 824 |
+
// ===== Game mode and sentinel arrays =====
|
| 825 |
+
*(w.add(0x3F0) as *mut i32) = 1;
|
| 826 |
+
|
| 827 |
+
// Fill team slot mapping with -1 sentinels (0x38C-0x3EC = 25 entries)
|
| 828 |
+
for i in 0..25u32 {
|
| 829 |
+
*(w.add(0x38C + i as usize * 4) as *mut i32) = -1;
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
// ===== Team-to-slot mapping (conditional on network/replay mode) =====
|
| 833 |
+
if *(ddgame_raw.add(0x1C) as *const u32) != 0 || (*wrapper).replay_flag_a != 0 {
|
| 834 |
+
let team_count = *game_info_sub as i32; // first byte of game_info_sub = team count
|
| 835 |
+
let exclude_team = *(game_info_sub.add(0xD9DC) as *const i8) as i32;
|
| 836 |
+
let mut slot_idx = 0i32;
|
| 837 |
+
let pivar8_base = w.add(0x38C) as *mut i32;
|
| 838 |
+
let pivar7_base = w.add(0x3BC) as *mut i32;
|
| 839 |
+
// pivar7 (at 0x3BC) advances every iteration, indexed by team_id
|
| 840 |
+
// pivar8 (at 0x38C) advances only for non-excluded teams, indexed by slot_idx
|
| 841 |
+
for team_id in 0..team_count {
|
| 842 |
+
if team_id != exclude_team {
|
| 843 |
+
*pivar7_base.add(team_id as usize) = slot_idx;
|
| 844 |
+
*pivar8_base.add(slot_idx as usize) = team_id;
|
| 845 |
+
slot_idx += 1;
|
| 846 |
+
} else {
|
| 847 |
+
*pivar7_base.add(team_id as usize) = -1;
|
| 848 |
+
}
|
| 849 |
+
}
|
| 850 |
+
}
|
| 851 |
+
|
| 852 |
+
// ===== Game logic initialization =====
|
| 853 |
+
*(w.add(0x474) as *mut u32) = 0;
|
| 854 |
+
(*wrapper).init_flag = 1;
|
| 855 |
+
|
| 856 |
+
// Already-ported sub-functions (called via wrapper pointer as the existing hooks expect)
|
| 857 |
+
init_team_scoring(wrapper as *mut u8);
|
| 858 |
+
init_alliance_data(wrapper as *mut u8);
|
| 859 |
+
|
| 860 |
+
// ===== Game speed/timing fields =====
|
| 861 |
+
(*wrapper).health_precision = 500;
|
| 862 |
+
*(w.add(0x45C) as *mut u32) = 0;
|
| 863 |
+
*(w.add(0x464) as *mut u32) = 0;
|
| 864 |
+
*(w.add(0x460) as *mut u32) = 0;
|
| 865 |
+
*(w.add(0x478) as *mut u32) = 0;
|
| 866 |
+
*(w.add(0x418) as *mut u32) = 0;
|
| 867 |
+
|
| 868 |
+
// Turn percentage: (game_info.turn_percentage << 16) / 100
|
| 869 |
+
let turn_pct_raw = *(game_info_sub.add(0xDAA8) as *const i32);
|
| 870 |
+
(*wrapper).turn_percentage = (turn_pct_raw << 16) / 100;
|
| 871 |
+
|
| 872 |
+
// ===== Display setup (headful only) =====
|
| 873 |
+
let is_headful = *(ddgame_raw.add(0x7EF8) as *const u32) != 0;
|
| 874 |
+
|
| 875 |
+
if is_headful {
|
| 876 |
+
// The headful display initialization involves complex usercall patterns
|
| 877 |
+
// (BitGrid__Init with per-layer dimensions, DisplayGfx__ConstructFull with
|
| 878 |
+
// ECX/EDX register params). For now, bridge to original display layer code
|
| 879 |
+
// via the existing ported DisplayGfx constructors.
|
| 880 |
+
// TODO: Port the headful display init with proper usercall bridges.
|
| 881 |
+
init_game_state_display(wrapper, w, ddgame_raw, game_info_sub);
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
// ===== Worm selection count and terrain config =====
|
| 885 |
+
{
|
| 886 |
+
let setup_data = *(ddgame_raw.add(0x24) as *const *const u8);
|
| 887 |
+
let worm_select_f368 = *(setup_data.add(0xF368) as *const i32);
|
| 888 |
+
let min_teams = *(w.add(0x2A8) as *const i32);
|
| 889 |
+
let team_count_cfg = *(w.add(0x2AC) as *const i32);
|
| 890 |
+
|
| 891 |
+
let mut val = worm_select_f368;
|
| 892 |
+
if val == 0 {
|
| 893 |
+
val = team_count_cfg;
|
| 894 |
+
} else if val < min_teams {
|
| 895 |
+
val = min_teams;
|
| 896 |
+
} else if val > 0x20 {
|
| 897 |
+
val = 0x20;
|
| 898 |
+
}
|
| 899 |
+
*(w.add(0x2A0) as *mut i32) = val;
|
| 900 |
+
|
| 901 |
+
let worm_select_f36c = *(setup_data.add(0xF36C) as *const i32);
|
| 902 |
+
val = worm_select_f36c;
|
| 903 |
+
if val == -1 {
|
| 904 |
+
val = 7;
|
| 905 |
+
} else if val < min_teams {
|
| 906 |
+
val = min_teams;
|
| 907 |
+
} else if val > 0x20 {
|
| 908 |
+
val = 0x20;
|
| 909 |
+
}
|
| 910 |
+
*(w.add(0x2A4) as *mut i32) = val;
|
| 911 |
+
|
| 912 |
+
*(w.add(0x414) as *mut u32) = (*(setup_data.add(0xF365) as *const u8) != 0) as u32;
|
| 913 |
+
|
| 914 |
+
if is_headful {
|
| 915 |
+
let palette_ptr = *(ddgame_raw as *const *mut u8);
|
| 916 |
+
*(palette_ptr.add(0x10) as *mut u32) =
|
| 917 |
+
(*(setup_data.add(0xF370) as *const u8) != 0) as u32;
|
| 918 |
+
}
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
// Ensure worm_select_count >= worm_select_count_alt
|
| 922 |
+
if *(w.add(0x2A0) as *const i32) < *(w.add(0x2A4) as *const i32) {
|
| 923 |
+
*(w.add(0x2A0) as *mut i32) = *(w.add(0x2A4) as *const i32);
|
| 924 |
+
}
|
| 925 |
+
|
| 926 |
+
// ===== Zero state fields =====
|
| 927 |
+
*(w.add(0x434) as *mut u32) = 0;
|
| 928 |
+
*(w.add(0x438) as *mut i32) = -1;
|
| 929 |
+
*(w.add(0x43C) as *mut i32) = -1;
|
| 930 |
+
*(w.add(0x440) as *mut u32) = 0;
|
| 931 |
+
*(w.add(0x444) as *mut u32) = 0;
|
| 932 |
+
*(w.add(0x424) as *mut u32) = 0;
|
| 933 |
+
*(w.add(0x428) as *mut u32) = 0;
|
| 934 |
+
*(w.add(0x448) as *mut u32) = 0;
|
| 935 |
+
*(w.add(0x44C) as *mut u32) = 0;
|
| 936 |
+
*(w.add(0x42C) as *mut u32) = 0;
|
| 937 |
+
*(w.add(0x430) as *mut u32) = 0;
|
| 938 |
+
*(w.add(0x40C) as *mut u32) = 0;
|
| 939 |
+
*(w.add(0x410) as *mut u32) = 0;
|
| 940 |
+
|
| 941 |
+
// ===== Landscape flags =====
|
| 942 |
+
init_landscape_flags(wrapper as *mut u8);
|
| 943 |
+
|
| 944 |
+
// ===== Level bounds and camera initialization =====
|
| 945 |
+
init_game_state_level_bounds(ddgame_raw, game_info_sub);
|
| 946 |
+
|
| 947 |
+
// ===== Display objects for HUD (headful only) =====
|
| 948 |
+
if is_headful {
|
| 949 |
+
init_game_state_hud_objects(wrapper, ddgame_raw, game_info_sub);
|
| 950 |
+
}
|
| 951 |
+
|
| 952 |
+
// ===== Team name validation =====
|
| 953 |
+
init_game_state_team_names(ddgame_raw, game_info_sub);
|
| 954 |
+
|
| 955 |
+
// ===== Weapon and team initialization =====
|
| 956 |
+
*(ddgame_raw.add(0x4624) as *mut u32) = 0;
|
| 957 |
+
*(ddgame_raw.add(0x7EF4) as *mut u32) = 0;
|
| 958 |
+
|
| 959 |
+
// InitWeaponTable
|
| 960 |
+
{
|
| 961 |
+
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 962 |
+
core::mem::transmute(rb(va::INIT_WEAPON_TABLE) as usize);
|
| 963 |
+
f(wrapper);
|
| 964 |
+
}
|
| 965 |
+
|
| 966 |
+
// DDGame__InitTeamsFromSetup
|
| 967 |
+
{
|
| 968 |
+
let f: unsafe extern "stdcall" fn(u32, u32) =
|
| 969 |
+
core::mem::transmute(rb(va::INIT_TEAMS_FROM_SETUP) as usize);
|
| 970 |
+
f(
|
| 971 |
+
ddgame as u32 + 0x4628,
|
| 972 |
+
*(ddgame_raw.add(0x24) as *const u32),
|
| 973 |
+
);
|
| 974 |
+
}
|
| 975 |
+
|
| 976 |
+
// ===== Version-dependent stack/render config =====
|
| 977 |
+
init_game_state_version_config(ddgame_raw, game_info_sub);
|
| 978 |
+
|
| 979 |
+
// ===== Water level and wind =====
|
| 980 |
+
init_game_state_water_level(ddgame_raw, game_info_sub);
|
| 981 |
+
|
| 982 |
+
// ===== Random bag initialization =====
|
| 983 |
+
init_game_state_random_bags(ddgame_raw, game_info_sub);
|
| 984 |
+
|
| 985 |
+
// ===== Turn state =====
|
| 986 |
+
init_turn_state(wrapper as *mut u8);
|
| 987 |
+
|
| 988 |
+
// ===== Weapon availability loop =====
|
| 989 |
+
init_game_state_weapon_avail(wrapper, ddgame_raw, game_info_sub);
|
| 990 |
+
|
| 991 |
+
// ===== Team/object tracking arrays =====
|
| 992 |
+
init_game_state_tracking_arrays(ddgame_raw, game_info_sub);
|
| 993 |
+
|
| 994 |
+
// ===== Statistics counters =====
|
| 995 |
+
for i in 0..10u32 {
|
| 996 |
+
*(ddgame_raw.add(0x9890 + i as usize * 4) as *mut u32) = 0;
|
| 997 |
+
}
|
| 998 |
+
|
| 999 |
+
// ===== TeamManager constructor =====
|
| 1000 |
+
{
|
| 1001 |
+
let mem = wa_malloc(0x6C);
|
| 1002 |
+
core::ptr::write_bytes(mem, 0, 0x4C);
|
| 1003 |
+
let result = if mem.is_null() {
|
| 1004 |
+
core::ptr::null_mut()
|
| 1005 |
+
} else {
|
| 1006 |
+
let ctor: unsafe extern "stdcall" fn(*mut u8, u32) -> *mut u8 =
|
| 1007 |
+
core::mem::transmute(rb(va::TEAM_MANAGER_CONSTRUCTOR) as usize);
|
| 1008 |
+
ctor(mem, ddgame as u32)
|
| 1009 |
+
};
|
| 1010 |
+
*(ddgame_raw.add(0x530) as *mut *mut u8) = result;
|
| 1011 |
+
}
|
| 1012 |
+
|
| 1013 |
+
// ===== Network sync callback =====
|
| 1014 |
+
if *(ddgame_raw.add(8) as *const u32) != 0 {
|
| 1015 |
+
let snd_ptr = *(ddgame_raw.add(8) as *const *const u8);
|
| 1016 |
+
let vtable = *(snd_ptr as *const *const u32);
|
| 1017 |
+
let vmethod: unsafe extern "thiscall" fn(*const u8) = core::mem::transmute(*vtable.add(1));
|
| 1018 |
+
vmethod(snd_ptr);
|
| 1019 |
+
}
|
| 1020 |
+
|
| 1021 |
+
// ===== CTaskTurnGame constructor =====
|
| 1022 |
+
{
|
| 1023 |
+
let game_version = *(game_info_sub.add(0xD778) as *const i32);
|
| 1024 |
+
if game_version == -2 {
|
| 1025 |
+
// Online game: ECX = *net_bridge (dereferenced!) for the constructor
|
| 1026 |
+
let mem = wa_malloc(0x324);
|
| 1027 |
+
core::ptr::write_bytes(mem, 0, 0x304);
|
| 1028 |
+
if mem.is_null() {
|
| 1029 |
+
(*wrapper).task_turn_game = core::ptr::null_mut();
|
| 1030 |
+
} else {
|
| 1031 |
+
let net_bridge_ptr = *(w.add(0x48C) as *const *const u32);
|
| 1032 |
+
let ecx_val = *net_bridge_ptr; // deref net_bridge to get ECX
|
| 1033 |
+
call_ctor_with_ecx(
|
| 1034 |
+
mem,
|
| 1035 |
+
*(ddgame_raw.add(0x24) as *const u32),
|
| 1036 |
+
ecx_val, // ECX = *net_bridge
|
| 1037 |
+
rb(va::CTASK_TURNGAME_CTOR),
|
| 1038 |
+
);
|
| 1039 |
+
*(mem.add(0x300) as *mut u32) = net_bridge_ptr as u32;
|
| 1040 |
+
// Override vtables for online mode
|
| 1041 |
+
*(mem as *mut u32) = rb(0x669C28);
|
| 1042 |
+
*(mem.add(0x30) as *mut u32) = rb(0x669C44);
|
| 1043 |
+
(*wrapper).task_turn_game = mem;
|
| 1044 |
+
}
|
| 1045 |
+
} else {
|
| 1046 |
+
// Normal game: ECX must = DDGame for the constructor
|
| 1047 |
+
let mem = wa_malloc(0x320);
|
| 1048 |
+
core::ptr::write_bytes(mem, 0, 0x300);
|
| 1049 |
+
let result = if mem.is_null() {
|
| 1050 |
+
core::ptr::null_mut()
|
| 1051 |
+
} else {
|
| 1052 |
+
call_ctor_with_ecx(
|
| 1053 |
+
mem,
|
| 1054 |
+
*(ddgame_raw.add(0x24) as *const u32),
|
| 1055 |
+
ddgame_raw as u32, // ECX = DDGame
|
| 1056 |
+
rb(va::CTASK_TURNGAME_CTOR),
|
| 1057 |
+
)
|
| 1058 |
+
};
|
| 1059 |
+
(*wrapper).task_turn_game = result;
|
| 1060 |
+
}
|
| 1061 |
+
}
|
| 1062 |
+
|
| 1063 |
+
// ===== Zero state block (0x98-0xCC, 14 entries) =====
|
| 1064 |
+
for i in 0..14u32 {
|
| 1065 |
+
*(w.add(0x98 + i as usize * 4) as *mut u32) = 0;
|
| 1066 |
+
}
|
| 1067 |
+
|
| 1068 |
+
// ===== Replay/network mode flag =====
|
| 1069 |
+
{
|
| 1070 |
+
let replay_a = (*wrapper).replay_flag_a;
|
| 1071 |
+
let result = if replay_a == 0
|
| 1072 |
+
|| (*(game_info_sub.add(0xDB0C) as *const u8) != 0
|
| 1073 |
+
&& *(w.add(0x49C) as *const i32) > 0xC)
|
| 1074 |
+
{
|
| 1075 |
+
0u32
|
| 1076 |
+
} else {
|
| 1077 |
+
0xFFFF_FFFFu32
|
| 1078 |
+
};
|
| 1079 |
+
*(ddgame_raw.add(0x7EF0) as *mut u32) = result;
|
| 1080 |
+
}
|
| 1081 |
+
|
| 1082 |
+
*(w.add(0x4B0) as *mut i32) = -1;
|
| 1083 |
+
*(w.add(0x4AC) as *mut u32) = 0;
|
| 1084 |
+
*(w.add(0xE0) as *mut u32) = 0;
|
| 1085 |
+
|
| 1086 |
+
// 0xEC: (game_info.f340 != 0) - 1 (i.e., 0 if nonzero, 0xFFFFFFFF if zero)
|
| 1087 |
+
*(w.add(0xEC) as *mut u32) =
|
| 1088 |
+
((*(game_info_sub.add(0xF340) as *const u32) != 0) as u32).wrapping_sub(1);
|
| 1089 |
+
|
| 1090 |
+
*(game_info_sub.add(0xF34C) as *mut i32) = -1;
|
| 1091 |
+
(*wrapper).state_initialized = 1;
|
| 1092 |
+
|
| 1093 |
+
// ===== Serialize initial game state =====
|
| 1094 |
+
{
|
| 1095 |
+
let serialize: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8) =
|
| 1096 |
+
core::mem::transmute(rb(va::SERIALIZE_GAME_STATE) as usize);
|
| 1097 |
+
serialize(wrapper, (*wrapper).state_buffer);
|
| 1098 |
+
}
|
| 1099 |
+
|
| 1100 |
+
// ===== Copy game state to statistics buffer =====
|
| 1101 |
+
{
|
| 1102 |
+
let src = ddgame_raw.add(0x8168) as *const u32;
|
| 1103 |
+
let dst = (*wrapper).statistics as *mut u32;
|
| 1104 |
+
core::ptr::copy_nonoverlapping(src, dst, 0x2E5);
|
| 1105 |
+
}
|
| 1106 |
+
|
| 1107 |
+
// ===== Compute initial checksum =====
|
| 1108 |
+
{
|
| 1109 |
+
let buf_ptr = *((*wrapper).state_buffer as *const *const u8);
|
| 1110 |
+
let buf_len = *((*wrapper).state_buffer.add(0x0C) as *const u32); // field[3]
|
| 1111 |
+
let mut hash = 0u32;
|
| 1112 |
+
for i in 0..buf_len as usize {
|
| 1113 |
+
hash = (hash << 3 | hash >> 29).wrapping_add(*buf_ptr.add(i) as u32);
|
| 1114 |
+
}
|
| 1115 |
+
|
| 1116 |
+
// Get frame counter via landscape vtable
|
| 1117 |
+
let landscape_ptr = *(ddgame_raw.add(0x20) as *const *const u8);
|
| 1118 |
+
let landscape_vtable = *(landscape_ptr as *const *const u32);
|
| 1119 |
+
let get_frame: unsafe extern "thiscall" fn(*const u8) -> u32 =
|
| 1120 |
+
core::mem::transmute(*landscape_vtable.add(0x12));
|
| 1121 |
+
let frame = get_frame(landscape_ptr);
|
| 1122 |
+
|
| 1123 |
+
(*wrapper).initial_checksum = frame.wrapping_add(hash);
|
| 1124 |
+
}
|
| 1125 |
+
|
| 1126 |
+
// ===== Weapon panel (headful only) =====
|
| 1127 |
+
if is_headful {
|
| 1128 |
+
let mem = wa_malloc(0x208);
|
| 1129 |
+
core::ptr::write_bytes(mem, 0, 0x1E8);
|
| 1130 |
+
let result = if mem.is_null() {
|
| 1131 |
+
core::ptr::null_mut()
|
| 1132 |
+
} else {
|
| 1133 |
+
// usercall: ESI=this(mem), stack=(DDGame), RET 0x4
|
| 1134 |
+
call_usercall_esi_stack1(mem, ddgame as u32, rb(va::INIT_WEAPON_PANEL))
|
| 1135 |
+
};
|
| 1136 |
+
*(ddgame_raw.add(0x548) as *mut *mut u8) = result;
|
| 1137 |
+
}
|
| 1138 |
+
}
|
| 1139 |
+
|
| 1140 |
+
// =============================================================================
|
| 1141 |
+
// Helper functions for init_game_state
|
| 1142 |
+
// =============================================================================
|
| 1143 |
+
|
| 1144 |
+
/// Allocate a BufferObject (0x48 bytes) with size computed from game setup.
|
| 1145 |
+
/// Used for main_buffer and state_buffer.
|
| 1146 |
+
unsafe fn allocate_buffer_object(ddgame_raw: *mut u8, game_info_sub: *const u8) -> *mut u8 {
|
| 1147 |
+
use crate::address::va;
|
| 1148 |
+
use crate::rebase::rb;
|
| 1149 |
+
use crate::wa_alloc::wa_malloc;
|
| 1150 |
+
|
| 1151 |
+
let mem = wa_malloc(0x48) as *mut u32;
|
| 1152 |
+
// Zero first 10 dwords
|
| 1153 |
+
for i in 0..10 {
|
| 1154 |
+
*mem.add(i) = 0;
|
| 1155 |
+
}
|
| 1156 |
+
if mem.is_null() {
|
| 1157 |
+
return core::ptr::null_mut();
|
| 1158 |
+
}
|
| 1159 |
+
// Compute size: num_teams * 0x450 + 0x4F178 + num_objects * 0x70
|
| 1160 |
+
let num_teams = *(game_info_sub.add(0xD968) as *const u16) as u32;
|
| 1161 |
+
let num_objects = *(game_info_sub.add(0xD994) as *const i32);
|
| 1162 |
+
let size = num_teams * 0x450 + 0x4F178 + num_objects as u32 * 0x70;
|
| 1163 |
+
|
| 1164 |
+
let ctor: unsafe extern "stdcall" fn(*mut u32, u32, u32) -> *mut u8 =
|
| 1165 |
+
core::mem::transmute(rb(va::BUFFER_OBJECT_CONSTRUCTOR) as usize);
|
| 1166 |
+
ctor(mem, size, ddgame_raw as u32) // 3rd param is DDGame ptr as u32
|
| 1167 |
+
}
|
| 1168 |
+
|
| 1169 |
+
/// Allocate a raw ring-buffer-like object with manual field initialization.
|
| 1170 |
+
/// Used for objects at 0x3C (cap 0x2000), 0x28 (cap 0x2000), etc.
|
| 1171 |
+
/// `alloc_size` = total allocation size (0x3C or 0x48).
|
| 1172 |
+
/// `capacity` = buffer capacity (0x1000, 0x2000, or 0x10000).
|
| 1173 |
+
unsafe fn allocate_ring_buffer_raw(alloc_size: u32, capacity: u32) -> *mut u8 {
|
| 1174 |
+
use crate::wa_alloc::wa_malloc;
|
| 1175 |
+
|
| 1176 |
+
let n_dwords = alloc_size / 4;
|
| 1177 |
+
let mem = wa_malloc(alloc_size) as *mut u32;
|
| 1178 |
+
// Zero all dwords
|
| 1179 |
+
for i in 0..n_dwords as usize {
|
| 1180 |
+
*mem.add(i) = 0;
|
| 1181 |
+
}
|
| 1182 |
+
if mem.is_null() {
|
| 1183 |
+
return core::ptr::null_mut();
|
| 1184 |
+
}
|
| 1185 |
+
// Initialize: [1] = capacity, [0] = buffer ptr, [2..6] = 0
|
| 1186 |
+
*mem.add(1) = capacity;
|
| 1187 |
+
let buf = wa_malloc(capacity + 0x20);
|
| 1188 |
+
core::ptr::write_bytes(buf, 0, capacity as usize);
|
| 1189 |
+
*mem = buf as u32;
|
| 1190 |
+
*mem.add(6) = 0;
|
| 1191 |
+
*mem.add(5) = 0;
|
| 1192 |
+
*mem.add(4) = 0;
|
| 1193 |
+
*mem.add(3) = 0;
|
| 1194 |
+
*mem.add(2) = 0;
|
| 1195 |
+
|
| 1196 |
+
mem as *mut u8
|
| 1197 |
+
}
|
| 1198 |
+
|
| 1199 |
+
/// Allocate a RingBuffer using the already-ported ring_buffer_init function.
|
| 1200 |
+
/// Used for the conditional network ring buffer.
|
| 1201 |
+
unsafe fn allocate_ring_buffer_init(_wrapper: *mut u8) -> *mut u8 {
|
| 1202 |
+
use crate::wa_alloc::wa_malloc;
|
| 1203 |
+
|
| 1204 |
+
let mem = wa_malloc(0x3C) as *mut u32;
|
| 1205 |
+
for i in 0..7 {
|
| 1206 |
+
*mem.add(i) = 0;
|
| 1207 |
+
}
|
| 1208 |
+
if mem.is_null() {
|
| 1209 |
+
return core::ptr::null_mut();
|
| 1210 |
+
}
|
| 1211 |
+
ring_buffer_init(mem as *mut u8, 0x2000);
|
| 1212 |
+
mem as *mut u8
|
| 1213 |
+
}
|
| 1214 |
+
|
| 1215 |
+
/// Allocate and initialize a PaletteContext (0x72C bytes).
|
| 1216 |
+
/// Sets dirty_range_min=1, dirty_range_max=0xFF, then calls PaletteContext__Init.
|
| 1217 |
+
unsafe fn allocate_palette_context(
|
| 1218 |
+
_ddgame_raw: *mut u8,
|
| 1219 |
+
) -> *mut crate::render::palette::PaletteContext {
|
| 1220 |
+
use crate::address::va;
|
| 1221 |
+
use crate::rebase::rb;
|
| 1222 |
+
use crate::wa_alloc::wa_malloc;
|
| 1223 |
+
|
| 1224 |
+
let mem = wa_malloc(0x72C) as *mut u16;
|
| 1225 |
+
core::ptr::write_bytes(mem as *mut u8, 0, 0x70C);
|
| 1226 |
+
if mem.is_null() {
|
| 1227 |
+
return core::ptr::null_mut();
|
| 1228 |
+
}
|
| 1229 |
+
*mem = 1; // dirty_range_min
|
| 1230 |
+
*mem.add(1) = 0xFF; // dirty_range_max
|
| 1231 |
+
// Call PaletteContext__Init β usercall(EAX=ctx)
|
| 1232 |
+
call_usercall_eax(mem as *mut u8, rb(va::PALETTE_CONTEXT_INIT));
|
| 1233 |
+
*mem.add(900) = 0; // offset 0x708 (word)
|
| 1234 |
+
mem as *mut crate::render::palette::PaletteContext
|
| 1235 |
+
}
|
| 1236 |
+
|
| 1237 |
+
/// Bridge: usercall(EAX=ptr), plain RET.
|
| 1238 |
+
#[cfg(target_arch = "x86")]
|
| 1239 |
+
#[unsafe(naked)]
|
| 1240 |
+
unsafe extern "C" fn call_usercall_eax(_ptr: *mut u8, _addr: u32) {
|
| 1241 |
+
core::arch::naked_asm!(
|
| 1242 |
+
"movl 4(%esp), %eax", // EAX = ptr
|
| 1243 |
+
"movl 8(%esp), %ecx", // ECX = target address (temp)
|
| 1244 |
+
"calll *%ecx",
|
| 1245 |
+
"retl",
|
| 1246 |
+
options(att_syntax),
|
| 1247 |
+
);
|
| 1248 |
+
}
|
| 1249 |
+
|
| 1250 |
+
/// Bridge: stdcall constructor with ECX = implicit register param.
|
| 1251 |
+
/// cdecl(this, stack_param, ecx_val, target_addr) -> result
|
| 1252 |
+
/// Sets ECX, then calls target as stdcall(this, stack_param) -> result.
|
| 1253 |
+
#[cfg(target_arch = "x86")]
|
| 1254 |
+
#[unsafe(naked)]
|
| 1255 |
+
unsafe extern "C" fn call_ctor_with_ecx(
|
| 1256 |
+
_this: *mut u8,
|
| 1257 |
+
_stack_param: u32,
|
| 1258 |
+
_ecx_val: u32,
|
| 1259 |
+
_target: u32,
|
| 1260 |
+
) -> *mut u8 {
|
| 1261 |
+
core::arch::naked_asm!(
|
| 1262 |
+
// Stack: [ret_addr] [this] [stack_param] [ecx_val] [target]
|
| 1263 |
+
"movl 12(%esp), %ecx", // ECX = ecx_val
|
| 1264 |
+
"movl 16(%esp), %eax", // EAX = target address
|
| 1265 |
+
// Push stdcall args: stack_param, then this
|
| 1266 |
+
"pushl 8(%esp)", // push stack_param (now at esp+12 due to push)
|
| 1267 |
+
"pushl 8(%esp)", // push this (now at esp+12 due to two pushes)
|
| 1268 |
+
"calll *%eax", // call target (stdcall cleans 8 bytes)
|
| 1269 |
+
"retl",
|
| 1270 |
+
options(att_syntax),
|
| 1271 |
+
);
|
| 1272 |
+
}
|
| 1273 |
+
|
| 1274 |
+
/// Bridge: usercall(ESI=ptr, stack=param), plain RET 0x4.
|
| 1275 |
+
/// cdecl(ptr, stack_param, target_addr) -> result
|
| 1276 |
+
#[cfg(target_arch = "x86")]
|
| 1277 |
+
#[unsafe(naked)]
|
| 1278 |
+
unsafe extern "C" fn call_usercall_esi_stack1(
|
| 1279 |
+
_ptr: *mut u8,
|
| 1280 |
+
_stack_param: u32,
|
| 1281 |
+
_target: u32,
|
| 1282 |
+
) -> *mut u8 {
|
| 1283 |
+
core::arch::naked_asm!(
|
| 1284 |
+
// [ret@0] [ptr@4] [stack_param@8] [target@12]
|
| 1285 |
+
"pushl %esi", // save ESI
|
| 1286 |
+
"movl 8(%esp), %esi", // ESI = ptr (shifted by push)
|
| 1287 |
+
"movl 16(%esp), %eax", // EAX = target
|
| 1288 |
+
"pushl 12(%esp)", // push stack_param (shifted by 2 pushes)
|
| 1289 |
+
"calll *%eax", // stdcall cleans 4 bytes
|
| 1290 |
+
"popl %esi", // restore ESI
|
| 1291 |
+
"retl",
|
| 1292 |
+
options(att_syntax),
|
| 1293 |
+
);
|
| 1294 |
+
}
|
| 1295 |
+
|
| 1296 |
+
/// Display setup phase of InitGameState (headful only).
|
| 1297 |
+
///
|
| 1298 |
+
/// Creates DisplayGfx layers, camera objects, and the main display surface.
|
| 1299 |
+
unsafe fn init_game_state_display(
|
| 1300 |
+
wrapper: *mut DDGameWrapper,
|
| 1301 |
+
w: *mut u8,
|
| 1302 |
+
ddgame_raw: *mut u8,
|
| 1303 |
+
_game_info_sub: *const u8,
|
| 1304 |
+
) {
|
| 1305 |
+
use crate::address::va;
|
| 1306 |
+
use crate::rebase::rb;
|
| 1307 |
+
use crate::wa_alloc::wa_malloc;
|
| 1308 |
+
|
| 1309 |
+
let max_team_render = *(w.add(0x2B0) as *const i32);
|
| 1310 |
+
|
| 1311 |
+
// Display virtual call: display->vtable[1](&display_width, &display_height)
|
| 1312 |
+
// Returns display dimensions via output pointers.
|
| 1313 |
+
let mut display_width: u32 = 0;
|
| 1314 |
+
let mut display_height: u32 = 0;
|
| 1315 |
+
{
|
| 1316 |
+
let display_ptr = *(ddgame_raw.add(4) as *const *const u8);
|
| 1317 |
+
let vtable = *(display_ptr as *const *const u32);
|
| 1318 |
+
let vmethod: unsafe extern "thiscall" fn(*const u8, *mut u32, *mut u32) =
|
| 1319 |
+
core::mem::transmute(*vtable.add(1));
|
| 1320 |
+
vmethod(display_ptr, &mut display_width, &mut display_height);
|
| 1321 |
+
}
|
| 1322 |
+
|
| 1323 |
+
// Screen height for HUD: 0x12C (300) if wide layout, 0x8C (140) if narrow
|
| 1324 |
+
let screen_height: i32 = if max_team_render != 0xC {
|
| 1325 |
+
0xA0 + 0x8C // 300
|
| 1326 |
+
} else {
|
| 1327 |
+
0x8C // 140
|
| 1328 |
+
};
|
| 1329 |
+
*(w.add(0x388) as *mut i32) = screen_height;
|
| 1330 |
+
|
| 1331 |
+
// Count active teams from slot_to_team array
|
| 1332 |
+
let team_count = *_game_info_sub as u32;
|
| 1333 |
+
let count_active = |w: *mut u8, base: u32| -> u32 {
|
| 1334 |
+
let mut n = base;
|
| 1335 |
+
if *(w.add(0x3AC) as *const i32) >= 0 {
|
| 1336 |
+
let mut p = w.add(0x3AC) as *const i32;
|
| 1337 |
+
loop {
|
| 1338 |
+
p = p.add(1);
|
| 1339 |
+
n += 1;
|
| 1340 |
+
if *p < 0 {
|
| 1341 |
+
break;
|
| 1342 |
+
}
|
| 1343 |
+
}
|
| 1344 |
+
}
|
| 1345 |
+
n
|
| 1346 |
+
};
|
| 1347 |
+
let active_count = count_active(w, team_count);
|
| 1348 |
+
|
| 1349 |
+
// min_teams: (int)(active_count - 1) < 2 ? 1 : active_count - 1
|
| 1350 |
+
let min_teams: i32 = if (active_count.wrapping_sub(1) as i32) < 2 {
|
| 1351 |
+
1
|
| 1352 |
+
} else {
|
| 1353 |
+
count_active(w, team_count) as i32 - 1
|
| 1354 |
+
};
|
| 1355 |
+
*(w.add(0x2A8) as *mut i32) = min_teams;
|
| 1356 |
+
|
| 1357 |
+
// screen_offset = display_width - screen_height
|
| 1358 |
+
*(w.add(0x384) as *mut i32) = display_width as i32 - screen_height;
|
| 1359 |
+
let screen_offset = display_width as i32 - screen_height;
|
| 1360 |
+
|
| 1361 |
+
// ===== Layer 1 (wrapper+0x18): BitGrid(8, max_team*64+2, screen_offset-7) =====
|
| 1362 |
+
{
|
| 1363 |
+
let height = max_team_render * 64 + 2;
|
| 1364 |
+
let width = screen_offset - 7;
|
| 1365 |
+
(*wrapper).display_gfx_a = create_display_gfx_layer_sized(height as u32, width as u32);
|
| 1366 |
+
}
|
| 1367 |
+
|
| 1368 |
+
// ===== Layer 2 (wrapper+0x1C): BitGrid(8, max_team*33+6, display_width) =====
|
| 1369 |
+
{
|
| 1370 |
+
let height = max_team_render * 33 + 6;
|
| 1371 |
+
(*wrapper).display_gfx_b = create_display_gfx_layer_sized(height as u32, display_width);
|
| 1372 |
+
}
|
| 1373 |
+
|
| 1374 |
+
// ===== Layer 3 (wrapper+0x20): BitGrid(8, (min_teams+1)*max_team+1, screen_height) =====
|
| 1375 |
+
{
|
| 1376 |
+
let height = (min_teams + 1) * max_team_render + 1;
|
| 1377 |
+
(*wrapper).display_gfx_c =
|
| 1378 |
+
create_display_gfx_layer_sized(height as u32, screen_height as u32);
|
| 1379 |
+
}
|
| 1380 |
+
|
| 1381 |
+
// ===== ConstructFull for main display (wrapper+0x24) =====
|
| 1382 |
+
// usercall: ECX=DDGame+0x7328, EDX=max_team_render_index
|
| 1383 |
+
// stdcall stack: (this, display, team_idx, screen_offset-3, render_param)
|
| 1384 |
+
{
|
| 1385 |
+
let mem = wa_malloc(0x468);
|
| 1386 |
+
core::ptr::write_bytes(mem, 0, 0x448);
|
| 1387 |
+
let result = if mem.is_null() {
|
| 1388 |
+
core::ptr::null_mut()
|
| 1389 |
+
} else {
|
| 1390 |
+
{
|
| 1391 |
+
let p_display = *(ddgame_raw.add(4) as *const u32);
|
| 1392 |
+
let p_team = *(w.add(0x280) as *const u32);
|
| 1393 |
+
let p_offset = screen_offset - 3;
|
| 1394 |
+
let p_render = *(ddgame_raw.add(0x732C) as *const u32);
|
| 1395 |
+
let p_ecx = *(ddgame_raw.add(0x7328) as *const u32);
|
| 1396 |
+
let p_edx = max_team_render as u32;
|
| 1397 |
+
let target = rb(va::DISPLAYGFX_CONSTRUCT_FULL);
|
| 1398 |
+
// Use fastcall directly: ECX=ecx_val, EDX=edx_val, 5 stack params
|
| 1399 |
+
let f: unsafe extern "fastcall" fn(
|
| 1400 |
+
u32,
|
| 1401 |
+
u32,
|
| 1402 |
+
*mut u8,
|
| 1403 |
+
u32,
|
| 1404 |
+
u32,
|
| 1405 |
+
i32,
|
| 1406 |
+
u32,
|
| 1407 |
+
) -> *mut u8 = core::mem::transmute(target as usize);
|
| 1408 |
+
f(p_ecx, p_edx, mem, p_display, p_team, p_offset, p_render)
|
| 1409 |
+
}
|
| 1410 |
+
};
|
| 1411 |
+
(*wrapper).display_gfx_main = result;
|
| 1412 |
+
}
|
| 1413 |
+
|
| 1414 |
+
// Turn timer
|
| 1415 |
+
{
|
| 1416 |
+
let timer_val = max_team_render << 5;
|
| 1417 |
+
*(w.add(0x400) as *mut i32) = timer_val;
|
| 1418 |
+
*(w.add(0x408) as *mut i32) = timer_val;
|
| 1419 |
+
*(w.add(0x404) as *mut i32) = 0;
|
| 1420 |
+
}
|
| 1421 |
+
|
| 1422 |
+
// Dirty rect / clipping calls on layers A, B, C
|
| 1423 |
+
call_display_gfx_dirty_rect((*wrapper).display_gfx_a, ddgame_raw);
|
| 1424 |
+
call_display_gfx_dirty_rect((*wrapper).display_gfx_b, ddgame_raw);
|
| 1425 |
+
call_display_gfx_dirty_rect((*wrapper).display_gfx_c, ddgame_raw);
|
| 1426 |
+
|
| 1427 |
+
// ===== Layer 4 (wrapper+0x2C): BitGrid(8, 0x100, 0x154) β constant =====
|
| 1428 |
+
(*wrapper).display_gfx_d = create_display_gfx_layer_sized(0x100, 0x154);
|
| 1429 |
+
|
| 1430 |
+
// ===== Layer 5 (wrapper+0x34): BitGrid(8, 0x30, 0xC0) β constant =====
|
| 1431 |
+
(*wrapper).display_gfx_e = create_display_gfx_layer_sized(0x30, 0xC0);
|
| 1432 |
+
|
| 1433 |
+
// Create 2 camera objects (wrapper+0x30, +0x38)
|
| 1434 |
+
(*wrapper).camera_a = create_camera_object(wrapper, ddgame_raw, 0x2C);
|
| 1435 |
+
(*wrapper).camera_b = create_camera_object(wrapper, ddgame_raw, 0x34);
|
| 1436 |
+
}
|
| 1437 |
+
|
| 1438 |
+
/// Create a DisplayGfx layer (0x4C bytes): malloc + memset + BitGrid__Init + vtable.
|
| 1439 |
+
/// Each layer has specific height/width for its BitGrid.
|
| 1440 |
+
unsafe fn create_display_gfx_layer_sized(height: u32, width: u32) -> *mut u8 {
|
| 1441 |
+
use crate::bitgrid::{BitGrid, BIT_GRID_DISPLAY_VTABLE};
|
| 1442 |
+
use crate::rebase::rb;
|
| 1443 |
+
use crate::wa_alloc::wa_malloc;
|
| 1444 |
+
|
| 1445 |
+
let mem = wa_malloc(0x4C) as *mut u32;
|
| 1446 |
+
core::ptr::write_bytes(mem as *mut u8, 0, 0x4C);
|
| 1447 |
+
if mem.is_null() {
|
| 1448 |
+
return core::ptr::null_mut();
|
| 1449 |
+
}
|
| 1450 |
+
// BitGrid::init at the start of the buffer (this IS a BitGrid, not a wrapper)
|
| 1451 |
+
BitGrid::init(mem as *mut BitGrid, 8, width, height);
|
| 1452 |
+
// Override base vtable (0x6640EC) with DisplayBitGrid vtable (0x664144)
|
| 1453 |
+
*mem = rb(BIT_GRID_DISPLAY_VTABLE);
|
| 1454 |
+
mem as *mut u8
|
| 1455 |
+
}
|
| 1456 |
+
|
| 1457 |
+
/// Call the DisplayGfx dirty rect/clipping vtable dispatch (slot 0).
|
| 1458 |
+
unsafe fn call_display_gfx_dirty_rect(gfx: *mut u8, ddgame_raw: *mut u8) {
|
| 1459 |
+
if gfx.is_null() {
|
| 1460 |
+
return;
|
| 1461 |
+
}
|
| 1462 |
+
let gfx32 = gfx as *mut u32;
|
| 1463 |
+
let height = *gfx32.add(6);
|
| 1464 |
+
let width = *gfx32.add(5);
|
| 1465 |
+
if width <= 0 || height <= 0 {
|
| 1466 |
+
return;
|
| 1467 |
+
}
|
| 1468 |
+
if *(gfx32.add(9)) <= 0 || *(gfx32.add(10)) <= 0 {
|
| 1469 |
+
return;
|
| 1470 |
+
}
|
| 1471 |
+
let clip_x1 = *gfx32.add(7) as i32;
|
| 1472 |
+
let clip_y1 = *gfx32.add(8) as i32;
|
| 1473 |
+
if clip_x1 >= width as i32 || clip_y1 >= height as i32 {
|
| 1474 |
+
return;
|
| 1475 |
+
}
|
| 1476 |
+
|
| 1477 |
+
let x1 = if clip_x1 > 0 { clip_x1 } else { 0 };
|
| 1478 |
+
let y1 = if clip_y1 > 0 { clip_y1 } else { 0 };
|
| 1479 |
+
let x2 = if (*gfx32.add(9) as i32) < width as i32 {
|
| 1480 |
+
*gfx32.add(9) as i32
|
| 1481 |
+
} else {
|
| 1482 |
+
width as i32
|
| 1483 |
+
};
|
| 1484 |
+
let y2 = if (*gfx32.add(10) as i32) < height as i32 {
|
| 1485 |
+
*gfx32.add(10) as i32
|
| 1486 |
+
} else {
|
| 1487 |
+
height as i32
|
| 1488 |
+
};
|
| 1489 |
+
|
| 1490 |
+
let vtable = *(gfx as *const *const u32);
|
| 1491 |
+
let vmethod: unsafe extern "thiscall" fn(*mut u8, i32, i32, i32, i32, u32) =
|
| 1492 |
+
core::mem::transmute(*vtable);
|
| 1493 |
+
vmethod(gfx, x1, y1, x2, y2, *(ddgame_raw.add(0x7328) as *const u32));
|
| 1494 |
+
}
|
| 1495 |
+
|
| 1496 |
+
/// Create a camera/display object (0x3D4 bytes).
|
| 1497 |
+
unsafe fn create_camera_object(
|
| 1498 |
+
wrapper: *mut DDGameWrapper,
|
| 1499 |
+
ddgame_raw: *mut u8,
|
| 1500 |
+
display_gfx_offset: usize,
|
| 1501 |
+
) -> *mut u8 {
|
| 1502 |
+
use crate::wa_alloc::wa_malloc;
|
| 1503 |
+
|
| 1504 |
+
let mem = wa_malloc(0x3D4) as *mut i32;
|
| 1505 |
+
core::ptr::write_bytes(mem as *mut u8, 0, 0x3B4);
|
| 1506 |
+
if mem.is_null() {
|
| 1507 |
+
return core::ptr::null_mut();
|
| 1508 |
+
}
|
| 1509 |
+
|
| 1510 |
+
let w = wrapper as *mut u8;
|
| 1511 |
+
let display_gfx = *(w.add(display_gfx_offset) as *const i32);
|
| 1512 |
+
|
| 1513 |
+
*mem.add(1) = *(ddgame_raw.add(4) as *const i32); // display
|
| 1514 |
+
*mem.add(3) = *(ddgame_raw.add(0x730C) as *const i32);
|
| 1515 |
+
*mem.add(2) = *(ddgame_raw.add(0x7328) as *const i32);
|
| 1516 |
+
*mem = display_gfx;
|
| 1517 |
+
|
| 1518 |
+
let w_val = *((display_gfx as *const u8).add(0x14) as *const i32);
|
| 1519 |
+
let h_val = *((display_gfx as *const u8).add(0x18) as *const i32);
|
| 1520 |
+
|
| 1521 |
+
*mem.add(9) = w_val;
|
| 1522 |
+
*mem.add(4) = w_val / 2;
|
| 1523 |
+
*mem.add(6) = 0;
|
| 1524 |
+
*mem.add(7) = 0;
|
| 1525 |
+
*mem.add(8) = 0;
|
| 1526 |
+
*mem.add(10) = h_val;
|
| 1527 |
+
*mem.add(5) = h_val / 2;
|
| 1528 |
+
*mem.add(0xB) = 0;
|
| 1529 |
+
*mem.add(0xEC) = 0;
|
| 1530 |
+
|
| 1531 |
+
mem as *mut u8
|
| 1532 |
+
}
|
| 1533 |
+
|
| 1534 |
+
/// Initialize level bounds and camera center positions.
|
| 1535 |
+
unsafe fn init_game_state_level_bounds(ddgame_raw: *mut u8, game_info_sub: *const u8) {
|
| 1536 |
+
// DDGame+0x7794 / +0x7798 = 0
|
| 1537 |
+
*(ddgame_raw.add(0x7794) as *mut u32) = 0;
|
| 1538 |
+
*(ddgame_raw.add(0x7798) as *mut u32) = 0;
|
| 1539 |
+
|
| 1540 |
+
let is_cavern = *(ddgame_raw.add(0x777C) as *const i32);
|
| 1541 |
+
if is_cavern == 0 {
|
| 1542 |
+
// Open-air level
|
| 1543 |
+
*(ddgame_raw.add(0x779C) as *mut u32) = 0xF802_0000u32;
|
| 1544 |
+
*(ddgame_raw.add(0x77A4) as *mut u32) = 0xF802_0000u32;
|
| 1545 |
+
let level_width = *(ddgame_raw.add(0x77C0) as *const i32);
|
| 1546 |
+
*(ddgame_raw.add(0x77A0) as *mut i32) = (level_width + 0x7FE) * 0x10000;
|
| 1547 |
+
} else {
|
| 1548 |
+
// Cavern level
|
| 1549 |
+
let game_version = *(game_info_sub.add(0xD778) as *const i32);
|
| 1550 |
+
let bound = if game_version >= -1 { 0x20000u32 } else { 0u32 };
|
| 1551 |
+
*(ddgame_raw.add(0x779C) as *mut u32) = bound;
|
| 1552 |
+
*(ddgame_raw.add(0x77A4) as *mut u32) = 0x20000;
|
| 1553 |
+
let level_width = *(ddgame_raw.add(0x77C0) as *const i32);
|
| 1554 |
+
*(ddgame_raw.add(0x77A0) as *mut u32) = (level_width as u32)
|
| 1555 |
+
.wrapping_mul(0x10000)
|
| 1556 |
+
.wrapping_sub(bound);
|
| 1557 |
+
}
|
| 1558 |
+
|
| 1559 |
+
// Camera center initialization (4 viewports)
|
| 1560 |
+
for viewport_offset in [0x8CBCu32, 0x8CCCu32, 0x8CDCu32, 0x8CECu32] {
|
| 1561 |
+
let level_w = *(ddgame_raw.add(0x77C0) as *const i32);
|
| 1562 |
+
let level_h = *(ddgame_raw.add(0x77C4) as *const i32);
|
| 1563 |
+
let cx = (level_w << 16) / 2;
|
| 1564 |
+
let cy = (level_h * 0x10000) / 2;
|
| 1565 |
+
*(ddgame_raw.add(viewport_offset as usize + 0x18) as *mut i32) = cx;
|
| 1566 |
+
*(ddgame_raw.add(viewport_offset as usize + 0x10) as *mut i32) = cx;
|
| 1567 |
+
*(ddgame_raw.add(viewport_offset as usize + 0x1C) as *mut i32) = cy;
|
| 1568 |
+
*(ddgame_raw.add(viewport_offset as usize + 0x14) as *mut i32) = cy;
|
| 1569 |
+
}
|
| 1570 |
+
|
| 1571 |
+
// Map dimension fields
|
| 1572 |
+
*(ddgame_raw.add(0x77CC) as *mut u32) = 0x30D4;
|
| 1573 |
+
*(ddgame_raw.add(0x77D0) as *mut u32) = *(ddgame_raw.add(0x77C4) as *const u32);
|
| 1574 |
+
|
| 1575 |
+
let game_version = *(game_info_sub.add(0xD778) as *const i32);
|
| 1576 |
+
if game_version > 0x32 {
|
| 1577 |
+
let level_w = *(ddgame_raw.add(0x77C0) as *const i32);
|
| 1578 |
+
*(ddgame_raw.add(0x77CC) as *mut i32) = level_w + 0x2954;
|
| 1579 |
+
*(ddgame_raw.add(0x77D0) as *mut u32) = 0x2B8;
|
| 1580 |
+
}
|
| 1581 |
+
}
|
| 1582 |
+
|
| 1583 |
+
/// Create HUD display objects (headful only, conditional on is_headful).
|
| 1584 |
+
unsafe fn init_game_state_hud_objects(
|
| 1585 |
+
_wrapper: *mut DDGameWrapper,
|
| 1586 |
+
ddgame_raw: *mut u8,
|
| 1587 |
+
_game_info_sub: *const u8,
|
| 1588 |
+
) {
|
| 1589 |
+
use crate::address::va;
|
| 1590 |
+
use crate::rebase::rb;
|
| 1591 |
+
use crate::wa_alloc::wa_malloc;
|
| 1592 |
+
|
| 1593 |
+
// DisplayObject for HUD background
|
| 1594 |
+
// usercall: ECX=DDGame+0x7324, EDX=DDGame+0x7328, stdcall stack=(this, DDGame+0x33C)
|
| 1595 |
+
{
|
| 1596 |
+
let mem = wa_malloc(0x58) as *mut u32;
|
| 1597 |
+
core::ptr::write_bytes(mem as *mut u8, 0, 0x38);
|
| 1598 |
+
let result = if mem.is_null() {
|
| 1599 |
+
core::ptr::null_mut()
|
| 1600 |
+
} else {
|
| 1601 |
+
let ctor: unsafe extern "fastcall" fn(u32, u32, *mut u32, u32) -> *mut u8 =
|
| 1602 |
+
core::mem::transmute(rb(va::DISPLAY_OBJECT_CONSTRUCTOR) as usize);
|
| 1603 |
+
ctor(
|
| 1604 |
+
*(ddgame_raw.add(0x7324) as *const u32), // ECX
|
| 1605 |
+
*(ddgame_raw.add(0x7328) as *const u32), // EDX
|
| 1606 |
+
mem,
|
| 1607 |
+
ddgame_raw as u32 + 0x33C,
|
| 1608 |
+
)
|
| 1609 |
+
};
|
| 1610 |
+
*(ddgame_raw.add(0x540) as *mut *mut u8) = result;
|
| 1611 |
+
}
|
| 1612 |
+
|
| 1613 |
+
// DisplayGfx textbox for HUD text
|
| 1614 |
+
// thiscall: ECX=DDGame.display, stack=(this, 0x13, 2)
|
| 1615 |
+
{
|
| 1616 |
+
let mem = wa_malloc(0x158) as *mut u32;
|
| 1617 |
+
core::ptr::write_bytes(mem as *mut u8, 0, 0x138);
|
| 1618 |
+
let result = if mem.is_null() {
|
| 1619 |
+
core::ptr::null_mut()
|
| 1620 |
+
} else {
|
| 1621 |
+
let ctor: unsafe extern "thiscall" fn(u32, *mut u32, u32, u32) -> *mut u8 =
|
| 1622 |
+
core::mem::transmute(rb(va::CONSTRUCT_TEXTBOX) as usize);
|
| 1623 |
+
ctor(
|
| 1624 |
+
*(ddgame_raw.add(4) as *const u32), // ECX = DDGame.display
|
| 1625 |
+
mem,
|
| 1626 |
+
0x13,
|
| 1627 |
+
2,
|
| 1628 |
+
)
|
| 1629 |
+
};
|
| 1630 |
+
*(ddgame_raw.add(0x544) as *mut *mut u8) = result;
|
| 1631 |
+
}
|
| 1632 |
+
}
|
| 1633 |
+
|
| 1634 |
+
/// Validate team names β check for CPU team markers.
|
| 1635 |
+
unsafe fn init_game_state_team_names(ddgame_raw: *mut u8, game_info_sub: *const u8) {
|
| 1636 |
+
// Set initial team color from game_info
|
| 1637 |
+
*(ddgame_raw.add(0x7370) as *mut u32) = *(game_info_sub.add(0xD924) as *const u8) as u32;
|
| 1638 |
+
|
| 1639 |
+
// Check team names for CPU marker (high bit set)
|
| 1640 |
+
let team_count = *(game_info_sub.add(0x44C) as *const u8);
|
| 1641 |
+
if team_count != 0 {
|
| 1642 |
+
for i in 0..team_count as usize {
|
| 1643 |
+
let name_byte = *(game_info_sub.add(0x450 + i * 3000) as *const u8);
|
| 1644 |
+
if name_byte >= 0x80 {
|
| 1645 |
+
// CPU team detected β set team color to -1
|
| 1646 |
+
*(ddgame_raw.add(0x7370) as *mut u32) = 0xFFFF_FFFF;
|
| 1647 |
+
break;
|
| 1648 |
+
}
|
| 1649 |
+
}
|
| 1650 |
+
}
|
| 1651 |
+
}
|
| 1652 |
+
|
| 1653 |
+
/// Version-dependent configuration (stack size, rendering constants).
|
| 1654 |
+
unsafe fn init_game_state_version_config(ddgame_raw: *mut u8, game_info_sub: *const u8) {
|
| 1655 |
+
let game_version = *(game_info_sub.add(0xD778) as *const i32);
|
| 1656 |
+
|
| 1657 |
+
// Stack height: ((game_version < -1) - 1) & 0x100 + 0x700
|
| 1658 |
+
// game_version < -1: result = 0x700
|
| 1659 |
+
// game_version >= -1: result = 0x800
|
| 1660 |
+
let stack_val: u32 = if game_version < -1 { 0x700 } else { 0x800 };
|
| 1661 |
+
*(ddgame_raw.add(0x5C8) as *mut u32) = stack_val;
|
| 1662 |
+
|
| 1663 |
+
// Copy RNG seed
|
| 1664 |
+
*(ddgame_raw.add(0x45EC) as *mut u32) = *(game_info_sub.add(0xD774) as *const u32);
|
| 1665 |
+
*(ddgame_raw.add(0x45F0) as *mut u32) = *(game_info_sub.add(0xD774) as *const u32);
|
| 1666 |
+
|
| 1667 |
+
// Zero various fields
|
| 1668 |
+
*(ddgame_raw.add(0x5CC) as *mut u32) = 0;
|
| 1669 |
+
*(ddgame_raw.add(0x5D4) as *mut u32) = 0;
|
| 1670 |
+
*(ddgame_raw.add(0x5D8) as *mut u32) = 0;
|
| 1671 |
+
*(ddgame_raw.add(0x5DC) as *mut u32) = 0;
|
| 1672 |
+
*(ddgame_raw.add(0x7E48) as *mut u32) = 0;
|
| 1673 |
+
*(ddgame_raw.add(0x5D0) as *mut u32) = 0;
|
| 1674 |
+
|
| 1675 |
+
// Zero counter arrays (6 entries each Γ 3 blocks)
|
| 1676 |
+
for block_base in [0x7EA8u32, 0x7EC0, 0x7ED8] {
|
| 1677 |
+
for i in 0..6u32 {
|
| 1678 |
+
*(ddgame_raw.add((block_base + i * 4) as usize) as *mut u32) = 0;
|
| 1679 |
+
}
|
| 1680 |
+
}
|
| 1681 |
+
|
| 1682 |
+
// Zero more fields
|
| 1683 |
+
*(ddgame_raw.add(0x45E0) as *mut u32) = 0;
|
| 1684 |
+
*(ddgame_raw.add(0x45E4) as *mut u32) = 0;
|
| 1685 |
+
*(ddgame_raw.add(0x45E8) as *mut u32) = 0;
|
| 1686 |
+
}
|
| 1687 |
+
|
| 1688 |
+
/// Water level and boundary calculations.
|
| 1689 |
+
unsafe fn init_game_state_water_level(ddgame_raw: *mut u8, game_info_sub: *const u8) {
|
| 1690 |
+
// Water level: (100 - water_pct) * level_height / 100
|
| 1691 |
+
let water_pct = *(ddgame_raw.add(0x7780) as *const i32);
|
| 1692 |
+
let level_height = *(ddgame_raw.add(0x77C4) as *const i32);
|
| 1693 |
+
let water_level = ((100 - water_pct) * level_height) / 100;
|
| 1694 |
+
*(ddgame_raw.add(0x5E0) as *mut i32) = water_level;
|
| 1695 |
+
|
| 1696 |
+
// Clamp to 0 for newer versions
|
| 1697 |
+
let game_version = *(game_info_sub.add(0xD778) as *const i32);
|
| 1698 |
+
if water_level < 0 && game_version > 0x179 {
|
| 1699 |
+
*(ddgame_raw.add(0x5E0) as *mut i32) = 0;
|
| 1700 |
+
}
|
| 1701 |
+
|
| 1702 |
+
// Max Y boundary
|
| 1703 |
+
let water_val = *(ddgame_raw.add(0x5E0) as *const i32);
|
| 1704 |
+
let min_y = *(ddgame_raw.add(0x77A4) as *const i32);
|
| 1705 |
+
let max_y_candidate = (water_val + 0xA0) * 0x10000;
|
| 1706 |
+
let min_y_plus = min_y + 0x300_0000;
|
| 1707 |
+
let max_y = if max_y_candidate > min_y_plus {
|
| 1708 |
+
max_y_candidate
|
| 1709 |
+
} else {
|
| 1710 |
+
min_y_plus
|
| 1711 |
+
};
|
| 1712 |
+
*(ddgame_raw.add(0x77A8) as *mut i32) = max_y;
|
| 1713 |
+
|
| 1714 |
+
// Clamp for newer versions
|
| 1715 |
+
if game_version > 0x11E && (max_y >> 16) + 0x28 > 0x7FFF {
|
| 1716 |
+
*(ddgame_raw.add(0x77A8) as *mut u32) = 0x7FD7_0000;
|
| 1717 |
+
}
|
| 1718 |
+
|
| 1719 |
+
// Derived water fields
|
| 1720 |
+
*(ddgame_raw.add(0x5E4) as *mut i32) = *(ddgame_raw.add(0x77AA) as *const i16) as i32 + 0x28;
|
| 1721 |
+
*(ddgame_raw.add(0x5F8) as *mut u32) = 0;
|
| 1722 |
+
*(ddgame_raw.add(0x5E8) as *mut u32) = *(ddgame_raw.add(0x5E0) as *const u32);
|
| 1723 |
+
*(ddgame_raw.add(0x5EC) as *mut u32) = 0;
|
| 1724 |
+
|
| 1725 |
+
// Terrain type percentages
|
| 1726 |
+
*(ddgame_raw.add(0x45D4) as *mut u32) = *(game_info_sub.add(0xD955) as *const u8) as u32;
|
| 1727 |
+
*(ddgame_raw.add(0x45D8) as *mut u32) = *(game_info_sub.add(0xD958) as *const u8) as u32;
|
| 1728 |
+
*(ddgame_raw.add(0x45DC) as *mut u32) = *(game_info_sub.add(0xD957) as *const u8) as u32;
|
| 1729 |
+
|
| 1730 |
+
// Game state flags
|
| 1731 |
+
*(ddgame_raw.add(0x5F0) as *mut u32) = 1;
|
| 1732 |
+
*(ddgame_raw.add(0x5F4) as *mut u32) = 100;
|
| 1733 |
+
*(ddgame_raw.add(0x5FC) as *mut u32) = 0;
|
| 1734 |
+
}
|
| 1735 |
+
|
| 1736 |
+
/// Random bag initialization (land, mine, barrel, weapon).
|
| 1737 |
+
unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const u8) {
|
| 1738 |
+
// Random bag at DDGame+0x360C (5 zeroes + 1 one)
|
| 1739 |
+
{
|
| 1740 |
+
let write_idx_ptr = ddgame_raw.add(0x379C) as *mut i32;
|
| 1741 |
+
let mut write_idx = *write_idx_ptr;
|
| 1742 |
+
if write_idx + 5 < 0x65 {
|
| 1743 |
+
for _ in 0..5 {
|
| 1744 |
+
*(ddgame_raw.add(0x360C) as *mut u32).add(write_idx as usize) = 0;
|
| 1745 |
+
write_idx += 1;
|
| 1746 |
+
}
|
| 1747 |
+
*write_idx_ptr = write_idx;
|
| 1748 |
+
}
|
| 1749 |
+
if write_idx + 1 < 0x65 {
|
| 1750 |
+
*(ddgame_raw.add(0x360C) as *mut u32).add(write_idx as usize) = 1;
|
| 1751 |
+
*write_idx_ptr = write_idx + 1;
|
| 1752 |
+
}
|
| 1753 |
+
}
|
| 1754 |
+
|
| 1755 |
+
// Terrain type percentages validation and clamping
|
| 1756 |
+
{
|
| 1757 |
+
let pct_land = *(game_info_sub.add(0xD938) as *const u8) as i32;
|
| 1758 |
+
let pct_mine = *(game_info_sub.add(0xD939) as *const u8) as i32;
|
| 1759 |
+
let pct_barrel = *(game_info_sub.add(0xD93A) as *const u8) as i32;
|
| 1760 |
+
|
| 1761 |
+
let mut remaining = 100 - pct_land;
|
| 1762 |
+
if remaining < 0 {
|
| 1763 |
+
// Land > 100%, clamp all
|
| 1764 |
+
*(game_info_sub.add(0xD938) as *mut u8) = 100;
|
| 1765 |
+
*(game_info_sub.add(0xD939) as *mut u8) = 0;
|
| 1766 |
+
*(game_info_sub.add(0xD93A) as *mut u8) = 0;
|
| 1767 |
+
remaining = 0;
|
| 1768 |
+
} else {
|
| 1769 |
+
remaining -= pct_mine;
|
| 1770 |
+
if remaining < 0 {
|
| 1771 |
+
*(game_info_sub.add(0xD939) as *mut u8) = (pct_mine + remaining) as u8;
|
| 1772 |
+
*(game_info_sub.add(0xD93A) as *mut u8) = 0;
|
| 1773 |
+
remaining = 0;
|
| 1774 |
+
} else {
|
| 1775 |
+
remaining -= pct_barrel;
|
| 1776 |
+
if remaining < 0 {
|
| 1777 |
+
*(game_info_sub.add(0xD93A) as *mut u8) = (pct_barrel + remaining) as u8;
|
| 1778 |
+
}
|
| 1779 |
+
}
|
| 1780 |
+
}
|
| 1781 |
+
|
| 1782 |
+
// Extended terrain type from game_info
|
| 1783 |
+
let ext_type = *(game_info_sub.add(0xD96B) as *const u8);
|
| 1784 |
+
let ext_pct = *(game_info_sub.add(0xD96A) as *const u8) as i32;
|
| 1785 |
+
if ext_type == 0 {
|
| 1786 |
+
*(game_info_sub.add(0xD96A) as *mut u8) = remaining as u8;
|
| 1787 |
+
} else if remaining < ext_pct {
|
| 1788 |
+
*(game_info_sub.add(0xD96A) as *mut u8) = 0;
|
| 1789 |
+
}
|
| 1790 |
+
}
|
| 1791 |
+
|
| 1792 |
+
// Fill terrain type random bags at DDGame+0x3F84
|
| 1793 |
+
{
|
| 1794 |
+
let write_idx_ptr = ddgame_raw.add(0x4114) as *mut i32;
|
| 1795 |
+
|
| 1796 |
+
// Land entries (type 1)
|
| 1797 |
+
let count = *(game_info_sub.add(0xD938) as *const u8) as u32;
|
| 1798 |
+
let mut idx = *write_idx_ptr;
|
| 1799 |
+
if (idx as u32 + count) < 0x65 {
|
| 1800 |
+
for _ in 0..count {
|
| 1801 |
+
*(ddgame_raw.add(0x3F84) as *mut u32).add(idx as usize) = 1;
|
| 1802 |
+
idx += 1;
|
| 1803 |
+
}
|
| 1804 |
+
*write_idx_ptr = idx;
|
| 1805 |
+
}
|
| 1806 |
+
|
| 1807 |
+
// Mine entries (type 2)
|
| 1808 |
+
let count = *(game_info_sub.add(0xD939) as *const u8) as u32;
|
| 1809 |
+
idx = *write_idx_ptr;
|
| 1810 |
+
if (idx as u32 + count) < 0x65 {
|
| 1811 |
+
for _ in 0..count {
|
| 1812 |
+
*(ddgame_raw.add(0x3F84) as *mut u32).add(idx as usize) = 2;
|
| 1813 |
+
idx += 1;
|
| 1814 |
+
}
|
| 1815 |
+
*write_idx_ptr = idx;
|
| 1816 |
+
}
|
| 1817 |
+
|
| 1818 |
+
// Barrel entries (type 4)
|
| 1819 |
+
let count = *(game_info_sub.add(0xD93A) as *const u8) as u32;
|
| 1820 |
+
idx = *write_idx_ptr;
|
| 1821 |
+
if (idx as u32 + count) < 0x65 {
|
| 1822 |
+
for _ in 0..count {
|
| 1823 |
+
*(ddgame_raw.add(0x3F84) as *mut u32).add(idx as usize) = 4;
|
| 1824 |
+
idx += 1;
|
| 1825 |
+
}
|
| 1826 |
+
*write_idx_ptr = idx;
|
| 1827 |
+
}
|
| 1828 |
+
|
| 1829 |
+
// Extended entries (type 0)
|
| 1830 |
+
let count = *(game_info_sub.add(0xD96A) as *const u8) as u32;
|
| 1831 |
+
idx = *write_idx_ptr;
|
| 1832 |
+
if (idx as u32 + count) < 0x65 {
|
| 1833 |
+
for _ in 0..count {
|
| 1834 |
+
*(ddgame_raw.add(0x3F84) as *mut u32).add(idx as usize) = 0;
|
| 1835 |
+
idx += 1;
|
| 1836 |
+
}
|
| 1837 |
+
*write_idx_ptr = idx;
|
| 1838 |
+
}
|
| 1839 |
+
}
|
| 1840 |
+
|
| 1841 |
+
// Random bag at DDGame+0x3934 (entries 0, 1)
|
| 1842 |
+
{
|
| 1843 |
+
let write_idx_ptr = ddgame_raw.add(0x3AC4) as *mut i32;
|
| 1844 |
+
let mut idx = *write_idx_ptr;
|
| 1845 |
+
if idx + 1 < 0x65 {
|
| 1846 |
+
*(ddgame_raw.add(0x3934) as *mut u32).add(idx as usize) = 0;
|
| 1847 |
+
idx += 1;
|
| 1848 |
+
*write_idx_ptr = idx;
|
| 1849 |
+
}
|
| 1850 |
+
if idx + 1 < 0x65 {
|
| 1851 |
+
*(ddgame_raw.add(0x3934) as *mut u32).add(idx as usize) = 1;
|
| 1852 |
+
*write_idx_ptr = idx + 1;
|
| 1853 |
+
}
|
| 1854 |
+
}
|
| 1855 |
+
|
| 1856 |
+
// Random bag at DDGame+0x42AC (entries 0..3)
|
| 1857 |
+
{
|
| 1858 |
+
let write_idx_ptr = ddgame_raw.add(0x443C) as *mut i32;
|
| 1859 |
+
for val in 0..4i32 {
|
| 1860 |
+
let idx = *write_idx_ptr;
|
| 1861 |
+
if idx + 1 < 0x65 {
|
| 1862 |
+
*(ddgame_raw.add(0x42AC) as *mut i32).add(idx as usize) = val;
|
| 1863 |
+
*write_idx_ptr = idx + 1;
|
| 1864 |
+
}
|
| 1865 |
+
}
|
| 1866 |
+
}
|
| 1867 |
+
}
|
| 1868 |
+
|
| 1869 |
+
/// Weapon availability loop using already-ported check_weapon_avail.
|
| 1870 |
+
unsafe fn init_game_state_weapon_avail(
|
| 1871 |
+
_wrapper: *mut DDGameWrapper,
|
| 1872 |
+
ddgame_raw: *mut u8,
|
| 1873 |
+
game_info_sub: *const u8,
|
| 1874 |
+
) {
|
| 1875 |
+
let game_version = *(game_info_sub.add(0xD778) as *const i32);
|
| 1876 |
+
|
| 1877 |
+
for weapon_id in 1..0x47i32 {
|
| 1878 |
+
let avail = check_weapon_avail(ddgame_raw as *mut DDGame, weapon_id as u32);
|
| 1879 |
+
let is_avail = if game_version < 0x4D {
|
| 1880 |
+
avail != 0
|
| 1881 |
+
} else {
|
| 1882 |
+
avail > 0
|
| 1883 |
+
};
|
| 1884 |
+
|
| 1885 |
+
if is_avail {
|
| 1886 |
+
let write_idx_ptr = ddgame_raw.add(0x3DEC) as *mut i32;
|
| 1887 |
+
let idx = *write_idx_ptr;
|
| 1888 |
+
if idx + 1 < 0x65 {
|
| 1889 |
+
*(ddgame_raw.add(0x3C5C) as *mut i32).add(idx as usize) = weapon_id;
|
| 1890 |
+
*write_idx_ptr = idx + 1;
|
| 1891 |
+
}
|
| 1892 |
+
}
|
| 1893 |
+
}
|
| 1894 |
+
}
|
| 1895 |
+
|
| 1896 |
+
/// Allocate team/object tracking arrays and initialize the 0x51C object.
|
| 1897 |
+
unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info_sub: *const u8) {
|
| 1898 |
+
use crate::wa_alloc::wa_malloc;
|
| 1899 |
+
|
| 1900 |
+
// Game speed
|
| 1901 |
+
*(ddgame_raw.add(0x72D8) as *mut u32) = 0x10000;
|
| 1902 |
+
|
| 1903 |
+
let is_headful = *(ddgame_raw.add(0x7EF8) as *const u32) != 0;
|
| 1904 |
+
if !is_headful {
|
| 1905 |
+
*(ddgame_raw.add(0x72DC) as *mut u32) = 0x1000_0000;
|
| 1906 |
+
} else {
|
| 1907 |
+
*(ddgame_raw.add(0x72DC) as *mut u32) = *(game_info_sub.add(0xD988) as *const u32);
|
| 1908 |
+
}
|
| 1909 |
+
|
| 1910 |
+
// Network speed callback
|
| 1911 |
+
if *(ddgame_raw.add(8) as *const u32) != 0 {
|
| 1912 |
+
let snd_ptr = *(ddgame_raw.add(8) as *const *const u8);
|
| 1913 |
+
let vtable = *(snd_ptr as *const *const u32);
|
| 1914 |
+
let vmethod: unsafe extern "thiscall" fn(*const u8, u32, u32) =
|
| 1915 |
+
core::mem::transmute(*vtable.add(2));
|
| 1916 |
+
vmethod(
|
| 1917 |
+
snd_ptr,
|
| 1918 |
+
*(ddgame_raw.add(0x72D8) as *const u32),
|
| 1919 |
+
*(ddgame_raw.add(0x72DC) as *const u32),
|
| 1920 |
+
);
|
| 1921 |
+
}
|
| 1922 |
+
|
| 1923 |
+
// Misc fields
|
| 1924 |
+
*(ddgame_raw.add(0x8140) as *mut u32) = 0;
|
| 1925 |
+
*(ddgame_raw.add(0x764C) as *mut u32) = *(game_info_sub.add(0xF361) as *const u8) as u32;
|
| 1926 |
+
*(ddgame_raw.add(0x7640) as *mut u32) = 0;
|
| 1927 |
+
*(ddgame_raw.add(0x7644) as *mut u32) = *(game_info_sub.add(0xF363) as *const u8) as u32;
|
| 1928 |
+
*(ddgame_raw.add(0x7648) as *mut u32) = *(game_info_sub.add(0xF364) as *const u8) as u32;
|
| 1929 |
+
|
| 1930 |
+
// Render state
|
| 1931 |
+
*(ddgame_raw.add(0x7390) as *mut u32) = 0;
|
| 1932 |
+
*(ddgame_raw.add(0x7394) as *mut u32) = 0x10000;
|
| 1933 |
+
*(ddgame_raw.add(0x7398) as *mut u32) = 0;
|
| 1934 |
+
|
| 1935 |
+
// Allocate team tracking arrays (DDGame+0x514, DDGame+0x518)
|
| 1936 |
+
{
|
| 1937 |
+
let count_a = *(game_info_sub.add(0xD990) as *const u32);
|
| 1938 |
+
let size = count_a.wrapping_mul(4);
|
| 1939 |
+
let arr = wa_malloc(size);
|
| 1940 |
+
*(ddgame_raw.add(0x514) as *mut *mut u8) = arr;
|
| 1941 |
+
for i in 0..count_a as usize {
|
| 1942 |
+
*(arr as *mut u32).add(i) = 0;
|
| 1943 |
+
}
|
| 1944 |
+
}
|
| 1945 |
+
{
|
| 1946 |
+
let count_b = *(game_info_sub.add(0xD994) as *const u32);
|
| 1947 |
+
let size = count_b.wrapping_mul(4);
|
| 1948 |
+
let arr = wa_malloc(size);
|
| 1949 |
+
*(ddgame_raw.add(0x518) as *mut *mut u8) = arr;
|
| 1950 |
+
for i in 0..count_b as usize {
|
| 1951 |
+
*(arr as *mut u32).add(i) = 0;
|
| 1952 |
+
}
|
| 1953 |
+
}
|
| 1954 |
+
|
| 1955 |
+
// Initialize the 0x51C object (vector-like structure)
|
| 1956 |
+
{
|
| 1957 |
+
let obj = *(ddgame_raw.add(0x51C) as *const *mut u8);
|
| 1958 |
+
if !obj.is_null() {
|
| 1959 |
+
*(obj as *mut u32) = 0; // first field = 0
|
| 1960 |
+
// The vector operations (FUN_005370c0, etc.) are complex.
|
| 1961 |
+
// Bridge to original via setting fields directly.
|
| 1962 |
+
let obj32 = obj as *mut u32;
|
| 1963 |
+
// Reset the internal vector state
|
| 1964 |
+
*(obj32.add(5)) = 0; // +0x14
|
| 1965 |
+
|
| 1966 |
+
// Reset the second vector (at +0x1C..+0x24)
|
| 1967 |
+
let start = *(obj32.add(7)) as *mut u32; // +0x1C
|
| 1968 |
+
let end = *(obj32.add(8)) as *mut u32; // +0x20
|
| 1969 |
+
if start != end {
|
| 1970 |
+
// Move elements: dest = start + (capacity - end offset)
|
| 1971 |
+
// This is a std::vector erase-to-end operation.
|
| 1972 |
+
// For init, just set write pointer = start
|
| 1973 |
+
*(obj32.add(8)) = *(obj32.add(7)); // end = start
|
| 1974 |
+
}
|
| 1975 |
+
|
| 1976 |
+
*(obj32.add(10)) = 0xFFFF_FFFF; // +0x28 = -1
|
| 1977 |
+
}
|
| 1978 |
+
}
|
| 1979 |
+
}
|
|
@@ -10,7 +10,7 @@
|
|
| 10 |
//! - `DDGameWrapper__InitReplay` (0x56F860): usercall(EAX=game_info, ESI=this),
|
| 11 |
//! plain RET (no stack args). Bridged via `call_init_replay`.
|
| 12 |
//! - `DDGame__Constructor` (0x56E220): fully replaced by `create_ddgame()` in openwa-core.
|
| 13 |
-
//! - `DDGame__InitGameState` (0x526500):
|
| 14 |
|
| 15 |
use crate::hook;
|
| 16 |
use crate::log_line;
|
|
@@ -162,10 +162,8 @@ pub(crate) unsafe fn construct_ddgame_wrapper(
|
|
| 162 |
crate::debug_watchpoint::teardown();
|
| 163 |
}
|
| 164 |
|
| 165 |
-
// Initialize DDGame's game-state fields.
|
| 166 |
-
|
| 167 |
-
core::mem::transmute(rb(va::DDGAME_INIT_GAME_STATE) as usize);
|
| 168 |
-
init_state(this);
|
| 169 |
|
| 170 |
let _ = log_line(&format!(
|
| 171 |
"[GameSession] DDGameWrapper::Constructor done: wrapper=0x{:08X} ddgame=0x{:08X}",
|
|
@@ -199,6 +197,7 @@ pub fn install() -> Result<(), String> {
|
|
| 199 |
DDGAME_CTOR_ADDR = rb(0x56E220);
|
| 200 |
init_constructor_addrs();
|
| 201 |
hook::install_trap!("DDGameWrapper__Constructor", va::CONSTRUCT_DD_GAME_WRAPPER);
|
|
|
|
| 202 |
}
|
| 203 |
Ok(())
|
| 204 |
}
|
|
|
|
| 10 |
//! - `DDGameWrapper__InitReplay` (0x56F860): usercall(EAX=game_info, ESI=this),
|
| 11 |
//! plain RET (no stack args). Bridged via `call_init_replay`.
|
| 12 |
//! - `DDGame__Constructor` (0x56E220): fully replaced by `create_ddgame()` in openwa-core.
|
| 13 |
+
//! - `DDGame__InitGameState` (0x526500): ported to Rust in `init_game_state()`.
|
| 14 |
|
| 15 |
use crate::hook;
|
| 16 |
use crate::log_line;
|
|
|
|
| 162 |
crate::debug_watchpoint::teardown();
|
| 163 |
}
|
| 164 |
|
| 165 |
+
// Initialize DDGame's game-state fields (Rust port).
|
| 166 |
+
openwa_core::engine::game_state_init::init_game_state(this);
|
|
|
|
|
|
|
| 167 |
|
| 168 |
let _ = log_line(&format!(
|
| 169 |
"[GameSession] DDGameWrapper::Constructor done: wrapper=0x{:08X} ddgame=0x{:08X}",
|
|
|
|
| 197 |
DDGAME_CTOR_ADDR = rb(0x56E220);
|
| 198 |
init_constructor_addrs();
|
| 199 |
hook::install_trap!("DDGameWrapper__Constructor", va::CONSTRUCT_DD_GAME_WRAPPER);
|
| 200 |
+
hook::install_trap!("DDGame__InitGameState", va::DDGAME_INIT_GAME_STATE);
|
| 201 |
}
|
| 202 |
Ok(())
|
| 203 |
}
|
|
@@ -1,8 +1,9 @@
|
|
| 1 |
//! Passthrough hooks for DDGame__InitGameState sub-functions.
|
| 2 |
//!
|
| 3 |
-
//! These
|
| 4 |
-
//!
|
| 5 |
-
//!
|
|
|
|
| 6 |
|
| 7 |
use crate::hook;
|
| 8 |
use crate::log_line;
|
|
@@ -14,14 +15,7 @@ static mut HUD_PANEL_ORIG: *const () = core::ptr::null();
|
|
| 14 |
static mut INIT_WEAPON_TABLE_ORIG: *const () = core::ptr::null();
|
| 15 |
static mut INIT_TEAMS_ORIG: *const () = core::ptr::null();
|
| 16 |
static mut TEAM_MANAGER_ORIG: *const () = core::ptr::null();
|
| 17 |
-
static mut TURN_GAME_ORIG: *const () = core::ptr::null();
|
| 18 |
static mut GAME_STATE_ORIG: *const () = core::ptr::null();
|
| 19 |
-
static mut DISPLAYGFX_FULL_ORIG: *const () = core::ptr::null();
|
| 20 |
-
static mut TEXTBOX_ORIG: *const () = core::ptr::null();
|
| 21 |
-
static mut WEAPON_PANEL_ORIG: *const () = core::ptr::null();
|
| 22 |
-
static mut BUFFER_OBJ_ORIG: *const () = core::ptr::null();
|
| 23 |
-
static mut STREAM_INIT_ORIG: *const () = core::ptr::null();
|
| 24 |
-
static mut DISPLAY_OBJ_ORIG: *const () = core::ptr::null();
|
| 25 |
|
| 26 |
// βββ Passthrough hooks ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 27 |
|
|
@@ -61,72 +55,17 @@ unsafe extern "stdcall" fn hook_team_manager(this: u32, wrapper: u32) -> u32 {
|
|
| 61 |
orig(this, wrapper)
|
| 62 |
}
|
| 63 |
|
| 64 |
-
//
|
| 65 |
-
// No logging β format! clobbers ECX which the post-call code may depend on
|
| 66 |
-
unsafe extern "stdcall" fn hook_turn_game(this: u32, setup_data: u32) -> u32 {
|
| 67 |
-
let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(TURN_GAME_ORIG);
|
| 68 |
-
orig(this, setup_data)
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
// CTaskGameState__Constructor (0x532330): stdcall(this, param), RET 0x8
|
| 72 |
-
// No logging β may clobber registers the caller depends on
|
| 73 |
unsafe extern "stdcall" fn hook_game_state(this: u32, param: u32) -> u32 {
|
| 74 |
let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(GAME_STATE_ORIG);
|
| 75 |
orig(this, param)
|
| 76 |
}
|
| 77 |
|
| 78 |
-
// βββ Group F: Display objects ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 79 |
-
|
| 80 |
-
// DisplayGfx__ConstructFull (0x563FC0): stdcall(5 params), RET 0x14
|
| 81 |
-
unsafe extern "stdcall" fn hook_displaygfx_full(
|
| 82 |
-
p1: u32,
|
| 83 |
-
p2: u32,
|
| 84 |
-
p3: u32,
|
| 85 |
-
p4: u32,
|
| 86 |
-
p5: u32,
|
| 87 |
-
) -> u32 {
|
| 88 |
-
let orig: unsafe extern "stdcall" fn(u32, u32, u32, u32, u32) -> u32 =
|
| 89 |
-
core::mem::transmute(DISPLAYGFX_FULL_ORIG);
|
| 90 |
-
orig(p1, p2, p3, p4, p5)
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
// DisplayGfx__ConstructTextbox (0x4FAF00): stdcall(3 params), RET 0xC
|
| 94 |
-
unsafe extern "stdcall" fn hook_textbox(p1: u32, p2: u32, p3: u32) -> u32 {
|
| 95 |
-
let orig: unsafe extern "stdcall" fn(u32, u32, u32) -> u32 = core::mem::transmute(TEXTBOX_ORIG);
|
| 96 |
-
orig(p1, p2, p3)
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
// DDGame__InitWeaponPanel (0x567770): stdcall(1 param = wrapper), RET 0x4
|
| 100 |
-
unsafe extern "stdcall" fn hook_weapon_panel(wrapper: u32) -> u32 {
|
| 101 |
-
let orig: unsafe extern "stdcall" fn(u32) -> u32 = core::mem::transmute(WEAPON_PANEL_ORIG);
|
| 102 |
-
orig(wrapper)
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
// βββ Group G: Buffer/stream objects βββββββββββββββββββββββββββββββββββββββββ
|
| 106 |
-
|
| 107 |
-
// BufferObject__Constructor (0x545FD0): stdcall(3 params), RET 0xC
|
| 108 |
-
unsafe extern "stdcall" fn hook_buffer_obj(p1: u32, p2: u32, p3: u32) -> u32 {
|
| 109 |
-
let orig: unsafe extern "stdcall" fn(u32, u32, u32) -> u32 =
|
| 110 |
-
core::mem::transmute(BUFFER_OBJ_ORIG);
|
| 111 |
-
orig(p1, p2, p3)
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
// GameStateStream__Init (0x4FB490): stdcall(1 param), RET 0x4
|
| 115 |
-
unsafe extern "stdcall" fn hook_stream_init(p1: u32) -> u32 {
|
| 116 |
-
let orig: unsafe extern "stdcall" fn(u32) -> u32 = core::mem::transmute(STREAM_INIT_ORIG);
|
| 117 |
-
orig(p1)
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
// DisplayObject__Constructor (0x540440): stdcall(2 params), RET 0x8
|
| 121 |
-
unsafe extern "stdcall" fn hook_display_obj(p1: u32, p2: u32) -> u32 {
|
| 122 |
-
let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(DISPLAY_OBJ_ORIG);
|
| 123 |
-
orig(p1, p2)
|
| 124 |
-
}
|
| 125 |
-
|
| 126 |
// βββ Hook installation ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 127 |
|
| 128 |
pub fn install() -> Result<(), String> {
|
| 129 |
unsafe {
|
|
|
|
| 130 |
HUD_PANEL_ORIG = hook::install(
|
| 131 |
"HudPanel__Constructor",
|
| 132 |
va::HUD_PANEL_CONSTRUCTOR,
|
|
@@ -151,54 +90,22 @@ pub fn install() -> Result<(), String> {
|
|
| 151 |
hook_team_manager as *const (),
|
| 152 |
)? as *const ();
|
| 153 |
|
| 154 |
-
TURN_GAME_ORIG = hook::install(
|
| 155 |
-
"CTaskTurnGame__Constructor",
|
| 156 |
-
va::TURN_GAME_CONSTRUCTOR,
|
| 157 |
-
hook_turn_game as *const (),
|
| 158 |
-
)? as *const ();
|
| 159 |
GAME_STATE_ORIG = hook::install(
|
| 160 |
"CTaskGameState__Constructor",
|
| 161 |
va::GAME_STATE_CONSTRUCTOR,
|
| 162 |
hook_game_state as *const (),
|
| 163 |
)? as *const ();
|
| 164 |
|
| 165 |
-
//
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
hook_textbox as *const (),
|
| 176 |
-
)? as *const ();
|
| 177 |
-
|
| 178 |
-
WEAPON_PANEL_ORIG = hook::install(
|
| 179 |
-
"DDGame__InitWeaponPanel",
|
| 180 |
-
va::FUN_567770,
|
| 181 |
-
hook_weapon_panel as *const (),
|
| 182 |
-
)? as *const ();
|
| 183 |
-
|
| 184 |
-
// Group G: Buffer/stream objects
|
| 185 |
-
BUFFER_OBJ_ORIG = hook::install(
|
| 186 |
-
"BufferObject__Constructor",
|
| 187 |
-
va::BUFFER_OBJECT_CONSTRUCTOR,
|
| 188 |
-
hook_buffer_obj as *const (),
|
| 189 |
-
)? as *const ();
|
| 190 |
-
|
| 191 |
-
STREAM_INIT_ORIG = hook::install(
|
| 192 |
-
"GameStateStream__Init",
|
| 193 |
-
va::GAME_STATE_STREAM_INIT,
|
| 194 |
-
hook_stream_init as *const (),
|
| 195 |
-
)? as *const ();
|
| 196 |
-
|
| 197 |
-
DISPLAY_OBJ_ORIG = hook::install(
|
| 198 |
-
"DisplayObject__Constructor",
|
| 199 |
-
va::DISPLAY_OBJECT_CONSTRUCTOR,
|
| 200 |
-
hook_display_obj as *const (),
|
| 201 |
-
)? as *const ();
|
| 202 |
}
|
| 203 |
|
| 204 |
Ok(())
|
|
|
|
| 1 |
//! Passthrough hooks for DDGame__InitGameState sub-functions.
|
| 2 |
//!
|
| 3 |
+
//! These sub-constructors are called from the Rust port of InitGameState
|
| 4 |
+
//! via transmute bridges. Passthrough hooks remain for logging. Functions
|
| 5 |
+
//! that use implicit register params (usercall) have no hooks installed
|
| 6 |
+
//! to avoid dropping ECX/EDX/ESI.
|
| 7 |
|
| 8 |
use crate::hook;
|
| 9 |
use crate::log_line;
|
|
|
|
| 15 |
static mut INIT_WEAPON_TABLE_ORIG: *const () = core::ptr::null();
|
| 16 |
static mut INIT_TEAMS_ORIG: *const () = core::ptr::null();
|
| 17 |
static mut TEAM_MANAGER_ORIG: *const () = core::ptr::null();
|
|
|
|
| 18 |
static mut GAME_STATE_ORIG: *const () = core::ptr::null();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
// βββ Passthrough hooks ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 21 |
|
|
|
|
| 55 |
orig(this, wrapper)
|
| 56 |
}
|
| 57 |
|
| 58 |
+
// CTaskGameState__Constructor / SerializeGameState (0x532330): stdcall(this, param), RET 0x8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
unsafe extern "stdcall" fn hook_game_state(this: u32, param: u32) -> u32 {
|
| 60 |
let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(GAME_STATE_ORIG);
|
| 61 |
orig(this, param)
|
| 62 |
}
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
// βββ Hook installation ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 65 |
|
| 66 |
pub fn install() -> Result<(), String> {
|
| 67 |
unsafe {
|
| 68 |
+
// Pure stdcall hooks β safe to intercept, log, and forward.
|
| 69 |
HUD_PANEL_ORIG = hook::install(
|
| 70 |
"HudPanel__Constructor",
|
| 71 |
va::HUD_PANEL_CONSTRUCTOR,
|
|
|
|
| 90 |
hook_team_manager as *const (),
|
| 91 |
)? as *const ();
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
GAME_STATE_ORIG = hook::install(
|
| 94 |
"CTaskGameState__Constructor",
|
| 95 |
va::GAME_STATE_CONSTRUCTOR,
|
| 96 |
hook_game_state as *const (),
|
| 97 |
)? as *const ();
|
| 98 |
|
| 99 |
+
// Usercall functions (ECX/EDX/ESI carry implicit params) are NOT hooked.
|
| 100 |
+
// Passthrough hooks would drop register values. These are called directly
|
| 101 |
+
// from the Rust InitGameState port via typed transmute/fastcall bridges:
|
| 102 |
+
// - CTaskTurnGame__Constructor (ECX=DDGame)
|
| 103 |
+
// - DisplayGfx__ConstructFull (fastcall: ECX+EDX)
|
| 104 |
+
// - DisplayGfx__ConstructTextbox (thiscall: ECX=display)
|
| 105 |
+
// - DisplayObject__Constructor (fastcall: ECX+EDX)
|
| 106 |
+
// - DDGame__InitWeaponPanel (usercall: ESI=this)
|
| 107 |
+
// - BufferObject__Constructor, GameStateStream__Init (pure stdcall,
|
| 108 |
+
// but hooks removed since only caller is Rust)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
}
|
| 110 |
|
| 111 |
Ok(())
|