refactor: add typed structs and named fields to hardware_init
Browse files- Extend GameInfo to 0xF91C with named fields (display_width/height,
display_flags, headless_mode, speech_enabled, etc.)
- Add 6 placeholder structs: DisplayGfx, InputCtrl, GameTimer,
StreamingAudio, DDNetGameWrapper, GameStats
- Expand DDKeyboard with construction fields and 0x33C size assert
- Fix DSSound size to 0xBE0 and rename COM fields (direct_sound,
primary_buffer, init_success)
- Type GameSession pointers: DDGameWrapper, DDKeyboard, DSSound, Palette
- Fix DDKeyboard+0x4: must store ADDRESS of GameInfo+0xF918, not value
- Fix DSSound init: check Play() result, not outer DirectSoundCreate hr
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/address.rs +2 -0
- crates/openwa-core/src/ddkeyboard.rs +25 -3
- crates/openwa-core/src/ddnetgame_wrapper.rs +15 -0
- crates/openwa-core/src/display_gfx.rs +16 -0
- crates/openwa-core/src/dssound.rs +18 -19
- crates/openwa-core/src/game_info.rs +58 -14
- crates/openwa-core/src/game_session.rs +11 -5
- crates/openwa-core/src/game_stats.rs +17 -0
- crates/openwa-core/src/game_timer.rs +15 -0
- crates/openwa-core/src/input_ctrl.rs +21 -0
- crates/openwa-core/src/lib.rs +6 -0
- crates/openwa-core/src/streaming_audio.rs +16 -0
- crates/openwa-wormkit/src/replacements/config.rs +7 -5
- crates/openwa-wormkit/src/replacements/hardware_init.rs +96 -92
crates/openwa-core/src/address.rs
CHANGED
|
@@ -664,6 +664,8 @@ pub mod va {
|
|
| 664 |
pub const G_FULLSCREEN_FLAG: u32 = 0x007A_084C;
|
| 665 |
/// Suppress-cursor flag β if non-zero, skip SetCursorPos/ClipCursor in hardware init.
|
| 666 |
pub const G_SUPPRESS_CURSOR: u32 = 0x0088_E485;
|
|
|
|
|
|
|
| 667 |
/// Total sprite data bytes loaded (accumulated by ProcessSprite)
|
| 668 |
pub const G_SPRITE_DATA_BYTES: u32 = 0x007A_0864;
|
| 669 |
/// Total sprite frame count loaded
|
|
|
|
| 664 |
pub const G_FULLSCREEN_FLAG: u32 = 0x007A_084C;
|
| 665 |
/// Suppress-cursor flag β if non-zero, skip SetCursorPos/ClipCursor in hardware init.
|
| 666 |
pub const G_SUPPRESS_CURSOR: u32 = 0x0088_E485;
|
| 667 |
+
/// IAT thunk for MapWindowPoints (USER32.dll import).
|
| 668 |
+
pub const IAT_MAP_WINDOW_POINTS: u32 = 0x0061_A588;
|
| 669 |
/// Total sprite data bytes loaded (accumulated by ProcessSprite)
|
| 670 |
pub const G_SPRITE_DATA_BYTES: u32 = 0x007A_0864;
|
| 671 |
/// Total sprite frame count loaded
|
crates/openwa-core/src/ddkeyboard.rs
CHANGED
|
@@ -5,17 +5,39 @@
|
|
| 5 |
/// PollKeyboardState (0x572290): drains WM_KEY messages, calls GetKeyboardState,
|
| 6 |
/// normalizes to both `key_state` (+0x11C) and `prev_state` (+0x21C) buffers.
|
| 7 |
///
|
| 8 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
#[repr(C)]
|
| 10 |
pub struct DDKeyboard {
|
| 11 |
/// 0x000: Vtable pointer (0x66AEC8)
|
| 12 |
pub vtable: *mut u8,
|
| 13 |
-
/// 0x004
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
/// 0x11C: Current key state buffer (256 bytes).
|
| 16 |
/// Populated by PollKeyboardState via GetKeyboardState.
|
| 17 |
pub key_state: [u8; 256],
|
| 18 |
/// 0x21C: Previous key state buffer (256 bytes).
|
| 19 |
/// Copied from key_state at start of each poll cycle.
|
| 20 |
pub prev_state: [u8; 256],
|
|
|
|
|
|
|
| 21 |
}
|
|
|
|
|
|
|
|
|
| 5 |
/// PollKeyboardState (0x572290): drains WM_KEY messages, calls GetKeyboardState,
|
| 6 |
/// normalizes to both `key_state` (+0x11C) and `prev_state` (+0x21C) buffers.
|
| 7 |
///
|
| 8 |
+
/// Size: 0x33C bytes.
|
| 9 |
+
/// Inline construction in GameEngine__InitHardware:
|
| 10 |
+
/// - vtable = 0x66AEC8
|
| 11 |
+
/// - dinput_device = GameInfo+0xF918
|
| 12 |
+
/// - _field_008 = 1
|
| 13 |
+
/// - key_state zeroed (0x100 bytes)
|
| 14 |
+
/// - prev_state zeroed (0x100 bytes)
|
| 15 |
+
/// - _field_014 = 0, _field_018 = 0
|
| 16 |
#[repr(C)]
|
| 17 |
pub struct DDKeyboard {
|
| 18 |
/// 0x000: Vtable pointer (0x66AEC8)
|
| 19 |
pub vtable: *mut u8,
|
| 20 |
+
/// 0x004: Pointer into GameInfo+0xF918 (shared input state location).
|
| 21 |
+
/// Stores the ADDRESS of GameInfo+0xF918, not its value.
|
| 22 |
+
pub game_info_input_ptr: u32,
|
| 23 |
+
/// 0x008: Init flag (set to 1)
|
| 24 |
+
pub _field_008: u32,
|
| 25 |
+
/// 0x00C-0x013: Unknown
|
| 26 |
+
pub _unknown_00c: [u8; 8],
|
| 27 |
+
/// 0x014: Cleared to 0 during construction
|
| 28 |
+
pub _field_014: u32,
|
| 29 |
+
/// 0x018: Cleared to 0 during construction
|
| 30 |
+
pub _field_018: u32,
|
| 31 |
+
/// 0x01C-0x11B: Unknown
|
| 32 |
+
pub _unknown_01c: [u8; 0x100],
|
| 33 |
/// 0x11C: Current key state buffer (256 bytes).
|
| 34 |
/// Populated by PollKeyboardState via GetKeyboardState.
|
| 35 |
pub key_state: [u8; 256],
|
| 36 |
/// 0x21C: Previous key state buffer (256 bytes).
|
| 37 |
/// Copied from key_state at start of each poll cycle.
|
| 38 |
pub prev_state: [u8; 256],
|
| 39 |
+
/// 0x31C: Unknown trailing fields
|
| 40 |
+
pub _unknown_31c: [u8; 0x20],
|
| 41 |
}
|
| 42 |
+
|
| 43 |
+
const _: () = assert!(core::mem::size_of::<DDKeyboard>() == 0x33C);
|
crates/openwa-core/src/ddnetgame_wrapper.rs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// DDNetGameWrapper β network game wrapper.
|
| 2 |
+
///
|
| 3 |
+
/// Constructor: DDNetGameWrapper__Constructor (0x56D1F0), stdcall(this) β DDNetGameWrapper*.
|
| 4 |
+
/// Size: 0x2C bytes.
|
| 5 |
+
///
|
| 6 |
+
/// Created by GameEngine__InitHardware (always, after DDGameWrapper).
|
| 7 |
+
/// Stored at GameSession+0xC0.
|
| 8 |
+
///
|
| 9 |
+
/// OPAQUE: Internal layout not yet mapped.
|
| 10 |
+
#[repr(C)]
|
| 11 |
+
pub struct DDNetGameWrapper {
|
| 12 |
+
pub _data: [u8; 0x2C],
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
const _: () = assert!(core::mem::size_of::<DDNetGameWrapper>() == 0x2C);
|
crates/openwa-core/src/display_gfx.rs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// DisplayGfx β main display/graphics subsystem container.
|
| 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 at GameSession+0xAC.
|
| 8 |
+
///
|
| 9 |
+
/// OPAQUE: Internal layout not yet mapped.
|
| 10 |
+
#[repr(C)]
|
| 11 |
+
pub struct DisplayGfx {
|
| 12 |
+
pub vtable: *mut u8,
|
| 13 |
+
pub _unknown_004: [u8; 0x24E28 - 4],
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
const _: () = assert!(core::mem::size_of::<DisplayGfx>() == 0x24E28);
|
crates/openwa-core/src/dssound.rs
CHANGED
|
@@ -3,40 +3,39 @@ use crate::fixed::Fixed;
|
|
| 3 |
///
|
| 4 |
/// Created by DSSound__Constructor (0x573D50).
|
| 5 |
/// Vtable: 0x66AF20
|
|
|
|
| 6 |
///
|
| 7 |
/// Manages sound playback through DirectSound.
|
| 8 |
/// Has 500 sound channel slots and a master volume control.
|
| 9 |
///
|
| 10 |
-
/// PARTIAL: Only constructor-confirmed fields.
|
| 11 |
-
/// Ghidra uses DWORD-indexed offsets.
|
| 12 |
#[repr(C)]
|
| 13 |
pub struct DSSound {
|
| 14 |
/// 0x000: Vtable pointer (0x66AF20)
|
| 15 |
pub vtable: *mut u8,
|
| 16 |
-
/// 0x004:
|
| 17 |
-
pub
|
| 18 |
-
/// 0x008:
|
| 19 |
-
pub
|
| 20 |
-
/// 0x00C:
|
| 21 |
-
pub
|
| 22 |
-
/// 0x010:
|
| 23 |
-
pub
|
| 24 |
-
/// 0x014-0x8A3: Unknown fields
|
| 25 |
pub _unknown_014: [u8; 0x890],
|
| 26 |
/// 0x8A4: Master volume (Fixed, init 0x10000 = 1.0)
|
| 27 |
pub volume: Fixed,
|
| 28 |
-
/// 0x8A8-0xBB3: Unknown
|
| 29 |
pub _unknown_8a8: [u8; 0x30C],
|
| 30 |
/// 0xBB4: Status flag 1 (init 1)
|
| 31 |
pub status_1: u32,
|
| 32 |
/// 0xBB8: Status flag 2 (init 1)
|
| 33 |
pub status_2: u32,
|
| 34 |
-
/// 0xBBC:
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
| 38 |
}
|
| 39 |
|
| 40 |
-
|
| 41 |
-
// The 500 dword channel table starts around offset 0xD4+ but exact layout unclear.
|
| 42 |
-
const _: () = assert!(core::mem::size_of::<DSSound>() == 0xBD0);
|
|
|
|
| 3 |
///
|
| 4 |
/// Created by DSSound__Constructor (0x573D50).
|
| 5 |
/// Vtable: 0x66AF20
|
| 6 |
+
/// Size: 0xBE0 bytes (allocated by GameEngine__InitHardware).
|
| 7 |
///
|
| 8 |
/// Manages sound playback through DirectSound.
|
| 9 |
/// Has 500 sound channel slots and a master volume control.
|
| 10 |
///
|
| 11 |
+
/// PARTIAL: Only constructor-confirmed and InitHardware-confirmed fields.
|
|
|
|
| 12 |
#[repr(C)]
|
| 13 |
pub struct DSSound {
|
| 14 |
/// 0x000: Vtable pointer (0x66AF20)
|
| 15 |
pub vtable: *mut u8,
|
| 16 |
+
/// 0x004: HWND (set to hwnd after construction)
|
| 17 |
+
pub hwnd: u32,
|
| 18 |
+
/// 0x008: IDirectSound* (from DirectSoundCreate)
|
| 19 |
+
pub direct_sound: *mut u8,
|
| 20 |
+
/// 0x00C: Output param from DSSOUND_INIT_BUFFERS (primary buffer caps/format)
|
| 21 |
+
pub primary_buffer_caps: u32,
|
| 22 |
+
/// 0x010: IDirectSoundBuffer* (primary buffer, from DSSOUND_INIT_BUFFERS)
|
| 23 |
+
pub primary_buffer: *mut u8,
|
| 24 |
+
/// 0x014-0x8A3: Unknown fields (includes 500-entry channel slot table)
|
| 25 |
pub _unknown_014: [u8; 0x890],
|
| 26 |
/// 0x8A4: Master volume (Fixed, init 0x10000 = 1.0)
|
| 27 |
pub volume: Fixed,
|
| 28 |
+
/// 0x8A8-0xBB3: Unknown
|
| 29 |
pub _unknown_8a8: [u8; 0x30C],
|
| 30 |
/// 0xBB4: Status flag 1 (init 1)
|
| 31 |
pub status_1: u32,
|
| 32 |
/// 0xBB8: Status flag 2 (init 1)
|
| 33 |
pub status_2: u32,
|
| 34 |
+
/// 0xBBC: Init success flag β set to 1 when DirectSoundCreate +
|
| 35 |
+
/// DSSOUND_INIT_BUFFERS + IDirectSoundBuffer::Play all succeed.
|
| 36 |
+
pub init_success: u32,
|
| 37 |
+
/// 0xBC0-0xBDF: Unknown trailing fields
|
| 38 |
+
pub _unknown_bc0: [u8; 0x20],
|
| 39 |
}
|
| 40 |
|
| 41 |
+
const _: () = assert!(core::mem::size_of::<DSSound>() == 0xBE0);
|
|
|
|
|
|
crates/openwa-core/src/game_info.rs
CHANGED
|
@@ -4,8 +4,9 @@
|
|
| 4 |
/// `GameInfo__LoadOptions` (0x460AC0) which reads registry values and
|
| 5 |
/// copies global data into known offsets.
|
| 6 |
///
|
| 7 |
-
/// PARTIAL: Only fields discovered through GameInfo__LoadOptions
|
| 8 |
-
///
|
|
|
|
| 9 |
#[repr(C)]
|
| 10 |
pub struct GameInfo {
|
| 11 |
/// 0x0000-0x044B: Unknown
|
|
@@ -21,10 +22,22 @@ pub struct GameInfo {
|
|
| 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-
|
| 27 |
-
pub _unknown_d77c: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
// --- Cluster 1: data paths ---
|
| 30 |
|
|
@@ -33,8 +46,14 @@ pub struct GameInfo {
|
|
| 33 |
/// 0xDAEC: Land data path ("data\land.dat", 14 bytes incl. null)
|
| 34 |
pub land_dat_path: [u8; 14],
|
| 35 |
|
| 36 |
-
/// 0xDAFA-
|
| 37 |
-
pub _unknown_dafa: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
// --- Cluster 2: game options (populated by LoadOptions) ---
|
| 40 |
|
|
@@ -60,10 +79,16 @@ pub struct GameInfo {
|
|
| 60 |
pub home_lock: u8,
|
| 61 |
/// 0xF3B1: Unknown
|
| 62 |
pub _unknown_f3b1: [u8; 3],
|
| 63 |
-
/// 0xF3B4:
|
| 64 |
-
///
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
/// 0xF3D0: Unknown (not written by LoadOptions)
|
| 68 |
pub _unknown_f3d0: [u8; 4],
|
| 69 |
/// 0xF3D4: Config DWORD (from global 0x88E3B0[0])
|
|
@@ -91,11 +116,24 @@ pub struct GameInfo {
|
|
| 91 |
/// 0xF485: Config data block (64 bytes copied from global 0x88DFF3)
|
| 92 |
pub _config_block_f485: [u8; 64],
|
| 93 |
|
| 94 |
-
/// 0xF4C5-0xF4FF: Unknown
|
| 95 |
-
pub _unknown_f4c5: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
}
|
| 97 |
|
| 98 |
-
const _: () = assert!(core::mem::size_of::<GameInfo>() ==
|
| 99 |
|
| 100 |
struct HexU32s<'a>(&'a [u32]);
|
| 101 |
|
|
@@ -138,7 +176,10 @@ impl core::fmt::Debug for GameInfo {
|
|
| 138 |
.field("chat_lines", &self.chat_lines)
|
| 139 |
.field("pinned_chat_lines", &format_args!("0x{:08X}", self.pinned_chat_lines))
|
| 140 |
.field("home_lock", &self.home_lock)
|
| 141 |
-
.field("
|
|
|
|
|
|
|
|
|
|
| 142 |
.field("_config_dword_f3d4", &format_args!("0x{:08X}", self._config_dword_f3d4))
|
| 143 |
.field("_config_dword_f3d8", &format_args!("0x{:08X}", self._config_dword_f3d8))
|
| 144 |
.field("capture_transparent_pngs", &self.capture_transparent_pngs)
|
|
@@ -149,6 +190,9 @@ impl core::fmt::Debug for GameInfo {
|
|
| 149 |
.field("_zeroed_f3f0", &self._zeroed_f3f0)
|
| 150 |
.field("_conditional_config_f3f4", &HexU32s(&self._conditional_config_f3f4))
|
| 151 |
.field("speech_path", &speech_str)
|
|
|
|
|
|
|
|
|
|
| 152 |
.finish()
|
| 153 |
}
|
| 154 |
}
|
|
|
|
| 4 |
/// `GameInfo__LoadOptions` (0x460AC0) which reads registry values and
|
| 5 |
/// copies global data into known offsets.
|
| 6 |
///
|
| 7 |
+
/// PARTIAL: Only fields discovered through GameInfo__LoadOptions and
|
| 8 |
+
/// GameEngine__InitHardware are mapped. Size extended to 0xF91C to
|
| 9 |
+
/// cover all known accesses.
|
| 10 |
#[repr(C)]
|
| 11 |
pub struct GameInfo {
|
| 12 |
/// 0x0000-0x044B: Unknown
|
|
|
|
| 22 |
pub _unknown_044d: [u8; 0xD778 - 0x44D],
|
| 23 |
|
| 24 |
/// 0xD778: Crosshair overflow clamp threshold (compared to 0x11E in render).
|
| 25 |
+
/// Also passed to timer constructor and input controller init.
|
| 26 |
pub crosshair_overflow_threshold: i32,
|
| 27 |
|
| 28 |
+
/// 0xD77C-0xD9DF: Unknown
|
| 29 |
+
pub _unknown_d77c: [u8; 0xD9E0 - 0xD77C],
|
| 30 |
+
|
| 31 |
+
/// 0xD9E0: Streaming audio config data (path config passed to streaming audio ctor).
|
| 32 |
+
/// Address of this field is passed as a pointer parameter.
|
| 33 |
+
pub streaming_audio_config: [u8; 0xDAA4 - 0xD9E0],
|
| 34 |
+
|
| 35 |
+
/// 0xDAA4: Speech/streaming audio enabled flag.
|
| 36 |
+
/// If nonzero, streaming audio subsystem is created in InitHardware.
|
| 37 |
+
pub speech_enabled: u8,
|
| 38 |
+
|
| 39 |
+
/// 0xDAA5-0xDAE7: Unknown
|
| 40 |
+
pub _unknown_daa5: [u8; 0xDAE8 - 0xDAA5],
|
| 41 |
|
| 42 |
// --- Cluster 1: data paths ---
|
| 43 |
|
|
|
|
| 46 |
/// 0xDAEC: Land data path ("data\land.dat", 14 bytes incl. null)
|
| 47 |
pub land_dat_path: [u8; 14],
|
| 48 |
|
| 49 |
+
/// 0xDAFA-0xF373: Unknown
|
| 50 |
+
pub _unknown_dafa: [u8; 0xF374 - 0xDAFA],
|
| 51 |
+
|
| 52 |
+
/// 0xF374: Display flags passed to DDDisplay::Init.
|
| 53 |
+
pub display_flags: u32,
|
| 54 |
+
|
| 55 |
+
/// 0xF378-0xF39F: Unknown
|
| 56 |
+
pub _unknown_f378: [u8; 0xF3A0 - 0xF378],
|
| 57 |
|
| 58 |
// --- Cluster 2: game options (populated by LoadOptions) ---
|
| 59 |
|
|
|
|
| 79 |
pub home_lock: u8,
|
| 80 |
/// 0xF3B1: Unknown
|
| 81 |
pub _unknown_f3b1: [u8; 3],
|
| 82 |
+
/// 0xF3B4: Display width β first DWORD of the config block.
|
| 83 |
+
/// Written from G_CONFIG_DWORDS_F3B4, updated by DDDisplay::Init retry loop.
|
| 84 |
+
pub display_width: u32,
|
| 85 |
+
/// 0xF3B8: Display height β second DWORD of the config block.
|
| 86 |
+
/// Written from G_CONFIG_DWORDS_F3B4, updated by DDDisplay::Init retry loop.
|
| 87 |
+
pub display_height: u32,
|
| 88 |
+
/// 0xF3BC: Remaining config DWORDs (indices 2..7 of the original block).
|
| 89 |
+
/// LoadOptions writes indices 0..5 from G_CONFIG_DWORDS_F3B4,
|
| 90 |
+
/// then indices 4..7 from G_CONFIG_DWORDS_F3C4 (overlapping at [2]).
|
| 91 |
+
pub _config_dwords_f3bc: [u32; 5],
|
| 92 |
/// 0xF3D0: Unknown (not written by LoadOptions)
|
| 93 |
pub _unknown_f3d0: [u8; 4],
|
| 94 |
/// 0xF3D4: Config DWORD (from global 0x88E3B0[0])
|
|
|
|
| 116 |
/// 0xF485: Config data block (64 bytes copied from global 0x88DFF3)
|
| 117 |
pub _config_block_f485: [u8; 64],
|
| 118 |
|
| 119 |
+
/// 0xF4C5-0xF4FF: Unknown
|
| 120 |
+
pub _unknown_f4c5: [u8; 0xF500 - 0xF4C5],
|
| 121 |
+
|
| 122 |
+
// --- Extended region (beyond original 0xF500 conservative estimate) ---
|
| 123 |
+
|
| 124 |
+
/// 0xF500-0xF913: Unknown
|
| 125 |
+
pub _unknown_f500: [u8; 0xF914 - 0xF500],
|
| 126 |
+
|
| 127 |
+
/// 0xF914: Headless/stats mode flag.
|
| 128 |
+
/// If nonzero, InitHardware creates a GameStats stub instead of display hardware.
|
| 129 |
+
pub headless_mode: u32,
|
| 130 |
+
|
| 131 |
+
/// 0xF918: Input state field β DDKeyboard+0x4 stores a pointer TO this address.
|
| 132 |
+
/// The game reads/writes through DDKeyboard's pointer, so this must stay in place.
|
| 133 |
+
pub input_state_f918: u32,
|
| 134 |
}
|
| 135 |
|
| 136 |
+
const _: () = assert!(core::mem::size_of::<GameInfo>() == 0xF91C);
|
| 137 |
|
| 138 |
struct HexU32s<'a>(&'a [u32]);
|
| 139 |
|
|
|
|
| 176 |
.field("chat_lines", &self.chat_lines)
|
| 177 |
.field("pinned_chat_lines", &format_args!("0x{:08X}", self.pinned_chat_lines))
|
| 178 |
.field("home_lock", &self.home_lock)
|
| 179 |
+
.field("display_flags", &format_args!("0x{:08X}", self.display_flags))
|
| 180 |
+
.field("display_width", &self.display_width)
|
| 181 |
+
.field("display_height", &self.display_height)
|
| 182 |
+
.field("_config_dwords_f3bc", &HexU32s(&self._config_dwords_f3bc))
|
| 183 |
.field("_config_dword_f3d4", &format_args!("0x{:08X}", self._config_dword_f3d4))
|
| 184 |
.field("_config_dword_f3d8", &format_args!("0x{:08X}", self._config_dword_f3d8))
|
| 185 |
.field("capture_transparent_pngs", &self.capture_transparent_pngs)
|
|
|
|
| 190 |
.field("_zeroed_f3f0", &self._zeroed_f3f0)
|
| 191 |
.field("_conditional_config_f3f4", &HexU32s(&self._conditional_config_f3f4))
|
| 192 |
.field("speech_path", &speech_str)
|
| 193 |
+
.field("speech_enabled", &self.speech_enabled)
|
| 194 |
+
.field("headless_mode", &self.headless_mode)
|
| 195 |
+
.field("input_state_f918", &format_args!("0x{:08X}", self.input_state_f918))
|
| 196 |
.finish()
|
| 197 |
}
|
| 198 |
}
|
crates/openwa-core/src/game_session.rs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/// Top-level game session context, allocated once per game run.
|
| 2 |
///
|
| 3 |
/// `G_GAME_SESSION` (0x7A0884) stores a pointer to this struct. Created by
|
|
@@ -60,15 +65,16 @@ pub struct GameSession {
|
|
| 60 |
/// 0x09C: high DWORD
|
| 61 |
pub timer_counter_hi: u32,
|
| 62 |
/// 0x0A0: `DDGameWrapper*` β the main game object wrapper (β `DDGame` at `+0x488`)
|
| 63 |
-
pub ddgame_wrapper: *mut
|
| 64 |
/// 0x0A4: `DDKeyboard*` β 0x33C bytes, vtable `DDKeyboard_vtable` (0x66AEC8)
|
| 65 |
-
pub keyboard: *mut
|
| 66 |
/// 0x0A8: `DSSound*` β 0xBE0 bytes, vtable `DSSound_vtable` (0x66AF20)
|
| 67 |
-
pub sound: *mut
|
| 68 |
-
/// 0x0AC: `DisplayGfx*` β 0x24E28 bytes (normal), or `GameStats*` in headless mode
|
|
|
|
| 69 |
pub display_gfx: *mut u8,
|
| 70 |
/// 0x0B0: `Palette*` β 0x28 bytes, vtable `Palette_vtable_Maybe`
|
| 71 |
-
pub palette: *mut
|
| 72 |
/// 0x0B4: streaming audio object β 0x354 bytes (`FUN_0058bc10`)
|
| 73 |
pub streaming_audio: *mut u8,
|
| 74 |
/// 0x0B8: input controller β 0x1800 bytes; null if `param_4 == 0` at init
|
|
|
|
| 1 |
+
use crate::ddgame_wrapper::DDGameWrapper;
|
| 2 |
+
use crate::ddkeyboard::DDKeyboard;
|
| 3 |
+
use crate::dssound::DSSound;
|
| 4 |
+
use crate::palette::Palette;
|
| 5 |
+
|
| 6 |
/// Top-level game session context, allocated once per game run.
|
| 7 |
///
|
| 8 |
/// `G_GAME_SESSION` (0x7A0884) stores a pointer to this struct. Created by
|
|
|
|
| 65 |
/// 0x09C: high DWORD
|
| 66 |
pub timer_counter_hi: u32,
|
| 67 |
/// 0x0A0: `DDGameWrapper*` β the main game object wrapper (β `DDGame` at `+0x488`)
|
| 68 |
+
pub ddgame_wrapper: *mut DDGameWrapper,
|
| 69 |
/// 0x0A4: `DDKeyboard*` β 0x33C bytes, vtable `DDKeyboard_vtable` (0x66AEC8)
|
| 70 |
+
pub keyboard: *mut DDKeyboard,
|
| 71 |
/// 0x0A8: `DSSound*` β 0xBE0 bytes, vtable `DSSound_vtable` (0x66AF20)
|
| 72 |
+
pub sound: *mut DSSound,
|
| 73 |
+
/// 0x0AC: `DisplayGfx*` β 0x24E28 bytes (normal), or `GameStats*` in headless mode.
|
| 74 |
+
/// Stays `*mut u8` because it's dual-purpose (DisplayGfx vs GameStats).
|
| 75 |
pub display_gfx: *mut u8,
|
| 76 |
/// 0x0B0: `Palette*` β 0x28 bytes, vtable `Palette_vtable_Maybe`
|
| 77 |
+
pub palette: *mut Palette,
|
| 78 |
/// 0x0B4: streaming audio object β 0x354 bytes (`FUN_0058bc10`)
|
| 79 |
pub streaming_audio: *mut u8,
|
| 80 |
/// 0x0B8: input controller β 0x1800 bytes; null if `param_4 == 0` at init
|
crates/openwa-core/src/game_stats.rs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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);
|
crates/openwa-core/src/game_timer.rs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// GameTimer β timing subsystem.
|
| 2 |
+
///
|
| 3 |
+
/// Constructor: FUN_0053E950, usercall(ESI=this, EAX=init_val), plain RET.
|
| 4 |
+
/// Size: 0x30 bytes (header). Constructor allocates 2 Γ 0x20E0 internal buffers.
|
| 5 |
+
///
|
| 6 |
+
/// Created by GameEngine__InitHardware (always).
|
| 7 |
+
/// Stored at GameSession+0xBC.
|
| 8 |
+
///
|
| 9 |
+
/// OPAQUE: Internal layout not yet mapped.
|
| 10 |
+
#[repr(C)]
|
| 11 |
+
pub struct GameTimer {
|
| 12 |
+
pub _data: [u8; 0x30],
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
const _: () = assert!(core::mem::size_of::<GameTimer>() == 0x30);
|
crates/openwa-core/src/input_ctrl.rs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// InputCtrl β input controller subsystem.
|
| 2 |
+
///
|
| 3 |
+
/// Initializer: FUN_0058C0D0, usercall(ESI=this) + stdcall(4 params), RET 0x10.
|
| 4 |
+
/// Vtable: 0x66B3FC.
|
| 5 |
+
/// Size: 0x1800 bytes.
|
| 6 |
+
///
|
| 7 |
+
/// Created by GameEngine__InitHardware when param4 != 0.
|
| 8 |
+
/// Stored at GameSession+0xB8.
|
| 9 |
+
///
|
| 10 |
+
/// OPAQUE: Internal layout not yet mapped beyond vtable and a few known fields.
|
| 11 |
+
#[repr(C)]
|
| 12 |
+
pub struct InputCtrl {
|
| 13 |
+
/// 0x000: Vtable pointer (0x66B3FC)
|
| 14 |
+
pub vtable: *mut u8,
|
| 15 |
+
pub _unknown_004: [u8; 0xD74 - 4],
|
| 16 |
+
/// 0xD74: Set to 0x3F9 during inline construction.
|
| 17 |
+
pub _field_d74: u32,
|
| 18 |
+
pub _unknown_d78: [u8; 0x1800 - 0xD78],
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
const _: () = assert!(core::mem::size_of::<InputCtrl>() == 0x1800);
|
crates/openwa-core/src/lib.rs
CHANGED
|
@@ -29,3 +29,9 @@ pub mod rebase;
|
|
| 29 |
pub mod wa;
|
| 30 |
pub mod wa_call;
|
| 31 |
pub mod wa_alloc;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
pub mod wa;
|
| 30 |
pub mod wa_call;
|
| 31 |
pub mod wa_alloc;
|
| 32 |
+
pub mod display_gfx;
|
| 33 |
+
pub mod input_ctrl;
|
| 34 |
+
pub mod game_timer;
|
| 35 |
+
pub mod streaming_audio;
|
| 36 |
+
pub mod ddnetgame_wrapper;
|
| 37 |
+
pub mod game_stats;
|
crates/openwa-core/src/streaming_audio.rs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// StreamingAudio β streaming audio playback subsystem (music).
|
| 2 |
+
///
|
| 3 |
+
/// Constructor: FUN_0058BC10, usercall(ESI=this) + 2 stack params, RET 0x8.
|
| 4 |
+
/// Size: 0x354 bytes.
|
| 5 |
+
///
|
| 6 |
+
/// Created by GameEngine__InitHardware when speech is enabled
|
| 7 |
+
/// (GameInfo+0xDAA4 != 0) and DSSound initialized successfully.
|
| 8 |
+
/// Stored at GameSession+0xB4.
|
| 9 |
+
///
|
| 10 |
+
/// OPAQUE: Internal layout not yet mapped.
|
| 11 |
+
#[repr(C)]
|
| 12 |
+
pub struct StreamingAudio {
|
| 13 |
+
pub _data: [u8; 0x354],
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
const _: () = assert!(core::mem::size_of::<StreamingAudio>() == 0x354);
|
crates/openwa-wormkit/src/replacements/config.rs
CHANGED
|
@@ -196,10 +196,12 @@ unsafe extern "stdcall" fn hook_load_options(gi_ptr: u32) {
|
|
| 196 |
gi.detail_level = read_profile_int("Options", "DetailLevel", 5) as u8;
|
| 197 |
gi._zeroed_f3f0 = 0;
|
| 198 |
|
| 199 |
-
// Copy 5 DWORDs from globals (
|
| 200 |
let src = rb(va::G_CONFIG_DWORDS_F3B4) as *const u32;
|
| 201 |
-
|
| 202 |
-
|
|
|
|
|
|
|
| 203 |
}
|
| 204 |
|
| 205 |
// Conditional copy: 4 DWORDs if guard == 0
|
|
@@ -219,10 +221,10 @@ unsafe extern "stdcall" fn hook_load_options(gi_ptr: u32) {
|
|
| 219 |
|
| 220 |
gi.energy_bar = read_profile_int("Options", "EnergyBar", 1) as u8;
|
| 221 |
|
| 222 |
-
// 3 DWORDs from globals (overwrite indices
|
| 223 |
let src_c4 = rb(va::G_CONFIG_DWORDS_F3C4) as *const u32;
|
| 224 |
for i in 0..3 {
|
| 225 |
-
gi.
|
| 226 |
}
|
| 227 |
|
| 228 |
gi.info_transparency = read_profile_int("Options", "InfoTransparency", 0) as u8;
|
|
|
|
| 196 |
gi.detail_level = read_profile_int("Options", "DetailLevel", 5) as u8;
|
| 197 |
gi._zeroed_f3f0 = 0;
|
| 198 |
|
| 199 |
+
// Copy 5 DWORDs from globals (display_width, display_height, + 3 more)
|
| 200 |
let src = rb(va::G_CONFIG_DWORDS_F3B4) as *const u32;
|
| 201 |
+
gi.display_width = *src;
|
| 202 |
+
gi.display_height = *src.add(1);
|
| 203 |
+
for i in 0..3 {
|
| 204 |
+
gi._config_dwords_f3bc[i] = *src.add(i + 2);
|
| 205 |
}
|
| 206 |
|
| 207 |
// Conditional copy: 4 DWORDs if guard == 0
|
|
|
|
| 221 |
|
| 222 |
gi.energy_bar = read_profile_int("Options", "EnergyBar", 1) as u8;
|
| 223 |
|
| 224 |
+
// 3 DWORDs from globals (overwrite indices 2..5 of _config_dwords_f3bc)
|
| 225 |
let src_c4 = rb(va::G_CONFIG_DWORDS_F3C4) as *const u32;
|
| 226 |
for i in 0..3 {
|
| 227 |
+
gi._config_dwords_f3bc[i + 2] = *src_c4.add(i);
|
| 228 |
}
|
| 229 |
|
| 230 |
gi.info_transparency = read_profile_int("Options", "InfoTransparency", 0) as u8;
|
crates/openwa-wormkit/src/replacements/hardware_init.rs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
//!
|
| 6 |
//! ## Calling convention
|
| 7 |
//!
|
| 8 |
-
//! `__thiscall`: ECX = `*mut GameInfo` (β₯
|
| 9 |
//! (hwnd, param3, param4), `RET 0xC`. Returns 1 on success, 0 on failure.
|
| 10 |
//!
|
| 11 |
//! The naked entry trampoline captures ECX, pops the return address β `SAVED_RET`,
|
|
@@ -21,21 +21,21 @@
|
|
| 21 |
//! IF param4 != 0:
|
| 22 |
//! input ctrl (0x1800 bytes, inline vtable, FUN_0058C0D0 usercall ESI=this) β session+0xB8
|
| 23 |
//!
|
| 24 |
-
//! IF GameInfo
|
| 25 |
//! DisplayGfx (0x24E28, stdcall ctor) β session+0xAC
|
| 26 |
//! DDDisplay::Init retry loop (configured β 1024Γ768 β 800Γ600 β 640Γ480)
|
| 27 |
//! screen center / cursor setup
|
| 28 |
//! DDKeyboard (0x33C, inline) β session+0xA4
|
| 29 |
//! Palette (0x28, inline) β session+0xB0
|
| 30 |
//! DSSound (0xBE0, usercall ctor + DirectSoundCreate + coop level) β session+0xA8
|
| 31 |
-
//! IF GameInfo
|
| 32 |
//!
|
| 33 |
//! ELSE (headless):
|
| 34 |
//! GameStats (0x3560, stdcall ctor + vtable override) β session+0xAC
|
| 35 |
//! session+0xA4/0xA8/0xB0/0xB4 = null
|
| 36 |
//!
|
| 37 |
//! ALWAYS:
|
| 38 |
-
//! session+0x28 = (GameInfo
|
| 39 |
//! DDGameWrapper (0x6F10) β session+0xA0 [via game_session::construct_ddgame_wrapper]
|
| 40 |
//! Palette vtable[4/3/2] calls + DDKeyboard poll (normal mode only)
|
| 41 |
//! DDNetGameWrapper (0x2C, stdcall ctor) β session+0xC0
|
|
@@ -43,11 +43,16 @@
|
|
| 43 |
|
| 44 |
use openwa_core::address::va;
|
| 45 |
use openwa_core::rebase::rb;
|
|
|
|
| 46 |
use openwa_core::game_session::GameSession;
|
| 47 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
|
|
|
| 48 |
use openwa_core::dddisplay::DDDisplay;
|
| 49 |
use openwa_core::dssound::DSSound;
|
| 50 |
use openwa_core::palette::Palette;
|
|
|
|
|
|
|
|
|
|
| 51 |
use openwa_core::wa_alloc::WABox;
|
| 52 |
use crate::hook;
|
| 53 |
use crate::log_line;
|
|
@@ -88,7 +93,7 @@ static mut DSSOUND_INIT_EAX: u32 = 0;
|
|
| 88 |
/// Timer constructor: `usercall(ESI=timer_ptr, EAX=d778_val)`, plain RET.
|
| 89 |
/// Returns whatever EAX holds after the call.
|
| 90 |
#[unsafe(naked)]
|
| 91 |
-
unsafe extern "cdecl" fn call_timer_ctor(_timer_ptr: *mut
|
| 92 |
core::arch::naked_asm!(
|
| 93 |
// [esp+0]=bridge_ret, [esp+4]=timer_ptr, [esp+8]=d778_val
|
| 94 |
"pushl %esi",
|
|
@@ -108,7 +113,7 @@ unsafe extern "cdecl" fn call_timer_ctor(_timer_ptr: *mut u8, _d778_val: u32) ->
|
|
| 108 |
/// IMPORTANT: FUN_00573D50 clobbers ECX (MOV ECX, 0x1F4 for REP STOSD).
|
| 109 |
/// Do NOT use ECX to save bridge_ret β read it from the stack instead.
|
| 110 |
#[unsafe(naked)]
|
| 111 |
-
unsafe extern "cdecl" fn call_dssound_ctor(_dssound: *mut
|
| 112 |
core::arch::naked_asm!(
|
| 113 |
// [esp+0]=bridge_ret, [esp+4]=dssound
|
| 114 |
"movl 4(%esp), %eax", // EAX = dssound (bridge_ret stays on stack untouched)
|
|
@@ -129,19 +134,19 @@ static mut DSSOUND_INIT_SAVED_RET: u32 = 0;
|
|
| 129 |
/// Caller sets `DSSOUND_INIT_EAX = dssound` before calling.
|
| 130 |
#[unsafe(naked)]
|
| 131 |
unsafe extern "stdcall" fn call_dssound_init_buffers(
|
| 132 |
-
|
| 133 |
-
|
| 134 |
) -> u32 {
|
| 135 |
core::arch::naked_asm!(
|
| 136 |
-
// Entry: ESP=E, [E+0]=bridge_ret, [E+4]=
|
| 137 |
"movl {eax_val}, %eax", // EAX = dssound
|
| 138 |
"movl (%esp), %ecx", // ECX = bridge_ret (temp)
|
| 139 |
"movl %ecx, {saved_ret}", // save to static (ECX clobbered by FUN_00573E50)
|
| 140 |
-
"movl 4(%esp), %ecx", // ECX =
|
| 141 |
-
"movl 8(%esp), %edx", // EDX =
|
| 142 |
"addl $0xC, %esp", // ESP = E+12 (discard bridge_ret + 2 args)
|
| 143 |
-
"pushl %edx", // ESP = E+8, [E+8] =
|
| 144 |
-
"pushl %ecx", // ESP = E+4, [E+4] =
|
| 145 |
"calll *({fn})", // ESP = E, [E] = cont
|
| 146 |
// FUN_00573E50 RET 0x8: pops cont β E+4, +8 β E+12
|
| 147 |
// At cont: ESP = E+12; EAX = return value
|
|
@@ -242,43 +247,48 @@ unsafe extern "stdcall" fn call_ddisplay_init(
|
|
| 242 |
// βββ Implementation ββββββββββββββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½ββββββ
|
| 243 |
|
| 244 |
unsafe extern "cdecl" fn impl_init_hardware(
|
| 245 |
-
game_info: *mut
|
| 246 |
hwnd: u32,
|
| 247 |
param3: u32,
|
| 248 |
param4: u32,
|
| 249 |
) -> u32 {
|
| 250 |
let _ = log_line("[hardware_init] GameEngine::InitHardware");
|
| 251 |
let session = *(rb(va::G_GAME_SESSION) as *const *mut GameSession);
|
| 252 |
-
let
|
|
|
|
| 253 |
|
| 254 |
// ββ Input controller (if param4 != 0) ββββββββββββββββββββββββββββββββββββ
|
| 255 |
if param4 == 0 {
|
| 256 |
(*session).input_ctrl = core::ptr::null_mut();
|
| 257 |
} else {
|
| 258 |
-
let
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
if ok == 0 {
|
| 267 |
-
let vtbl = *(
|
| 268 |
let dtor: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 269 |
core::mem::transmute(*vtbl);
|
| 270 |
-
dtor(
|
| 271 |
(*session).input_ctrl = core::ptr::null_mut();
|
| 272 |
return 0;
|
| 273 |
}
|
| 274 |
}
|
| 275 |
|
| 276 |
// ββ Timer object (ALWAYS) βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 277 |
-
let
|
| 278 |
-
call_timer_ctor(
|
| 279 |
-
(*session).timer_obj =
|
| 280 |
|
| 281 |
-
let headless =
|
| 282 |
|
| 283 |
if !headless {
|
| 284 |
// ββ DisplayGfx βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -288,11 +298,9 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 288 |
(*session).display_gfx = display_gfx;
|
| 289 |
|
| 290 |
// ββ DDDisplay::Init retry loop ββββββββββββββββββββββββββββββββββββββββ
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
let
|
| 294 |
-
let w0 = *(game_info.add(0xF3B4) as *const u32);
|
| 295 |
-
let h0 = *(game_info.add(0xF3B8) as *const u32);
|
| 296 |
|
| 297 |
DDISPLAY_INIT_ECX = h0;
|
| 298 |
let mut init_ok = call_ddisplay_init(display_gfx, hwnd, w0, flags) != 0;
|
|
@@ -304,8 +312,8 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 304 |
(0x280, 0x1E0), // 640Γ480
|
| 305 |
];
|
| 306 |
for &(w, h) in &fallbacks {
|
| 307 |
-
|
| 308 |
-
|
| 309 |
DDISPLAY_INIT_ECX = h;
|
| 310 |
if call_ddisplay_init(display_gfx, hwnd, w, flags) != 0 {
|
| 311 |
init_ok = true;
|
|
@@ -329,9 +337,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 329 |
let h = GetSystemMetrics(SM_CYSCREEN);
|
| 330 |
(w / 2, h / 2)
|
| 331 |
} else {
|
| 332 |
-
|
| 333 |
-
let h = *(game_info.add(0xF3B8) as *const i32);
|
| 334 |
-
(w / 2, h / 2)
|
| 335 |
};
|
| 336 |
|
| 337 |
(*session).screen_center_x = cx;
|
|
@@ -348,8 +354,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 348 |
let hwnd_val: HWND = *(rb(va::G_FRONTEND_HWND) as *const HWND);
|
| 349 |
let mut rect = RECT { left: 0, top: 0, right: 0, bottom: 0 };
|
| 350 |
GetClientRect(hwnd_val, &mut rect);
|
| 351 |
-
|
| 352 |
-
let map_fn_ptr = *(rb(0x0061_A588) as *const usize);
|
| 353 |
let map_fn: unsafe extern "stdcall" fn(HWND, HWND, *mut RECT, u32) -> i32 =
|
| 354 |
core::mem::transmute(map_fn_ptr);
|
| 355 |
map_fn(hwnd_val, core::ptr::null_mut(), &mut rect, 2);
|
|
@@ -358,53 +363,54 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 358 |
}
|
| 359 |
|
| 360 |
// ββ DDKeyboard (inline construction) ββββββββββββββββββββββββββββββββββ
|
| 361 |
-
let
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
core::ptr::write_bytes(
|
| 366 |
-
core::ptr::write_bytes(
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
(*session).keyboard =
|
| 370 |
|
| 371 |
// ββ Palette (inline construction) βββββββββββββββββββββββββββββββββββββ
|
| 372 |
-
let
|
| 373 |
-
|
| 374 |
-
*(
|
| 375 |
-
(*session).palette =
|
| 376 |
|
| 377 |
// ββ DSSound βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 378 |
(*session).sound = core::ptr::null_mut();
|
| 379 |
{
|
| 380 |
-
let
|
| 381 |
-
call_dssound_ctor(
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
// snd_mem+0xBBC = 1 only if all 3 COM init steps succeed; WA code checks this flag.
|
| 385 |
-
(*session).sound = snd_mem;
|
| 386 |
|
| 387 |
let ds_create: unsafe extern "stdcall" fn(*const u8, *mut *mut u8, *const u8) -> i32 =
|
| 388 |
core::mem::transmute(rb(va::DIRECTSOUND_CREATE) as usize);
|
| 389 |
-
let hr = ds_create(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
|
| 391 |
if hr == 0 {
|
| 392 |
-
DSSOUND_INIT_EAX =
|
| 393 |
let hr2 = call_dssound_init_buffers(
|
| 394 |
-
|
| 395 |
-
|
| 396 |
);
|
| 397 |
if hr2 == 0 {
|
| 398 |
-
//
|
| 399 |
-
|
| 400 |
-
let pds = *(snd_mem.add(0x10) as *const *const *const usize);
|
| 401 |
let vtbl = *pds;
|
| 402 |
let play: unsafe extern "stdcall" fn(
|
| 403 |
*const *const usize, u32, u32, u32,
|
| 404 |
) -> i32 = core::mem::transmute(*vtbl.add(0x30 / 4));
|
| 405 |
let hr3 = play(pds, 0, 0, 1);
|
| 406 |
if hr3 == 0 {
|
| 407 |
-
|
| 408 |
}
|
| 409 |
}
|
| 410 |
}
|
|
@@ -413,24 +419,26 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 413 |
// ββ Streaming audio βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 414 |
(*session).streaming_audio = core::ptr::null_mut();
|
| 415 |
if !(*session).sound.is_null() {
|
| 416 |
-
|
| 417 |
-
if speech_flag != 0 {
|
| 418 |
let stream_mem = WABox::<u8>::alloc(0x354, 0x334).leak();
|
| 419 |
-
let
|
| 420 |
-
let ids =
|
| 421 |
-
call_streaming_audio_ctor(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
(*session).streaming_audio = stream_mem;
|
| 423 |
}
|
| 424 |
}
|
| 425 |
} else {
|
| 426 |
// ββ Headless / stats mode βββββββββββββββββββββββββββββββββββββββββββββ
|
| 427 |
-
let
|
| 428 |
let gamestats_ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
| 429 |
core::mem::transmute(rb(va::GAMESTATS_CTOR) as usize);
|
| 430 |
-
gamestats_ctor(
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
(*session).display_gfx = stats_ptr;
|
| 434 |
(*session).keyboard = core::ptr::null_mut();
|
| 435 |
(*session).sound = core::ptr::null_mut();
|
| 436 |
(*session).palette = core::ptr::null_mut();
|
|
@@ -438,37 +446,33 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 438 |
}
|
| 439 |
|
| 440 |
// ββ Windowed/fullscreen flag β session+0x28, and session+0x24 = 1 ββββββββββ
|
| 441 |
-
let fullscreen_word =
|
| 442 |
*((session as *mut u8).add(0x28) as *mut u32) = (fullscreen_word != 0) as u32;
|
| 443 |
-
// Original always sets session+0x24 = 1 unconditionally (both normal + headless).
|
| 444 |
*((session as *mut u8).add(0x24) as *mut u32) = 1;
|
| 445 |
|
| 446 |
// ββ DDGameWrapper (ALWAYS) ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 447 |
-
// The 7 explicit args mirror the session fields:
|
| 448 |
-
// display_gfx (DisplayGfx*), sound (DSSound*), keyboard (DDKeyboard* β called "gfx"),
|
| 449 |
-
// palette, streaming_audio ("music"), input_ctrl ("network").
|
| 450 |
let wrapper = game_session::construct_ddgame_wrapper(
|
| 451 |
-
game_info,
|
| 452 |
WABox::<DDGameWrapper>::alloc(0x6F10, 0x6EF0).leak(),
|
| 453 |
(*session).display_gfx as *mut DDDisplay,
|
| 454 |
-
(*session).sound
|
| 455 |
-
(*session).keyboard,
|
| 456 |
-
(*session).palette
|
| 457 |
(*session).streaming_audio,
|
| 458 |
(*session).input_ctrl,
|
| 459 |
);
|
| 460 |
-
(*session).ddgame_wrapper = wrapper
|
| 461 |
|
| 462 |
// ββ Palette vtable[4/3/2] + keyboard poll (normal mode only) βββββββββββββ
|
| 463 |
if !headless {
|
| 464 |
let pal = (*session).palette;
|
| 465 |
if !pal.is_null() {
|
| 466 |
let vtbl = *(pal as *const *const usize);
|
| 467 |
-
let vt4: unsafe extern "thiscall" fn(*mut
|
| 468 |
core::mem::transmute(*vtbl.add(4));
|
| 469 |
-
let vt3: unsafe extern "thiscall" fn(*mut
|
| 470 |
core::mem::transmute(*vtbl.add(3));
|
| 471 |
-
let vt2: unsafe extern "thiscall" fn(*mut
|
| 472 |
core::mem::transmute(*vtbl.add(2));
|
| 473 |
vt4(pal);
|
| 474 |
vt3(pal);
|
|
@@ -477,7 +481,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 477 |
|
| 478 |
let kb = (*session).keyboard;
|
| 479 |
if !kb.is_null() {
|
| 480 |
-
let kb_poll: unsafe extern "stdcall" fn(*mut
|
| 481 |
core::mem::transmute(rb(va::DDKEYBOARD_POLL_KEYBOARD_STATE) as usize);
|
| 482 |
kb_poll(kb);
|
| 483 |
}
|
|
|
|
| 5 |
//!
|
| 6 |
//! ## Calling convention
|
| 7 |
//!
|
| 8 |
+
//! `__thiscall`: ECX = `*mut GameInfo` (β₯0xF91C bytes), 3 stack params
|
| 9 |
//! (hwnd, param3, param4), `RET 0xC`. Returns 1 on success, 0 on failure.
|
| 10 |
//!
|
| 11 |
//! The naked entry trampoline captures ECX, pops the return address β `SAVED_RET`,
|
|
|
|
| 21 |
//! IF param4 != 0:
|
| 22 |
//! input ctrl (0x1800 bytes, inline vtable, FUN_0058C0D0 usercall ESI=this) β session+0xB8
|
| 23 |
//!
|
| 24 |
+
//! IF GameInfo.headless_mode == 0 (normal mode):
|
| 25 |
//! DisplayGfx (0x24E28, stdcall ctor) β session+0xAC
|
| 26 |
//! DDDisplay::Init retry loop (configured β 1024Γ768 β 800Γ600 β 640Γ480)
|
| 27 |
//! screen center / cursor setup
|
| 28 |
//! DDKeyboard (0x33C, inline) β session+0xA4
|
| 29 |
//! Palette (0x28, inline) β session+0xB0
|
| 30 |
//! DSSound (0xBE0, usercall ctor + DirectSoundCreate + coop level) β session+0xA8
|
| 31 |
+
//! IF GameInfo.speech_enabled != 0 AND DSSound OK: streaming audio β session+0xB4
|
| 32 |
//!
|
| 33 |
//! ELSE (headless):
|
| 34 |
//! GameStats (0x3560, stdcall ctor + vtable override) β session+0xAC
|
| 35 |
//! session+0xA4/0xA8/0xB0/0xB4 = null
|
| 36 |
//!
|
| 37 |
//! ALWAYS:
|
| 38 |
+
//! session+0x28 = (GameInfo.home_lock != 0) ? 1 : 0
|
| 39 |
//! DDGameWrapper (0x6F10) β session+0xA0 [via game_session::construct_ddgame_wrapper]
|
| 40 |
//! Palette vtable[4/3/2] calls + DDKeyboard poll (normal mode only)
|
| 41 |
//! DDNetGameWrapper (0x2C, stdcall ctor) β session+0xC0
|
|
|
|
| 43 |
|
| 44 |
use openwa_core::address::va;
|
| 45 |
use openwa_core::rebase::rb;
|
| 46 |
+
use openwa_core::game_info::GameInfo;
|
| 47 |
use openwa_core::game_session::GameSession;
|
| 48 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 49 |
+
use openwa_core::ddkeyboard::DDKeyboard;
|
| 50 |
use openwa_core::dddisplay::DDDisplay;
|
| 51 |
use openwa_core::dssound::DSSound;
|
| 52 |
use openwa_core::palette::Palette;
|
| 53 |
+
use openwa_core::input_ctrl::InputCtrl;
|
| 54 |
+
use openwa_core::game_timer::GameTimer;
|
| 55 |
+
use openwa_core::game_stats::GameStats;
|
| 56 |
use openwa_core::wa_alloc::WABox;
|
| 57 |
use crate::hook;
|
| 58 |
use crate::log_line;
|
|
|
|
| 93 |
/// Timer constructor: `usercall(ESI=timer_ptr, EAX=d778_val)`, plain RET.
|
| 94 |
/// Returns whatever EAX holds after the call.
|
| 95 |
#[unsafe(naked)]
|
| 96 |
+
unsafe extern "cdecl" fn call_timer_ctor(_timer_ptr: *mut GameTimer, _d778_val: u32) -> u32 {
|
| 97 |
core::arch::naked_asm!(
|
| 98 |
// [esp+0]=bridge_ret, [esp+4]=timer_ptr, [esp+8]=d778_val
|
| 99 |
"pushl %esi",
|
|
|
|
| 113 |
/// IMPORTANT: FUN_00573D50 clobbers ECX (MOV ECX, 0x1F4 for REP STOSD).
|
| 114 |
/// Do NOT use ECX to save bridge_ret β read it from the stack instead.
|
| 115 |
#[unsafe(naked)]
|
| 116 |
+
unsafe extern "cdecl" fn call_dssound_ctor(_dssound: *mut DSSound) {
|
| 117 |
core::arch::naked_asm!(
|
| 118 |
// [esp+0]=bridge_ret, [esp+4]=dssound
|
| 119 |
"movl 4(%esp), %eax", // EAX = dssound (bridge_ret stays on stack untouched)
|
|
|
|
| 134 |
/// Caller sets `DSSOUND_INIT_EAX = dssound` before calling.
|
| 135 |
#[unsafe(naked)]
|
| 136 |
unsafe extern "stdcall" fn call_dssound_init_buffers(
|
| 137 |
+
_out_primary_buffer: *mut *mut u8,
|
| 138 |
+
_out_primary_caps: *mut u32,
|
| 139 |
) -> u32 {
|
| 140 |
core::arch::naked_asm!(
|
| 141 |
+
// Entry: ESP=E, [E+0]=bridge_ret, [E+4]=out_primary_buffer, [E+8]=out_primary_caps
|
| 142 |
"movl {eax_val}, %eax", // EAX = dssound
|
| 143 |
"movl (%esp), %ecx", // ECX = bridge_ret (temp)
|
| 144 |
"movl %ecx, {saved_ret}", // save to static (ECX clobbered by FUN_00573E50)
|
| 145 |
+
"movl 4(%esp), %ecx", // ECX = out_primary_buffer
|
| 146 |
+
"movl 8(%esp), %edx", // EDX = out_primary_caps
|
| 147 |
"addl $0xC, %esp", // ESP = E+12 (discard bridge_ret + 2 args)
|
| 148 |
+
"pushl %edx", // ESP = E+8, [E+8] = out_primary_caps
|
| 149 |
+
"pushl %ecx", // ESP = E+4, [E+4] = out_primary_buffer
|
| 150 |
"calll *({fn})", // ESP = E, [E] = cont
|
| 151 |
// FUN_00573E50 RET 0x8: pops cont β E+4, +8 β E+12
|
| 152 |
// At cont: ESP = E+12; EAX = return value
|
|
|
|
| 247 |
// βββ Implementation ββββββββββββββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½ββββββ
|
| 248 |
|
| 249 |
unsafe extern "cdecl" fn impl_init_hardware(
|
| 250 |
+
game_info: *mut GameInfo,
|
| 251 |
hwnd: u32,
|
| 252 |
param3: u32,
|
| 253 |
param4: u32,
|
| 254 |
) -> u32 {
|
| 255 |
let _ = log_line("[hardware_init] GameEngine::InitHardware");
|
| 256 |
let session = *(rb(va::G_GAME_SESSION) as *const *mut GameSession);
|
| 257 |
+
let gi = &mut *game_info;
|
| 258 |
+
let d778_val = gi.crosshair_overflow_threshold as u32;
|
| 259 |
|
| 260 |
// ββ Input controller (if param4 != 0) ββββββββββββββββββββββββββββββββββββ
|
| 261 |
if param4 == 0 {
|
| 262 |
(*session).input_ctrl = core::ptr::null_mut();
|
| 263 |
} else {
|
| 264 |
+
let ctrl = WABox::<InputCtrl>::alloc(0x1800, 0x17E0).leak();
|
| 265 |
+
(*ctrl)._field_d74 = 0x3F9;
|
| 266 |
+
(*ctrl).vtable = rb(va::INPUT_CTRL_VTABLE) as *mut u8;
|
| 267 |
+
(*session).input_ctrl = ctrl as *mut u8;
|
| 268 |
+
|
| 269 |
+
INPUT_CTRL_ESI = ctrl as u32;
|
| 270 |
+
let ok = call_input_ctrl_init(
|
| 271 |
+
(game_info as *mut u8).add(4),
|
| 272 |
+
param3,
|
| 273 |
+
param4,
|
| 274 |
+
d778_val,
|
| 275 |
+
);
|
| 276 |
if ok == 0 {
|
| 277 |
+
let vtbl = *(ctrl as *const *const usize);
|
| 278 |
let dtor: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 279 |
core::mem::transmute(*vtbl);
|
| 280 |
+
dtor(ctrl as *mut u8, 1);
|
| 281 |
(*session).input_ctrl = core::ptr::null_mut();
|
| 282 |
return 0;
|
| 283 |
}
|
| 284 |
}
|
| 285 |
|
| 286 |
// ββ Timer object (ALWAYS) βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 287 |
+
let timer = WABox::<GameTimer>::alloc(0x30, 0x30).leak();
|
| 288 |
+
call_timer_ctor(timer, d778_val);
|
| 289 |
+
(*session).timer_obj = timer as *mut u8;
|
| 290 |
|
| 291 |
+
let headless = gi.headless_mode != 0;
|
| 292 |
|
| 293 |
if !headless {
|
| 294 |
// ββ DisplayGfx βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 298 |
(*session).display_gfx = display_gfx;
|
| 299 |
|
| 300 |
// ββ DDDisplay::Init retry loop ββββββββββββββββββββββββββββββββββββββββ
|
| 301 |
+
let flags = gi.display_flags;
|
| 302 |
+
let w0 = gi.display_width;
|
| 303 |
+
let h0 = gi.display_height;
|
|
|
|
|
|
|
| 304 |
|
| 305 |
DDISPLAY_INIT_ECX = h0;
|
| 306 |
let mut init_ok = call_ddisplay_init(display_gfx, hwnd, w0, flags) != 0;
|
|
|
|
| 312 |
(0x280, 0x1E0), // 640Γ480
|
| 313 |
];
|
| 314 |
for &(w, h) in &fallbacks {
|
| 315 |
+
gi.display_width = w;
|
| 316 |
+
gi.display_height = h;
|
| 317 |
DDISPLAY_INIT_ECX = h;
|
| 318 |
if call_ddisplay_init(display_gfx, hwnd, w, flags) != 0 {
|
| 319 |
init_ok = true;
|
|
|
|
| 337 |
let h = GetSystemMetrics(SM_CYSCREEN);
|
| 338 |
(w / 2, h / 2)
|
| 339 |
} else {
|
| 340 |
+
(gi.display_width as i32 / 2, gi.display_height as i32 / 2)
|
|
|
|
|
|
|
| 341 |
};
|
| 342 |
|
| 343 |
(*session).screen_center_x = cx;
|
|
|
|
| 354 |
let hwnd_val: HWND = *(rb(va::G_FRONTEND_HWND) as *const HWND);
|
| 355 |
let mut rect = RECT { left: 0, top: 0, right: 0, bottom: 0 };
|
| 356 |
GetClientRect(hwnd_val, &mut rect);
|
| 357 |
+
let map_fn_ptr = *(rb(va::IAT_MAP_WINDOW_POINTS) as *const usize);
|
|
|
|
| 358 |
let map_fn: unsafe extern "stdcall" fn(HWND, HWND, *mut RECT, u32) -> i32 =
|
| 359 |
core::mem::transmute(map_fn_ptr);
|
| 360 |
map_fn(hwnd_val, core::ptr::null_mut(), &mut rect, 2);
|
|
|
|
| 363 |
}
|
| 364 |
|
| 365 |
// ββ DDKeyboard (inline construction) ββββββββββββββββββββββββββββββββββ
|
| 366 |
+
let kb = WABox::<DDKeyboard>::alloc(0x33C, 0x31C).leak();
|
| 367 |
+
(*kb).vtable = rb(va::DDKEYBOARD_VTABLE) as *mut u8;
|
| 368 |
+
(*kb).game_info_input_ptr = &raw mut gi.input_state_f918 as u32;
|
| 369 |
+
(*kb)._field_008 = 1;
|
| 370 |
+
core::ptr::write_bytes((*kb).key_state.as_mut_ptr(), 0, 0x100);
|
| 371 |
+
core::ptr::write_bytes((*kb).prev_state.as_mut_ptr(), 0, 0x100);
|
| 372 |
+
(*kb)._field_014 = 0;
|
| 373 |
+
(*kb)._field_018 = 0;
|
| 374 |
+
(*session).keyboard = kb;
|
| 375 |
|
| 376 |
// ββ Palette (inline construction) βββββββββββββββββββββββββββββββββββββ
|
| 377 |
+
let pal = WABox::<Palette>::alloc(0x28, 0).leak();
|
| 378 |
+
(*pal).vtable = rb(va::PALETTE_VTABLE_MAYBE) as *mut u8;
|
| 379 |
+
*((pal as *mut u8).add(4) as *mut u32) = 0xFFFF_FFFF;
|
| 380 |
+
(*session).palette = pal;
|
| 381 |
|
| 382 |
// ββ DSSound βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 383 |
(*session).sound = core::ptr::null_mut();
|
| 384 |
{
|
| 385 |
+
let snd = WABox::<DSSound>::alloc(0xBE0, 0xBC0).leak();
|
| 386 |
+
call_dssound_ctor(snd);
|
| 387 |
+
(*snd).hwnd = hwnd;
|
| 388 |
+
(*session).sound = snd;
|
|
|
|
|
|
|
| 389 |
|
| 390 |
let ds_create: unsafe extern "stdcall" fn(*const u8, *mut *mut u8, *const u8) -> i32 =
|
| 391 |
core::mem::transmute(rb(va::DIRECTSOUND_CREATE) as usize);
|
| 392 |
+
let hr = ds_create(
|
| 393 |
+
core::ptr::null(),
|
| 394 |
+
&mut (*snd).direct_sound,
|
| 395 |
+
core::ptr::null(),
|
| 396 |
+
);
|
| 397 |
|
| 398 |
if hr == 0 {
|
| 399 |
+
DSSOUND_INIT_EAX = snd as u32;
|
| 400 |
let hr2 = call_dssound_init_buffers(
|
| 401 |
+
&mut (*snd).primary_buffer,
|
| 402 |
+
&mut (*snd).primary_buffer_caps,
|
| 403 |
);
|
| 404 |
if hr2 == 0 {
|
| 405 |
+
// IDirectSoundBuffer::Play(this, 0, 0, DSBPLAY_LOOPING=1)
|
| 406 |
+
let pds = (*snd).primary_buffer as *const *const usize;
|
|
|
|
| 407 |
let vtbl = *pds;
|
| 408 |
let play: unsafe extern "stdcall" fn(
|
| 409 |
*const *const usize, u32, u32, u32,
|
| 410 |
) -> i32 = core::mem::transmute(*vtbl.add(0x30 / 4));
|
| 411 |
let hr3 = play(pds, 0, 0, 1);
|
| 412 |
if hr3 == 0 {
|
| 413 |
+
(*snd).init_success = 1;
|
| 414 |
}
|
| 415 |
}
|
| 416 |
}
|
|
|
|
| 419 |
// ββ Streaming audio βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 420 |
(*session).streaming_audio = core::ptr::null_mut();
|
| 421 |
if !(*session).sound.is_null() {
|
| 422 |
+
if gi.speech_enabled != 0 {
|
|
|
|
| 423 |
let stream_mem = WABox::<u8>::alloc(0x354, 0x334).leak();
|
| 424 |
+
let snd = (*session).sound;
|
| 425 |
+
let ids = (*snd).direct_sound;
|
| 426 |
+
call_streaming_audio_ctor(
|
| 427 |
+
stream_mem,
|
| 428 |
+
ids,
|
| 429 |
+
gi.streaming_audio_config.as_mut_ptr(),
|
| 430 |
+
);
|
| 431 |
(*session).streaming_audio = stream_mem;
|
| 432 |
}
|
| 433 |
}
|
| 434 |
} else {
|
| 435 |
// ββ Headless / stats mode βββββββββββββββββββββββββββββββββββββββββββββ
|
| 436 |
+
let stats = WABox::<GameStats>::alloc(0x3560, 0x3560).leak();
|
| 437 |
let gamestats_ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
| 438 |
core::mem::transmute(rb(va::GAMESTATS_CTOR) as usize);
|
| 439 |
+
gamestats_ctor(stats as *mut u8);
|
| 440 |
+
(*stats).vtable = rb(va::GAMESTATS_VTABLE) as *mut u8;
|
| 441 |
+
(*session).display_gfx = stats as *mut u8;
|
|
|
|
| 442 |
(*session).keyboard = core::ptr::null_mut();
|
| 443 |
(*session).sound = core::ptr::null_mut();
|
| 444 |
(*session).palette = core::ptr::null_mut();
|
|
|
|
| 446 |
}
|
| 447 |
|
| 448 |
// ββ Windowed/fullscreen flag β session+0x28, and session+0x24 = 1 ββββββββββ
|
| 449 |
+
let fullscreen_word = gi.home_lock;
|
| 450 |
*((session as *mut u8).add(0x28) as *mut u32) = (fullscreen_word != 0) as u32;
|
|
|
|
| 451 |
*((session as *mut u8).add(0x24) as *mut u32) = 1;
|
| 452 |
|
| 453 |
// ββ DDGameWrapper (ALWAYS) ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
| 454 |
let wrapper = game_session::construct_ddgame_wrapper(
|
| 455 |
+
game_info as *mut u8,
|
| 456 |
WABox::<DDGameWrapper>::alloc(0x6F10, 0x6EF0).leak(),
|
| 457 |
(*session).display_gfx as *mut DDDisplay,
|
| 458 |
+
(*session).sound,
|
| 459 |
+
(*session).keyboard as *mut u8,
|
| 460 |
+
(*session).palette,
|
| 461 |
(*session).streaming_audio,
|
| 462 |
(*session).input_ctrl,
|
| 463 |
);
|
| 464 |
+
(*session).ddgame_wrapper = wrapper;
|
| 465 |
|
| 466 |
// ββ Palette vtable[4/3/2] + keyboard poll (normal mode only) βββββββββββββ
|
| 467 |
if !headless {
|
| 468 |
let pal = (*session).palette;
|
| 469 |
if !pal.is_null() {
|
| 470 |
let vtbl = *(pal as *const *const usize);
|
| 471 |
+
let vt4: unsafe extern "thiscall" fn(*mut Palette) =
|
| 472 |
core::mem::transmute(*vtbl.add(4));
|
| 473 |
+
let vt3: unsafe extern "thiscall" fn(*mut Palette) =
|
| 474 |
core::mem::transmute(*vtbl.add(3));
|
| 475 |
+
let vt2: unsafe extern "thiscall" fn(*mut Palette, u32) =
|
| 476 |
core::mem::transmute(*vtbl.add(2));
|
| 477 |
vt4(pal);
|
| 478 |
vt3(pal);
|
|
|
|
| 481 |
|
| 482 |
let kb = (*session).keyboard;
|
| 483 |
if !kb.is_null() {
|
| 484 |
+
let kb_poll: unsafe extern "stdcall" fn(*mut DDKeyboard) =
|
| 485 |
core::mem::transmute(rb(va::DDKEYBOARD_POLL_KEYBOARD_STATE) as usize);
|
| 486 |
kb_poll(kb);
|
| 487 |
}
|