refactor: extract subsystem creation into construct() methods
Browse filesMove transmute-heavy subsystem creation out of impl_init_hardware into
dedicated methods: DisplayGfx::construct(), GameStats::construct(),
DDNetGameWrapper::construct(), DDKeyboard::poll(), and a local
create_dssound() function for the 33-line COM init sequence.
impl_init_hardware now reads as a high-level orchestrator with only
the DDDisplay::Init retry loop and MapWindowPoints IAT call inline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/ddkeyboard.rs
CHANGED
|
@@ -43,6 +43,18 @@ pub struct DDKeyboard {
|
|
| 43 |
const _: () = assert!(core::mem::size_of::<DDKeyboard>() == 0x33C);
|
| 44 |
|
| 45 |
impl DDKeyboard {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
/// Create a new DDKeyboard with inline construction (no native C++ ctor).
|
| 47 |
///
|
| 48 |
/// All fields are zero-initialized, then known fields are set.
|
|
|
|
| 43 |
const _: () = assert!(core::mem::size_of::<DDKeyboard>() == 0x33C);
|
| 44 |
|
| 45 |
impl DDKeyboard {
|
| 46 |
+
/// Poll keyboard state via WA's PollKeyboardState (0x572290).
|
| 47 |
+
///
|
| 48 |
+
/// # Safety
|
| 49 |
+
/// Must be called from within the WA.exe process.
|
| 50 |
+
pub unsafe fn poll(&mut self) {
|
| 51 |
+
use crate::address::va;
|
| 52 |
+
use crate::rebase::rb;
|
| 53 |
+
let poll_fn: unsafe extern "stdcall" fn(*mut Self) =
|
| 54 |
+
core::mem::transmute(rb(va::DDKEYBOARD_POLL_KEYBOARD_STATE) as usize);
|
| 55 |
+
poll_fn(self);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
/// Create a new DDKeyboard with inline construction (no native C++ ctor).
|
| 59 |
///
|
| 60 |
/// All fields are zero-initialized, then known fields are set.
|
crates/openwa-core/src/ddnetgame_wrapper.rs
CHANGED
|
@@ -13,3 +13,18 @@ pub struct DDNetGameWrapper {
|
|
| 13 |
}
|
| 14 |
|
| 15 |
const _: () = assert!(core::mem::size_of::<DDNetGameWrapper>() == 0x2C);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
const _: () = assert!(core::mem::size_of::<DDNetGameWrapper>() == 0x2C);
|
| 16 |
+
|
| 17 |
+
impl DDNetGameWrapper {
|
| 18 |
+
/// Allocate and construct a DDNetGameWrapper via WA's native constructor.
|
| 19 |
+
///
|
| 20 |
+
/// # Safety
|
| 21 |
+
/// Must be called from within the WA.exe process.
|
| 22 |
+
pub unsafe fn construct() -> *mut Self {
|
| 23 |
+
use crate::address::va;
|
| 24 |
+
use crate::rebase::rb;
|
| 25 |
+
use crate::wa_alloc::WABox;
|
| 26 |
+
let ctor: unsafe extern "stdcall" fn(*mut Self) -> *mut Self =
|
| 27 |
+
core::mem::transmute(rb(va::DDNETGAME_WRAPPER_CTOR) as usize);
|
| 28 |
+
ctor(WABox::<Self>::alloc(0x2C, 0).leak())
|
| 29 |
+
}
|
| 30 |
+
}
|
crates/openwa-core/src/display_gfx.rs
CHANGED
|
@@ -14,3 +14,18 @@ pub struct DisplayGfx {
|
|
| 14 |
}
|
| 15 |
|
| 16 |
const _: () = assert!(core::mem::size_of::<DisplayGfx>() == 0x24E28);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
}
|
| 15 |
|
| 16 |
const _: () = assert!(core::mem::size_of::<DisplayGfx>() == 0x24E28);
|
| 17 |
+
|
| 18 |
+
impl DisplayGfx {
|
| 19 |
+
/// Allocate and construct a DisplayGfx via WA's native constructor.
|
| 20 |
+
///
|
| 21 |
+
/// # Safety
|
| 22 |
+
/// Must be called from within the WA.exe process.
|
| 23 |
+
pub unsafe fn construct() -> *mut Self {
|
| 24 |
+
use crate::address::va;
|
| 25 |
+
use crate::rebase::rb;
|
| 26 |
+
use crate::wa_alloc::WABox;
|
| 27 |
+
let ctor: unsafe extern "stdcall" fn(*mut Self) -> *mut Self =
|
| 28 |
+
core::mem::transmute(rb(va::DISPLAYGFX_CTOR) as usize);
|
| 29 |
+
ctor(WABox::<Self>::alloc(0x24E28, 0x24E08).leak())
|
| 30 |
+
}
|
| 31 |
+
}
|
crates/openwa-core/src/game_stats.rs
CHANGED
|
@@ -15,3 +15,22 @@ pub struct GameStats {
|
|
| 15 |
}
|
| 16 |
|
| 17 |
const _: () = assert!(core::mem::size_of::<GameStats>() == 0x3560);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
}
|
| 16 |
|
| 17 |
const _: () = assert!(core::mem::size_of::<GameStats>() == 0x3560);
|
| 18 |
+
|
| 19 |
+
impl GameStats {
|
| 20 |
+
/// Allocate and construct a GameStats via WA's native constructor,
|
| 21 |
+
/// then overlay the GameStats vtable (used in headless mode).
|
| 22 |
+
///
|
| 23 |
+
/// # Safety
|
| 24 |
+
/// Must be called from within the WA.exe process.
|
| 25 |
+
pub unsafe fn construct() -> *mut Self {
|
| 26 |
+
use crate::address::va;
|
| 27 |
+
use crate::rebase::rb;
|
| 28 |
+
use crate::wa_alloc::WABox;
|
| 29 |
+
let stats = WABox::<Self>::alloc(0x3560, 0x3560).leak();
|
| 30 |
+
let ctor: unsafe extern "stdcall" fn(*mut Self) -> *mut Self =
|
| 31 |
+
core::mem::transmute(rb(va::GAMESTATS_CTOR) as usize);
|
| 32 |
+
ctor(stats);
|
| 33 |
+
(*stats).vtable = rb(va::GAMESTATS_VTABLE) as *mut u8;
|
| 34 |
+
stats
|
| 35 |
+
}
|
| 36 |
+
}
|
crates/openwa-wormkit/src/replacements/hardware_init.rs
CHANGED
|
@@ -247,6 +247,51 @@ unsafe extern "stdcall" fn call_ddisplay_init(
|
|
| 247 |
);
|
| 248 |
}
|
| 249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
// βββ Implementation βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 251 |
|
| 252 |
unsafe extern "cdecl" fn impl_init_hardware(
|
|
@@ -289,9 +334,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 289 |
|
| 290 |
if !headless {
|
| 291 |
// ββ DisplayGfx βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 292 |
-
let
|
| 293 |
-
core::mem::transmute(rb(va::DISPLAYGFX_CTOR) as usize);
|
| 294 |
-
let display_gfx = displaygfx_ctor(WABox::<DisplayGfx>::alloc(0x24E28, 0x24E08).leak());
|
| 295 |
(*session).display_gfx = display_gfx as *mut u8;
|
| 296 |
|
| 297 |
// ββ DDDisplay::Init retry loop ββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -371,42 +414,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 371 |
(*session).palette = pal;
|
| 372 |
|
| 373 |
// ββ DSSound βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 374 |
-
(*session).sound =
|
| 375 |
-
{
|
| 376 |
-
let snd = WABox::<DSSound>::alloc(0xBE0, 0xBC0).leak();
|
| 377 |
-
call_dssound_ctor(snd);
|
| 378 |
-
(*snd).hwnd = hwnd;
|
| 379 |
-
(*session).sound = snd;
|
| 380 |
-
|
| 381 |
-
let ds_create: unsafe extern "stdcall" fn(*const u8, *mut *mut u8, *const u8) -> i32 =
|
| 382 |
-
core::mem::transmute(rb(va::DIRECTSOUND_CREATE) as usize);
|
| 383 |
-
let hr = ds_create(
|
| 384 |
-
core::ptr::null(),
|
| 385 |
-
&mut (*snd).direct_sound,
|
| 386 |
-
core::ptr::null(),
|
| 387 |
-
);
|
| 388 |
-
|
| 389 |
-
if hr == 0 {
|
| 390 |
-
DSSOUND_INIT_EAX = snd as u32;
|
| 391 |
-
let hr2 = call_dssound_init_buffers(
|
| 392 |
-
&mut (*snd).primary_buffer,
|
| 393 |
-
&mut (*snd).primary_buffer_caps,
|
| 394 |
-
);
|
| 395 |
-
if hr2 == 0 {
|
| 396 |
-
// IDirectSoundBuffer::Play(this, 0, 0, DSBPLAY_LOOPING=1)
|
| 397 |
-
const PLAY_VSLOT: usize = 0x30 / 4; // IDirectSoundBuffer vtable slot 12
|
| 398 |
-
let pds = (*snd).primary_buffer as *const *const usize;
|
| 399 |
-
let vtbl = *pds;
|
| 400 |
-
let play: unsafe extern "stdcall" fn(
|
| 401 |
-
*const *const usize, u32, u32, u32,
|
| 402 |
-
) -> i32 = core::mem::transmute(*vtbl.add(PLAY_VSLOT));
|
| 403 |
-
let hr3 = play(pds, 0, 0, 1);
|
| 404 |
-
if hr3 == 0 {
|
| 405 |
-
(*snd).init_success = 1;
|
| 406 |
-
}
|
| 407 |
-
}
|
| 408 |
-
}
|
| 409 |
-
}
|
| 410 |
|
| 411 |
// ββ Streaming audio βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 412 |
(*session).streaming_audio = core::ptr::null_mut();
|
|
@@ -422,12 +430,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 422 |
}
|
| 423 |
} else {
|
| 424 |
// ββ Headless / stats mode βββββββββββββββββββββββββββββββββββββββββββββ
|
| 425 |
-
|
| 426 |
-
let gamestats_ctor: unsafe extern "stdcall" fn(*mut GameStats) -> *mut GameStats =
|
| 427 |
-
core::mem::transmute(rb(va::GAMESTATS_CTOR) as usize);
|
| 428 |
-
gamestats_ctor(stats);
|
| 429 |
-
(*stats).vtable = rb(va::GAMESTATS_VTABLE) as *mut u8;
|
| 430 |
-
(*session).display_gfx = stats as *mut u8;
|
| 431 |
(*session).keyboard = core::ptr::null_mut();
|
| 432 |
(*session).sound = core::ptr::null_mut();
|
| 433 |
(*session).palette = core::ptr::null_mut();
|
|
@@ -462,16 +465,12 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 462 |
|
| 463 |
let kb = (*session).keyboard;
|
| 464 |
if !kb.is_null() {
|
| 465 |
-
|
| 466 |
-
core::mem::transmute(rb(va::DDKEYBOARD_POLL_KEYBOARD_STATE) as usize);
|
| 467 |
-
kb_poll(kb);
|
| 468 |
}
|
| 469 |
}
|
| 470 |
|
| 471 |
// ββ DDNetGameWrapper (ALWAYS) βββββββββββββββββββββββββββββββββββββββββββββ
|
| 472 |
-
|
| 473 |
-
core::mem::transmute(rb(va::DDNETGAME_WRAPPER_CTOR) as usize);
|
| 474 |
-
(*session).net_game = net_ctor(WABox::<DDNetGameWrapper>::alloc(0x2C, 0).leak()) as *mut u8;
|
| 475 |
|
| 476 |
let _ = log_line("[hardware_init] GameEngine::InitHardware done");
|
| 477 |
1
|
|
|
|
| 247 |
);
|
| 248 |
}
|
| 249 |
|
| 250 |
+
// βββ Subsystem creation βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 251 |
+
|
| 252 |
+
/// Allocate and initialize a DSSound object with DirectSound COM setup.
|
| 253 |
+
///
|
| 254 |
+
/// Creates the DSSound, calls DirectSoundCreate, initializes primary buffer,
|
| 255 |
+
/// and starts looping playback. Sets `init_success` if all COM steps succeed.
|
| 256 |
+
/// The returned pointer is always valid (sound may be partially initialized
|
| 257 |
+
/// if COM steps fail, matching original WA behavior).
|
| 258 |
+
unsafe fn create_dssound(hwnd: u32) -> *mut DSSound {
|
| 259 |
+
let snd = WABox::<DSSound>::alloc(0xBE0, 0xBC0).leak();
|
| 260 |
+
call_dssound_ctor(snd);
|
| 261 |
+
(*snd).hwnd = hwnd;
|
| 262 |
+
|
| 263 |
+
let ds_create: unsafe extern "stdcall" fn(*const u8, *mut *mut u8, *const u8) -> i32 =
|
| 264 |
+
core::mem::transmute(rb(va::DIRECTSOUND_CREATE) as usize);
|
| 265 |
+
let hr = ds_create(
|
| 266 |
+
core::ptr::null(),
|
| 267 |
+
&mut (*snd).direct_sound,
|
| 268 |
+
core::ptr::null(),
|
| 269 |
+
);
|
| 270 |
+
|
| 271 |
+
if hr == 0 {
|
| 272 |
+
DSSOUND_INIT_EAX = snd as u32;
|
| 273 |
+
let hr2 = call_dssound_init_buffers(
|
| 274 |
+
&mut (*snd).primary_buffer,
|
| 275 |
+
&mut (*snd).primary_buffer_caps,
|
| 276 |
+
);
|
| 277 |
+
if hr2 == 0 {
|
| 278 |
+
// IDirectSoundBuffer::Play(this, 0, 0, DSBPLAY_LOOPING=1)
|
| 279 |
+
const PLAY_VSLOT: usize = 0x30 / 4; // IDirectSoundBuffer vtable slot 12
|
| 280 |
+
let pds = (*snd).primary_buffer as *const *const usize;
|
| 281 |
+
let vtbl = *pds;
|
| 282 |
+
let play: unsafe extern "stdcall" fn(
|
| 283 |
+
*const *const usize, u32, u32, u32,
|
| 284 |
+
) -> i32 = core::mem::transmute(*vtbl.add(PLAY_VSLOT));
|
| 285 |
+
let hr3 = play(pds, 0, 0, 1);
|
| 286 |
+
if hr3 == 0 {
|
| 287 |
+
(*snd).init_success = 1;
|
| 288 |
+
}
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
snd
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
// βββ Implementation βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 296 |
|
| 297 |
unsafe extern "cdecl" fn impl_init_hardware(
|
|
|
|
| 334 |
|
| 335 |
if !headless {
|
| 336 |
// ββ DisplayGfx βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 337 |
+
let display_gfx = DisplayGfx::construct();
|
|
|
|
|
|
|
| 338 |
(*session).display_gfx = display_gfx as *mut u8;
|
| 339 |
|
| 340 |
// ββ DDDisplay::Init retry loop ββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 414 |
(*session).palette = pal;
|
| 415 |
|
| 416 |
// ββ DSSound βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 417 |
+
(*session).sound = create_dssound(hwnd);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
| 419 |
// ββ Streaming audio βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 420 |
(*session).streaming_audio = core::ptr::null_mut();
|
|
|
|
| 430 |
}
|
| 431 |
} else {
|
| 432 |
// ββ Headless / stats mode βββββββββββββββββββββββββββββββββββββββββββββ
|
| 433 |
+
(*session).display_gfx = GameStats::construct() as *mut u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
(*session).keyboard = core::ptr::null_mut();
|
| 435 |
(*session).sound = core::ptr::null_mut();
|
| 436 |
(*session).palette = core::ptr::null_mut();
|
|
|
|
| 465 |
|
| 466 |
let kb = (*session).keyboard;
|
| 467 |
if !kb.is_null() {
|
| 468 |
+
(*kb).poll();
|
|
|
|
|
|
|
| 469 |
}
|
| 470 |
}
|
| 471 |
|
| 472 |
// ββ DDNetGameWrapper (ALWAYS) βββββββββββββββββββββββββββββββββββββββββββββ
|
| 473 |
+
(*session).net_game = DDNetGameWrapper::construct() as *mut u8;
|
|
|
|
|
|
|
| 474 |
|
| 475 |
let _ = log_line("[hardware_init] GameEngine::InitHardware done");
|
| 476 |
1
|