refactor: remove DDGameWrapperHandle, retype DDGame.game_state to GameInfo
Browse files- Delete DDGameWrapperHandle — consumers use *const DDGameWrapper directly
- Retype DDGame.game_state (*mut u8) → game_info (*mut GameInfo)
- Add GameInfo.crosshair_overflow_threshold at 0xD778 (was raw pointer math)
- Use SpeechSlotTable::clear() instead of write_bytes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- crates/openwa-core/src/address.rs +2 -2
- crates/openwa-core/src/ddgame.rs +5 -4
- crates/openwa-core/src/game_info.rs +8 -2
- crates/openwa-core/src/wa/ddgame.rs +0 -43
- crates/openwa-core/src/wa/mod.rs +0 -1
- crates/openwa-wormkit/src/replacements/render.rs +1 -2
- crates/openwa-wormkit/src/replacements/speech.rs +15 -13
- crates/openwa-wormkit/src/validation/mod.rs +1 -1
crates/openwa-core/src/address.rs
CHANGED
|
@@ -655,8 +655,8 @@ pub mod va {
|
|
| 655 |
}
|
| 656 |
|
| 657 |
// === GameInfo struct offsets ===
|
| 658 |
-
// These are offsets from the GameInfo pointer at DDGame+0x24
|
| 659 |
-
//
|
| 660 |
|
| 661 |
pub mod game_info_offsets {
|
| 662 |
// === Speech configuration ===
|
|
|
|
| 655 |
}
|
| 656 |
|
| 657 |
// === GameInfo struct offsets ===
|
| 658 |
+
// These are offsets from the GameInfo pointer at DDGame+0x24
|
| 659 |
+
// (DDGame.game_info). Access pattern: *(DDGame+0x24) + offset.
|
| 660 |
|
| 661 |
pub mod game_info_offsets {
|
| 662 |
// === Speech configuration ===
|
crates/openwa-core/src/ddgame.rs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
use crate::render::RenderQueue;
|
| 2 |
use crate::speech::SpeechSlotTable;
|
| 3 |
|
|
@@ -35,8 +36,8 @@ pub struct DDGame {
|
|
| 35 |
pub _caller: *mut u8,
|
| 36 |
/// 0x020: PCLandscape pointer (copied from DDGameWrapper[0x133])
|
| 37 |
pub landscape: *mut u8,
|
| 38 |
-
/// 0x024:
|
| 39 |
-
pub
|
| 40 |
/// 0x028: param_9
|
| 41 |
pub _param_028: *mut u8,
|
| 42 |
/// 0x02C: Secondary GfxDir object (0x70C bytes, conditional on GfxHandler 1)
|
|
@@ -119,7 +120,7 @@ pub struct DDGame {
|
|
| 119 |
/// 0x7D84-0x7EFF: Unknown
|
| 120 |
///
|
| 121 |
/// Known landmarks:
|
| 122 |
-
/// - 0x7EF8: flag from
|
| 123 |
/// - 0x7EFC: init 1
|
| 124 |
pub _unknown_7d84: [u8; 0x7F00 - 0x7D84],
|
| 125 |
|
|
@@ -162,7 +163,7 @@ const _: () = assert!(core::mem::size_of::<DDGame>() == 0x98B8);
|
|
| 162 |
pub mod offsets {
|
| 163 |
// === Header / init params (0x000-0x02C) ===
|
| 164 |
pub const LANDSCAPE: usize = 0x020;
|
| 165 |
-
pub const
|
| 166 |
pub const SECONDARY_GFXDIR: usize = 0x02C;
|
| 167 |
pub const GRADIENT_IMAGE: usize = 0x030;
|
| 168 |
|
|
|
|
| 1 |
+
use crate::game_info::GameInfo;
|
| 2 |
use crate::render::RenderQueue;
|
| 3 |
use crate::speech::SpeechSlotTable;
|
| 4 |
|
|
|
|
| 36 |
pub _caller: *mut u8,
|
| 37 |
/// 0x020: PCLandscape pointer (copied from DDGameWrapper[0x133])
|
| 38 |
pub landscape: *mut u8,
|
| 39 |
+
/// 0x024: GameInfo pointer (passed as param_10 to constructor).
|
| 40 |
+
pub game_info: *mut GameInfo,
|
| 41 |
/// 0x028: param_9
|
| 42 |
pub _param_028: *mut u8,
|
| 43 |
/// 0x02C: Secondary GfxDir object (0x70C bytes, conditional on GfxHandler 1)
|
|
|
|
| 120 |
/// 0x7D84-0x7EFF: Unknown
|
| 121 |
///
|
| 122 |
/// Known landmarks:
|
| 123 |
+
/// - 0x7EF8: flag from game_info+0xF914
|
| 124 |
/// - 0x7EFC: init 1
|
| 125 |
pub _unknown_7d84: [u8; 0x7F00 - 0x7D84],
|
| 126 |
|
|
|
|
| 163 |
pub mod offsets {
|
| 164 |
// === Header / init params (0x000-0x02C) ===
|
| 165 |
pub const LANDSCAPE: usize = 0x020;
|
| 166 |
+
pub const GAME_INFO: usize = 0x024;
|
| 167 |
pub const SECONDARY_GFXDIR: usize = 0x02C;
|
| 168 |
pub const GRADIENT_IMAGE: usize = 0x030;
|
| 169 |
|
crates/openwa-core/src/game_info.rs
CHANGED
|
@@ -17,8 +17,14 @@ pub struct GameInfo {
|
|
| 17 |
/// Used by DSSound_LoadAllSpeechBanks to iterate teams.
|
| 18 |
pub speech_team_count: u8,
|
| 19 |
|
| 20 |
-
/// 0x044D-
|
| 21 |
-
pub _unknown_044d: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
// --- Cluster 1: data paths ---
|
| 24 |
|
|
|
|
| 17 |
/// Used by DSSound_LoadAllSpeechBanks to iterate teams.
|
| 18 |
pub speech_team_count: u8,
|
| 19 |
|
| 20 |
+
/// 0x044D-0xD777: Unknown
|
| 21 |
+
pub _unknown_044d: [u8; 0xD778 - 0x44D],
|
| 22 |
+
|
| 23 |
+
/// 0xD778: Crosshair overflow clamp threshold (compared to 0x11E in render).
|
| 24 |
+
pub crosshair_overflow_threshold: i32,
|
| 25 |
+
|
| 26 |
+
/// 0xD77C-0xDAE7: Unknown
|
| 27 |
+
pub _unknown_d77c: [u8; 0xDAE8 - 0xD77C],
|
| 28 |
|
| 29 |
// --- Cluster 1: data paths ---
|
| 30 |
|
crates/openwa-core/src/wa/ddgame.rs
DELETED
|
@@ -1,43 +0,0 @@
|
|
| 1 |
-
//! Typed handle for DDGameWrapper — the main game engine wrapper object.
|
| 2 |
-
|
| 3 |
-
use crate::ddgame::DDGame;
|
| 4 |
-
use crate::game_info::GameInfo;
|
| 5 |
-
|
| 6 |
-
/// Zero-cost handle to a DDGameWrapper instance (raw pointer as u32).
|
| 7 |
-
///
|
| 8 |
-
/// DDGameWrapper is the top-level game engine object (~28KB). It holds
|
| 9 |
-
/// the DDGame pointer, graphics handlers, landscape, and display state.
|
| 10 |
-
///
|
| 11 |
-
/// Pointer chain: DDGameWrapper → DDGame (+0x488) → GameInfo (+0x24).
|
| 12 |
-
#[derive(Clone, Copy)]
|
| 13 |
-
#[repr(transparent)]
|
| 14 |
-
pub struct DDGameWrapperHandle(pub u32);
|
| 15 |
-
|
| 16 |
-
impl DDGameWrapperHandle {
|
| 17 |
-
/// Wrap a raw DDGameWrapper pointer.
|
| 18 |
-
pub unsafe fn from_raw(ptr: u32) -> Self {
|
| 19 |
-
Self(ptr)
|
| 20 |
-
}
|
| 21 |
-
|
| 22 |
-
/// Read the DDGame pointer from DDGameWrapper+0x488.
|
| 23 |
-
pub unsafe fn ddgame(self) -> *mut DDGame {
|
| 24 |
-
*((self.0 + 0x488) as *const *mut DDGame)
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
/// Follow DDGameWrapper → DDGame(+0x488) → GameInfo(+0x24).
|
| 28 |
-
///
|
| 29 |
-
/// GameInfo holds team configuration, speech paths, replay state, etc.
|
| 30 |
-
/// Some fields are mapped in the struct; others require offset constants
|
| 31 |
-
/// from `game_info_offsets`.
|
| 32 |
-
pub unsafe fn game_info(self) -> *mut GameInfo {
|
| 33 |
-
let ddgame = self.ddgame() as u32;
|
| 34 |
-
*((ddgame + 0x24) as *const *mut GameInfo)
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
/// Pointer to the speech slot table (DDGame+0x77E4, 0x5A0 bytes).
|
| 38 |
-
///
|
| 39 |
-
/// Maps (team_index, speech_line_id) → DSSound buffer index.
|
| 40 |
-
pub unsafe fn speech_slot_table(self) -> *mut u8 {
|
| 41 |
-
(self.ddgame() as u32 + 0x77E4) as *mut u8
|
| 42 |
-
}
|
| 43 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crates/openwa-core/src/wa/mod.rs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
//! Typed wrappers for calling WA.exe and library functions.
|
| 2 |
|
| 3 |
-
pub mod ddgame;
|
| 4 |
pub mod frontend;
|
| 5 |
pub mod mfc;
|
| 6 |
pub mod registry;
|
|
|
|
| 1 |
//! Typed wrappers for calling WA.exe and library functions.
|
| 2 |
|
|
|
|
| 3 |
pub mod frontend;
|
| 4 |
pub mod mfc;
|
| 5 |
pub mod registry;
|
crates/openwa-wormkit/src/replacements/render.rs
CHANGED
|
@@ -529,8 +529,7 @@ unsafe extern "cdecl" fn draw_crosshair_line_impl(task_ptr: u32) {
|
|
| 529 |
let mut overflowed = false;
|
| 530 |
let mut clamp_factor = 0i32;
|
| 531 |
|
| 532 |
-
let
|
| 533 |
-
let threshold = *(game_state.add(0xD778) as *const i32);
|
| 534 |
|
| 535 |
if threshold > 0x11E {
|
| 536 |
// Check X overflow: sin > 0 but endpoint wrapped below start
|
|
|
|
| 529 |
let mut overflowed = false;
|
| 530 |
let mut clamp_factor = 0i32;
|
| 531 |
|
| 532 |
+
let threshold = (*ddgame.game_info).crosshair_overflow_threshold;
|
|
|
|
| 533 |
|
| 534 |
if threshold > 0x11E {
|
| 535 |
// Check X overflow: sin > 0 but endpoint wrapped below start
|
crates/openwa-wormkit/src/replacements/speech.rs
CHANGED
|
@@ -13,7 +13,7 @@ use std::sync::atomic::{AtomicU32, Ordering};
|
|
| 13 |
use heapless::CString;
|
| 14 |
|
| 15 |
use openwa_core::rebase::rb;
|
| 16 |
-
use openwa_core::
|
| 17 |
use openwa_core::address::va::{self, game_info_offsets};
|
| 18 |
use openwa_core::speech::SpeechLineTableEntry;
|
| 19 |
|
|
@@ -314,7 +314,7 @@ usercall_trampoline!(fn trampoline_load_speech_bank; impl_fn = load_speech_bank_
|
|
| 314 |
reg = eax; stack_params = 3; ret_bytes = "0xC");
|
| 315 |
|
| 316 |
unsafe extern "cdecl" fn load_speech_bank_impl(
|
| 317 |
-
ddgw:
|
| 318 |
team_index: u32,
|
| 319 |
speech_base_path: *const u8,
|
| 320 |
speech_dir: *const u8,
|
|
@@ -327,7 +327,7 @@ unsafe extern "cdecl" fn load_speech_bank_impl(
|
|
| 327 |
.unwrap_or("?");
|
| 328 |
let _ = log_line(&format!(
|
| 329 |
"[Speech] LoadSpeechBank: team={} path=\"{}\" dir=\"{}\" ddgw=0x{:08X}",
|
| 330 |
-
team_index, path_str, dir_str, ddgw
|
| 331 |
));
|
| 332 |
|
| 333 |
let table_base = rb(va::SPEECH_LINE_TABLE) as *const SpeechLineTableEntry;
|
|
@@ -357,7 +357,7 @@ unsafe extern "cdecl" fn load_speech_bank_impl(
|
|
| 357 |
let _ = full_path.extend_from_bytes(wav_path.as_bytes());
|
| 358 |
|
| 359 |
let result = call_load_speech_wav(
|
| 360 |
-
ddgw
|
| 361 |
team_index,
|
| 362 |
entry.id,
|
| 363 |
wav_path.as_ptr(),
|
|
@@ -368,7 +368,7 @@ unsafe extern "cdecl" fn load_speech_bank_impl(
|
|
| 368 |
// Failed — try fallback with default speech dir if no more alternates
|
| 369 |
let next_entry = &*table_base.add(i + 1);
|
| 370 |
if next_entry.name_ptr.is_null() || next_entry.id != entry.id {
|
| 371 |
-
let game_info = ddgw.
|
| 372 |
let default_dir =
|
| 373 |
game_info.add(game_info_offsets::DEFAULT_SPEECH_DIR as usize);
|
| 374 |
let default_base =
|
|
@@ -386,7 +386,7 @@ unsafe extern "cdecl" fn load_speech_bank_impl(
|
|
| 386 |
let _ = full_path2.extend_from_bytes(wav_path2.as_bytes());
|
| 387 |
|
| 388 |
call_load_speech_wav(
|
| 389 |
-
ddgw
|
| 390 |
team_index,
|
| 391 |
entry.id,
|
| 392 |
wav_path2.as_ptr(),
|
|
@@ -417,21 +417,23 @@ unsafe extern "cdecl" fn load_speech_bank_impl(
|
|
| 417 |
usercall_trampoline!(fn trampoline_load_all_speech_banks;
|
| 418 |
impl_fn = load_all_speech_banks_impl; reg = esi);
|
| 419 |
|
| 420 |
-
unsafe extern "cdecl" fn load_all_speech_banks_impl(ddgw:
|
| 421 |
-
|
| 422 |
-
|
|
|
|
|
|
|
| 423 |
|
| 424 |
// Read team count from GameInfo
|
| 425 |
-
let game_info =
|
| 426 |
-
let team_count =
|
| 427 |
|
| 428 |
let _ = log_line(&format!(
|
| 429 |
"[Speech] LoadAllSpeechBanks: ddgw=0x{:08X} teams={}",
|
| 430 |
-
ddgw
|
| 431 |
));
|
| 432 |
|
| 433 |
// Load speech bank for each team
|
| 434 |
-
let gi = game_info as *const u8;
|
| 435 |
for i in 0..team_count {
|
| 436 |
let team_offset = (i * game_info_offsets::SPEECH_TEAM_STRIDE) as usize;
|
| 437 |
let base_path =
|
|
|
|
| 13 |
use heapless::CString;
|
| 14 |
|
| 15 |
use openwa_core::rebase::rb;
|
| 16 |
+
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 17 |
use openwa_core::address::va::{self, game_info_offsets};
|
| 18 |
use openwa_core::speech::SpeechLineTableEntry;
|
| 19 |
|
|
|
|
| 314 |
reg = eax; stack_params = 3; ret_bytes = "0xC");
|
| 315 |
|
| 316 |
unsafe extern "cdecl" fn load_speech_bank_impl(
|
| 317 |
+
ddgw: *const DDGameWrapper,
|
| 318 |
team_index: u32,
|
| 319 |
speech_base_path: *const u8,
|
| 320 |
speech_dir: *const u8,
|
|
|
|
| 327 |
.unwrap_or("?");
|
| 328 |
let _ = log_line(&format!(
|
| 329 |
"[Speech] LoadSpeechBank: team={} path=\"{}\" dir=\"{}\" ddgw=0x{:08X}",
|
| 330 |
+
team_index, path_str, dir_str, ddgw as u32
|
| 331 |
));
|
| 332 |
|
| 333 |
let table_base = rb(va::SPEECH_LINE_TABLE) as *const SpeechLineTableEntry;
|
|
|
|
| 357 |
let _ = full_path.extend_from_bytes(wav_path.as_bytes());
|
| 358 |
|
| 359 |
let result = call_load_speech_wav(
|
| 360 |
+
ddgw as u32,
|
| 361 |
team_index,
|
| 362 |
entry.id,
|
| 363 |
wav_path.as_ptr(),
|
|
|
|
| 368 |
// Failed — try fallback with default speech dir if no more alternates
|
| 369 |
let next_entry = &*table_base.add(i + 1);
|
| 370 |
if next_entry.name_ptr.is_null() || next_entry.id != entry.id {
|
| 371 |
+
let game_info = (*(*ddgw).ddgame).game_info as *const u8;
|
| 372 |
let default_dir =
|
| 373 |
game_info.add(game_info_offsets::DEFAULT_SPEECH_DIR as usize);
|
| 374 |
let default_base =
|
|
|
|
| 386 |
let _ = full_path2.extend_from_bytes(wav_path2.as_bytes());
|
| 387 |
|
| 388 |
call_load_speech_wav(
|
| 389 |
+
ddgw as u32,
|
| 390 |
team_index,
|
| 391 |
entry.id,
|
| 392 |
wav_path2.as_ptr(),
|
|
|
|
| 417 |
usercall_trampoline!(fn trampoline_load_all_speech_banks;
|
| 418 |
impl_fn = load_all_speech_banks_impl; reg = esi);
|
| 419 |
|
| 420 |
+
unsafe extern "cdecl" fn load_all_speech_banks_impl(ddgw: *const DDGameWrapper) {
|
| 421 |
+
let ddgame = &mut *(*ddgw).ddgame;
|
| 422 |
+
|
| 423 |
+
// Clear speech slot table
|
| 424 |
+
ddgame.speech_slot_table.clear();
|
| 425 |
|
| 426 |
// Read team count from GameInfo
|
| 427 |
+
let game_info = &*ddgame.game_info;
|
| 428 |
+
let team_count = game_info.speech_team_count as u32;
|
| 429 |
|
| 430 |
let _ = log_line(&format!(
|
| 431 |
"[Speech] LoadAllSpeechBanks: ddgw=0x{:08X} teams={}",
|
| 432 |
+
ddgw as u32, team_count
|
| 433 |
));
|
| 434 |
|
| 435 |
// Load speech bank for each team
|
| 436 |
+
let gi = ddgame.game_info as *const u8;
|
| 437 |
for i in 0..team_count {
|
| 438 |
let team_offset = (i * game_info_offsets::SPEECH_TEAM_STRIDE) as usize;
|
| 439 |
let base_path =
|
crates/openwa-wormkit/src/validation/mod.rs
CHANGED
|
@@ -281,7 +281,7 @@ fn validate_struct_offsets(result: &mut ValidationResult) {
|
|
| 281 |
let _ = log_validation("");
|
| 282 |
let _ = log_validation(" DDGame:");
|
| 283 |
check_offset!(result, DDGame, landscape, 0x20);
|
| 284 |
-
check_offset!(result, DDGame,
|
| 285 |
check_offset!(result, DDGame, arrow_sprites, 0x38);
|
| 286 |
check_offset!(result, DDGame, arrow_gfxdirs, 0xB8);
|
| 287 |
check_offset!(result, DDGame, display_gfx, 0x138);
|
|
|
|
| 281 |
let _ = log_validation("");
|
| 282 |
let _ = log_validation(" DDGame:");
|
| 283 |
check_offset!(result, DDGame, landscape, 0x20);
|
| 284 |
+
check_offset!(result, DDGame, game_info, 0x24);
|
| 285 |
check_offset!(result, DDGame, arrow_sprites, 0x38);
|
| 286 |
check_offset!(result, DDGame, arrow_gfxdirs, 0xB8);
|
| 287 |
check_offset!(result, DDGame, display_gfx, 0x138);
|