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