paavohtl Claude Opus 4.6 (1M context) commited on
Commit
d439705
Β·
1 Parent(s): 56e9d3b

feat: port DDGame__InitGameState (0x526500) to Rust

Browse files

Full 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>

crates/openwa-core/src/address.rs CHANGED
@@ -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
- fn/Stdcall FUN_567770 = 0x0056_7770;
 
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
crates/openwa-core/src/engine/ddgame_wrapper.rs CHANGED
@@ -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-0x267: Unknown fields
31
- pub _unknown_004: [u8; 0x264],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- /// 0x274-0x487: Unknown fields
39
- pub _unknown_274: [u8; 0x214],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-0x4BF: Unknown
45
- pub _unknown_490: [u8; 0x30],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.
crates/openwa-core/src/engine/game_state_init.rs CHANGED
@@ -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
+ }
crates/openwa-dll/src/replacements/game_session.rs CHANGED
@@ -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): plain stdcall(this), called via transmute.
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
- let init_state: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
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
  }
crates/openwa-dll/src/replacements/game_state_hooks.rs CHANGED
@@ -1,8 +1,9 @@
1
  //! Passthrough hooks for DDGame__InitGameState sub-functions.
2
  //!
3
- //! These hooks intercept stdcall constructors called during InitGameState,
4
- //! log their parameters, call the original, and return. This validates
5
- //! calling conventions and prepares for future Rust replacements.
 
6
 
7
  use crate::hook;
8
  use crate::log_line;
@@ -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
- // CTaskTurnGame__Constructor (0x55B280): stdcall(this, setup_data), RET 0x8
65
- // No logging β€” format! clobbers ECX which the post-call code may depend on
66
- unsafe extern "stdcall" fn hook_turn_game(this: u32, setup_data: u32) -> u32 {
67
- let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(TURN_GAME_ORIG);
68
- orig(this, setup_data)
69
- }
70
-
71
- // CTaskGameState__Constructor (0x532330): stdcall(this, param), RET 0x8
72
- // No logging β€” may clobber registers the caller depends on
73
  unsafe extern "stdcall" fn hook_game_state(this: u32, param: u32) -> u32 {
74
  let orig: unsafe extern "stdcall" fn(u32, u32) -> u32 = core::mem::transmute(GAME_STATE_ORIG);
75
  orig(this, param)
76
  }
77
 
78
- // ─── Group F: Display objects ────────────────────────────────────────────────
79
-
80
- // DisplayGfx__ConstructFull (0x563FC0): stdcall(5 params), RET 0x14
81
- unsafe extern "stdcall" fn hook_displaygfx_full(
82
- p1: u32,
83
- p2: u32,
84
- p3: u32,
85
- p4: u32,
86
- p5: u32,
87
- ) -> u32 {
88
- let orig: unsafe extern "stdcall" fn(u32, u32, u32, u32, u32) -> u32 =
89
- core::mem::transmute(DISPLAYGFX_FULL_ORIG);
90
- orig(p1, p2, p3, p4, p5)
91
- }
92
-
93
- // 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
- // Group F: Display objects
166
- DISPLAYGFX_FULL_ORIG = hook::install(
167
- "DisplayGfx__ConstructFull",
168
- va::DISPLAYGFX_CONSTRUCT_FULL,
169
- hook_displaygfx_full as *const (),
170
- )? as *const ();
171
-
172
- TEXTBOX_ORIG = hook::install(
173
- "DisplayGfx__ConstructTextbox",
174
- va::CONSTRUCT_TEXTBOX,
175
- hook_textbox as *const (),
176
- )? as *const ();
177
-
178
- WEAPON_PANEL_ORIG = hook::install(
179
- "DDGame__InitWeaponPanel",
180
- va::FUN_567770,
181
- hook_weapon_panel as *const (),
182
- )? as *const ();
183
-
184
- // Group G: Buffer/stream objects
185
- BUFFER_OBJ_ORIG = hook::install(
186
- "BufferObject__Constructor",
187
- va::BUFFER_OBJECT_CONSTRUCTOR,
188
- hook_buffer_obj as *const (),
189
- )? as *const ();
190
-
191
- STREAM_INIT_ORIG = hook::install(
192
- "GameStateStream__Init",
193
- va::GAME_STATE_STREAM_INIT,
194
- hook_stream_init as *const (),
195
- )? as *const ();
196
-
197
- DISPLAY_OBJ_ORIG = hook::install(
198
- "DisplayObject__Constructor",
199
- va::DISPLAY_OBJECT_CONSTRUCTOR,
200
- hook_display_obj as *const (),
201
- )? as *const ();
202
  }
203
 
204
  Ok(())
 
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(())