refactor: rename GameStats → DDDisplayBase, display_gfx → display
Browse filesGameStats is actually the base class of the display hierarchy —
DisplayGfx__Constructor calls it as its base ctor. Rename to
DDDisplayBase to reflect the inheritance relationship.
Rename GameSession.display_gfx → display since it holds a polymorphic
base class pointer (DisplayGfx in normal, DDDisplayBase in headless).
Update Ghidra: DDDisplayBase__Constructor, DDDisplayBase__Destructor,
DDDisplayBase__Vtable (0x6645F8), DDDisplayBase__HeadlessVtable (0x66A0F8).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/address.rs +6 -5
- crates/openwa-core/src/dd_display_base.rs +44 -0
- crates/openwa-core/src/display_gfx.rs +6 -2
- crates/openwa-core/src/game_session.rs +3 -3
- crates/openwa-core/src/game_stats.rs +0 -36
- crates/openwa-core/src/lib.rs +1 -1
- crates/openwa-wormkit/src/replacements/hardware_init.rs +4 -4
crates/openwa-core/src/address.rs
CHANGED
|
@@ -45,8 +45,9 @@ pub mod va {
|
|
| 45 |
pub const DDKEYBOARD_VTABLE: u32 = 0x0066_AEC8;
|
| 46 |
/// Palette vtable (0x28-byte palette object)
|
| 47 |
pub const PALETTE_VTABLE_MAYBE: u32 = 0x0066_A2E4;
|
| 48 |
-
///
|
| 49 |
-
|
|
|
|
| 50 |
/// Input controller vtable (0x1800-byte object, set inline before FUN_0058C0D0)
|
| 51 |
pub const INPUT_CTRL_VTABLE: u32 = 0x0066_B3FC;
|
| 52 |
/// TaskStateMachine vtable
|
|
@@ -276,9 +277,9 @@ pub mod va {
|
|
| 276 |
pub const DDISPLAY_INIT: u32 = 0x0056_9D00;
|
| 277 |
/// Alias kept for callers that use the old name.
|
| 278 |
pub const CONSTRUCT_DD_DISPLAY: u32 = DDISPLAY_INIT;
|
| 279 |
-
///
|
| 280 |
-
/// Constructs the 0x3560-byte
|
| 281 |
-
pub const
|
| 282 |
/// Streaming audio constructor — stdcall(IDirectSound*, path_config_ptr) → *mut u8.
|
| 283 |
/// Constructs 0x354-byte streaming audio object (only if GameInfo+0xDAA4 != 0).
|
| 284 |
pub const STREAMING_AUDIO_CTOR: u32 = 0x0058_BC10;
|
|
|
|
| 45 |
pub const DDKEYBOARD_VTABLE: u32 = 0x0066_AEC8;
|
| 46 |
/// Palette vtable (0x28-byte palette object)
|
| 47 |
pub const PALETTE_VTABLE_MAYBE: u32 = 0x0066_A2E4;
|
| 48 |
+
/// DDDisplayBase headless vtable — overlaid after DDDisplayBase__Constructor
|
| 49 |
+
/// in headless mode, filling in stub slots for headless operation.
|
| 50 |
+
pub const DD_DISPLAY_BASE_HEADLESS_VTABLE: u32 = 0x0066_A0F8;
|
| 51 |
/// Input controller vtable (0x1800-byte object, set inline before FUN_0058C0D0)
|
| 52 |
pub const INPUT_CTRL_VTABLE: u32 = 0x0066_B3FC;
|
| 53 |
/// TaskStateMachine vtable
|
|
|
|
| 277 |
pub const DDISPLAY_INIT: u32 = 0x0056_9D00;
|
| 278 |
/// Alias kept for callers that use the old name.
|
| 279 |
pub const CONSTRUCT_DD_DISPLAY: u32 = DDISPLAY_INIT;
|
| 280 |
+
/// DDDisplayBase__Constructor — stdcall(this) → DDDisplayBase*.
|
| 281 |
+
/// Constructs the 0x3560-byte base display object (used standalone in headless mode).
|
| 282 |
+
pub const DD_DISPLAY_BASE_CTOR: u32 = 0x0052_2DB0;
|
| 283 |
/// Streaming audio constructor — stdcall(IDirectSound*, path_config_ptr) → *mut u8.
|
| 284 |
/// Constructs 0x354-byte streaming audio object (only if GameInfo+0xDAA4 != 0).
|
| 285 |
pub const STREAMING_AUDIO_CTOR: u32 = 0x0058_BC10;
|
crates/openwa-core/src/dd_display_base.rs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// DDDisplayBase — base class of the display subsystem hierarchy.
|
| 2 |
+
///
|
| 3 |
+
/// Constructor: DDDisplayBase__Constructor (0x522DB0), stdcall(this) → DDDisplayBase*.
|
| 4 |
+
/// Vtable (primary): 0x6645F8 (set by constructor).
|
| 5 |
+
/// Vtable (headless overlay): 0x66A0F8 (fills in stub slots for headless mode).
|
| 6 |
+
/// Size: 0x3560 bytes.
|
| 7 |
+
///
|
| 8 |
+
/// Inheritance:
|
| 9 |
+
/// ```text
|
| 10 |
+
/// DDDisplayBase (this) ← vtable 0x6645F8 / 0x66A0F8
|
| 11 |
+
/// └─ DisplayGfx (derived) ← vtable 0x66A218
|
| 12 |
+
/// ```
|
| 13 |
+
///
|
| 14 |
+
/// In headless mode (`GameInfo.headless_mode != 0`), only the base is constructed
|
| 15 |
+
/// with the headless vtable overlay. In normal mode, `DisplayGfx` (derived) is
|
| 16 |
+
/// constructed instead. The session's `display` field holds a polymorphic pointer
|
| 17 |
+
/// to whichever variant.
|
| 18 |
+
#[repr(C)]
|
| 19 |
+
pub struct DDDisplayBase {
|
| 20 |
+
pub vtable: *mut u8,
|
| 21 |
+
pub _unknown_004: [u8; 0x3560 - 4],
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
const _: () = assert!(core::mem::size_of::<DDDisplayBase>() == 0x3560);
|
| 25 |
+
|
| 26 |
+
impl DDDisplayBase {
|
| 27 |
+
/// Allocate and construct a DDDisplayBase for headless mode.
|
| 28 |
+
///
|
| 29 |
+
/// Calls WA's native constructor, then overlays the headless vtable.
|
| 30 |
+
///
|
| 31 |
+
/// # Safety
|
| 32 |
+
/// Must be called from within the WA.exe process.
|
| 33 |
+
pub unsafe fn construct() -> *mut Self {
|
| 34 |
+
use crate::address::va;
|
| 35 |
+
use crate::rebase::rb;
|
| 36 |
+
use crate::wa_alloc::WABox;
|
| 37 |
+
let this = WABox::<Self>::alloc(0x3560, 0x3560).leak();
|
| 38 |
+
let ctor: unsafe extern "stdcall" fn(*mut Self) -> *mut Self =
|
| 39 |
+
core::mem::transmute(rb(va::DD_DISPLAY_BASE_CTOR) as usize);
|
| 40 |
+
ctor(this);
|
| 41 |
+
(*this).vtable = rb(va::DD_DISPLAY_BASE_HEADLESS_VTABLE) as *mut u8;
|
| 42 |
+
this
|
| 43 |
+
}
|
| 44 |
+
}
|
crates/openwa-core/src/display_gfx.rs
CHANGED
|
@@ -1,10 +1,14 @@
|
|
| 1 |
-
/// DisplayGfx —
|
| 2 |
///
|
| 3 |
/// Constructor: DisplayGfx__Constructor (0x569C10), stdcall(this) → DisplayGfx*.
|
| 4 |
/// Size: 0x24E28 bytes.
|
| 5 |
///
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
/// Created by GameEngine__InitHardware in normal (non-headless) mode.
|
| 7 |
-
/// Stored
|
| 8 |
///
|
| 9 |
/// OPAQUE: Internal layout not yet mapped.
|
| 10 |
#[repr(C)]
|
|
|
|
| 1 |
+
/// DisplayGfx — full display/graphics subsystem (derived from DDDisplayBase).
|
| 2 |
///
|
| 3 |
/// Constructor: DisplayGfx__Constructor (0x569C10), stdcall(this) → DisplayGfx*.
|
| 4 |
/// Size: 0x24E28 bytes.
|
| 5 |
///
|
| 6 |
+
/// Inheritance: DDDisplayBase (0x3560) → DisplayGfx (0x24E28).
|
| 7 |
+
/// The constructor calls DDDisplayBase__Constructor first, then sets the
|
| 8 |
+
/// DisplayGfx vtable (0x66A218) and initializes display-specific fields.
|
| 9 |
+
///
|
| 10 |
/// Created by GameEngine__InitHardware in normal (non-headless) mode.
|
| 11 |
+
/// Stored in the session's `display` field (shared with DDDisplayBase in headless).
|
| 12 |
///
|
| 13 |
/// OPAQUE: Internal layout not yet mapped.
|
| 14 |
#[repr(C)]
|
crates/openwa-core/src/game_session.rs
CHANGED
|
@@ -74,9 +74,9 @@ pub struct GameSession {
|
|
| 74 |
pub keyboard: *mut DDKeyboard,
|
| 75 |
/// 0x0A8: `DSSound*` — 0xBE0 bytes, vtable `DSSound_vtable` (0x66AF20)
|
| 76 |
pub sound: *mut DSSound,
|
| 77 |
-
/// 0x0AC: `DisplayGfx*`
|
| 78 |
-
/// Stays `*mut u8` because
|
| 79 |
-
pub
|
| 80 |
/// 0x0B0: `Palette*` — 0x28 bytes, vtable `Palette_vtable_Maybe`
|
| 81 |
pub palette: *mut Palette,
|
| 82 |
/// 0x0B4: streaming audio object — 0x354 bytes (`FUN_0058bc10`)
|
|
|
|
| 74 |
pub keyboard: *mut DDKeyboard,
|
| 75 |
/// 0x0A8: `DSSound*` — 0xBE0 bytes, vtable `DSSound_vtable` (0x66AF20)
|
| 76 |
pub sound: *mut DSSound,
|
| 77 |
+
/// 0x0AC: Polymorphic display — `DisplayGfx*` (normal) or `DDDisplayBase*` (headless).
|
| 78 |
+
/// Stays `*mut u8` because the concrete type depends on mode.
|
| 79 |
+
pub display: *mut u8,
|
| 80 |
/// 0x0B0: `Palette*` — 0x28 bytes, vtable `Palette_vtable_Maybe`
|
| 81 |
pub palette: *mut Palette,
|
| 82 |
/// 0x0B4: streaming audio object — 0x354 bytes (`FUN_0058bc10`)
|
crates/openwa-core/src/game_stats.rs
DELETED
|
@@ -1,36 +0,0 @@
|
|
| 1 |
-
/// GameStats — headless mode display stub.
|
| 2 |
-
///
|
| 3 |
-
/// Constructor: GameStats__Constructor (0x522DB0), stdcall(this) → GameStats*.
|
| 4 |
-
/// Vtable overlay: 0x66A0F8 (applied after constructor returns).
|
| 5 |
-
/// Size: 0x3560 bytes.
|
| 6 |
-
///
|
| 7 |
-
/// Created by GameEngine__InitHardware in headless mode (GameInfo+0xF914 != 0)
|
| 8 |
-
/// instead of DisplayGfx. Stored in the same GameSession+0xAC slot.
|
| 9 |
-
///
|
| 10 |
-
/// OPAQUE: Internal layout not yet mapped.
|
| 11 |
-
#[repr(C)]
|
| 12 |
-
pub struct GameStats {
|
| 13 |
-
pub vtable: *mut u8,
|
| 14 |
-
pub _unknown_004: [u8; 0x3560 - 4],
|
| 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-core/src/lib.rs
CHANGED
|
@@ -37,4 +37,4 @@ pub mod input_ctrl;
|
|
| 37 |
pub mod game_timer;
|
| 38 |
pub mod streaming_audio;
|
| 39 |
pub mod ddnetgame_wrapper;
|
| 40 |
-
pub mod
|
|
|
|
| 37 |
pub mod game_timer;
|
| 38 |
pub mod streaming_audio;
|
| 39 |
pub mod ddnetgame_wrapper;
|
| 40 |
+
pub mod dd_display_base;
|
crates/openwa-wormkit/src/replacements/hardware_init.rs
CHANGED
|
@@ -52,7 +52,7 @@ use openwa_core::dssound::DSSound;
|
|
| 52 |
use openwa_core::palette::Palette;
|
| 53 |
use openwa_core::input_ctrl::{InputCtrl, InputCtrlVtable};
|
| 54 |
use openwa_core::game_timer::GameTimer;
|
| 55 |
-
use openwa_core::
|
| 56 |
use openwa_core::display_gfx::DisplayGfx;
|
| 57 |
use openwa_core::streaming_audio::StreamingAudio;
|
| 58 |
use openwa_core::ddnetgame_wrapper::DDNetGameWrapper;
|
|
@@ -335,7 +335,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 335 |
if !headless {
|
| 336 |
// ── DisplayGfx ───────────────────────────────────────────────────────
|
| 337 |
let display_gfx = DisplayGfx::construct();
|
| 338 |
-
(*session).
|
| 339 |
|
| 340 |
// ── DDDisplay::Init retry loop ────────────────────────────────────────
|
| 341 |
let flags = gi.display_flags;
|
|
@@ -430,7 +430,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 430 |
}
|
| 431 |
} else {
|
| 432 |
// ── Headless / stats mode ─────────────────────────────────────────────
|
| 433 |
-
(*session).
|
| 434 |
(*session).keyboard = core::ptr::null_mut();
|
| 435 |
(*session).sound = core::ptr::null_mut();
|
| 436 |
(*session).palette = core::ptr::null_mut();
|
|
@@ -445,7 +445,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 445 |
let wrapper = game_session::construct_ddgame_wrapper(
|
| 446 |
game_info,
|
| 447 |
WABox::<DDGameWrapper>::alloc(0x6F10, 0x6EF0).leak(),
|
| 448 |
-
(*session).
|
| 449 |
(*session).sound,
|
| 450 |
(*session).keyboard as *mut u8,
|
| 451 |
(*session).palette,
|
|
|
|
| 52 |
use openwa_core::palette::Palette;
|
| 53 |
use openwa_core::input_ctrl::{InputCtrl, InputCtrlVtable};
|
| 54 |
use openwa_core::game_timer::GameTimer;
|
| 55 |
+
use openwa_core::dd_display_base::DDDisplayBase;
|
| 56 |
use openwa_core::display_gfx::DisplayGfx;
|
| 57 |
use openwa_core::streaming_audio::StreamingAudio;
|
| 58 |
use openwa_core::ddnetgame_wrapper::DDNetGameWrapper;
|
|
|
|
| 335 |
if !headless {
|
| 336 |
// ── DisplayGfx ───────────────────────────────────────────────────────
|
| 337 |
let display_gfx = DisplayGfx::construct();
|
| 338 |
+
(*session).display = display_gfx as *mut u8;
|
| 339 |
|
| 340 |
// ── DDDisplay::Init retry loop ────────────────────────────────────────
|
| 341 |
let flags = gi.display_flags;
|
|
|
|
| 430 |
}
|
| 431 |
} else {
|
| 432 |
// ── Headless / stats mode ─────────────────────────────────────────────
|
| 433 |
+
(*session).display = DDDisplayBase::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();
|
|
|
|
| 445 |
let wrapper = game_session::construct_ddgame_wrapper(
|
| 446 |
game_info,
|
| 447 |
WABox::<DDGameWrapper>::alloc(0x6F10, 0x6EF0).leak(),
|
| 448 |
+
(*session).display as *mut DDDisplay,
|
| 449 |
(*session).sound,
|
| 450 |
(*session).keyboard as *mut u8,
|
| 451 |
(*session).palette,
|