feat: add GameInfo__LoadOptions hook (registry reads + global copies)
Browse filesFull Rust replacement for GameInfo__LoadOptions (0x460AC0). Reads 12
registry values from HKCU Options section and copies ~20 global data
blocks into the GameInfo struct. Adds 17 global address constants.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
crates/openwa-types/src/address.rs
CHANGED
|
@@ -353,6 +353,41 @@ pub mod va {
|
|
| 353 |
/// +0xA0 = DDGameWrapper*, +0xAC = DDDisplay*, +0xA8 = DSSound*, etc.
|
| 354 |
pub const G_GAME_SESSION: u32 = 0x007A_0884;
|
| 355 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
// === Game context (DDGame struct offsets) ===
|
| 357 |
// These are offsets from the DDGame base pointer, not absolute addresses.
|
| 358 |
// DDGame pointer is obtained from hookConstructDDGameWrapper param.
|
|
|
|
| 353 |
/// +0xA0 = DDGameWrapper*, +0xAC = DDDisplay*, +0xA8 = DSSound*, etc.
|
| 354 |
pub const G_GAME_SESSION: u32 = 0x007A_0884;
|
| 355 |
|
| 356 |
+
// === Configuration globals (for GameInfo__LoadOptions) ===
|
| 357 |
+
|
| 358 |
+
/// Base directory string (null-terminated)
|
| 359 |
+
pub const G_BASE_DIR: u32 = 0x0088_E282;
|
| 360 |
+
/// 64-byte data block copied into GameInfo+0xF485
|
| 361 |
+
pub const G_GAMEINFO_BLOCK_F485: u32 = 0x0088_DFF3;
|
| 362 |
+
/// "data\land.dat" string constant (14 bytes)
|
| 363 |
+
pub const G_LAND_DAT_STRING: u32 = 0x0064_DA58;
|
| 364 |
+
/// Unknown byte read into GameInfo+0xF3A0
|
| 365 |
+
pub const G_CONFIG_BYTE_F3A0: u32 = 0x007C_0D38;
|
| 366 |
+
/// 5 DWORDs: GameInfo offsets +0xF3B4..+0xF3D0
|
| 367 |
+
pub const G_CONFIG_DWORDS_F3B4: u32 = 0x0088_E39C;
|
| 368 |
+
/// Guard flag for conditional config copies
|
| 369 |
+
pub const G_CONFIG_GUARD: u32 = 0x0088_C374;
|
| 370 |
+
/// 4 DWORDs (conditional): GameInfo offsets +0xF3F4..+0xF400
|
| 371 |
+
pub const G_CONFIG_DWORDS_F3F4: u32 = 0x0088_E3B8;
|
| 372 |
+
/// DWORD → GameInfo+0xDAE8
|
| 373 |
+
pub const G_CONFIG_DWORD_DAE8: u32 = 0x0088_E390;
|
| 374 |
+
/// 2 DWORDs → GameInfo+0xF3D4, +0xF3D8
|
| 375 |
+
pub const G_CONFIG_DWORDS_F3D4: u32 = 0x0088_E3B0;
|
| 376 |
+
/// 3 DWORDs → GameInfo+0xF3C4..+0xF3CC
|
| 377 |
+
pub const G_CONFIG_DWORDS_F3C4: u32 = 0x0088_E400;
|
| 378 |
+
/// DWORD → GameInfo+0xF3E4
|
| 379 |
+
pub const G_CONFIG_DWORD_F3E4: u32 = 0x0088_E44C;
|
| 380 |
+
/// Streams directory path buffer
|
| 381 |
+
pub const G_STREAMS_DIR: u32 = 0x0088_AE18;
|
| 382 |
+
/// Random stream indices (16 entries)
|
| 383 |
+
pub const G_STREAM_INDICES: u32 = 0x0088_AE9C;
|
| 384 |
+
/// Stream index end sentinel
|
| 385 |
+
pub const G_STREAM_INDICES_END: u32 = 0x0088_AEDC;
|
| 386 |
+
/// DAT_0088E394 flag for stream volume
|
| 387 |
+
pub const G_STREAM_FLAG: u32 = 0x0088_E394;
|
| 388 |
+
/// Stream volume byte
|
| 389 |
+
pub const G_STREAM_VOLUME: u32 = 0x0088_AEDD;
|
| 390 |
+
|
| 391 |
// === Game context (DDGame struct offsets) ===
|
| 392 |
// These are offsets from the DDGame base pointer, not absolute addresses.
|
| 393 |
// DDGame pointer is obtained from hookConstructDDGameWrapper param.
|
crates/openwa-wormkit/src/replacements/config.rs
CHANGED
|
@@ -6,10 +6,10 @@
|
|
| 6 |
//! - Theme__Save (0x44BBC0): theme file write
|
| 7 |
//! - Registry__DeleteKeyRecursive (0x4E4D10): recursive registry deletion
|
| 8 |
//! - Registry__CleanAll (0x4C90D0): full registry cleanup
|
|
|
|
| 9 |
|
| 10 |
use openwa_types::address::va;
|
| 11 |
use crate::log_line;
|
| 12 |
-
#[allow(unused_imports)]
|
| 13 |
use openwa_lib::rebase::rb;
|
| 14 |
|
| 15 |
const THEME_PATH: &str = "data\\current.thm";
|
|
@@ -124,6 +124,168 @@ unsafe extern "stdcall" fn hook_registry_clean_all(struct_ptr: u32) {
|
|
| 124 |
let _ = log_line("[Config] CleanAll completed");
|
| 125 |
}
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
// ============================================================
|
| 128 |
// Hook installation
|
| 129 |
// ============================================================
|
|
@@ -159,6 +321,12 @@ pub fn install() -> Result<(), String> {
|
|
| 159 |
va::REGISTRY_CLEAN_ALL,
|
| 160 |
hook_registry_clean_all as *const (),
|
| 161 |
)?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
}
|
| 163 |
|
| 164 |
Ok(())
|
|
|
|
| 6 |
//! - Theme__Save (0x44BBC0): theme file write
|
| 7 |
//! - Registry__DeleteKeyRecursive (0x4E4D10): recursive registry deletion
|
| 8 |
//! - Registry__CleanAll (0x4C90D0): full registry cleanup
|
| 9 |
+
//! - GameInfo__LoadOptions (0x460AC0): game options from registry
|
| 10 |
|
| 11 |
use openwa_types::address::va;
|
| 12 |
use crate::log_line;
|
|
|
|
| 13 |
use openwa_lib::rebase::rb;
|
| 14 |
|
| 15 |
const THEME_PATH: &str = "data\\current.thm";
|
|
|
|
| 124 |
let _ = log_line("[Config] CleanAll completed");
|
| 125 |
}
|
| 126 |
|
| 127 |
+
// ============================================================
|
| 128 |
+
// GameInfo__LoadOptions replacement (0x460AC0)
|
| 129 |
+
// ============================================================
|
| 130 |
+
|
| 131 |
+
/// Rust replacement for GameInfo__LoadOptions.
|
| 132 |
+
/// stdcall(game_info: u32)
|
| 133 |
+
///
|
| 134 |
+
/// Reads game options from the Windows registry and copies various globals
|
| 135 |
+
/// into the GameInfo struct at known offsets.
|
| 136 |
+
unsafe extern "stdcall" fn hook_load_options(gi: u32) {
|
| 137 |
+
use openwa_lib::wa::registry::read_profile_int;
|
| 138 |
+
|
| 139 |
+
let _ = log_line("[Config] LoadOptions: loading game options from registry");
|
| 140 |
+
|
| 141 |
+
// Format speech path: "%s\\user\\speech"
|
| 142 |
+
let base_dir = rb(va::G_BASE_DIR) as *const u8;
|
| 143 |
+
let speech_dest = (gi + 0xF404) as *mut u8;
|
| 144 |
+
let base_str = std::ffi::CStr::from_ptr(base_dir as *const i8);
|
| 145 |
+
let speech_path = format!("{}\\user\\speech\0", base_str.to_string_lossy());
|
| 146 |
+
core::ptr::copy_nonoverlapping(
|
| 147 |
+
speech_path.as_ptr(),
|
| 148 |
+
speech_dest,
|
| 149 |
+
speech_path.len(),
|
| 150 |
+
);
|
| 151 |
+
|
| 152 |
+
// Copy 64 bytes from global 0x88DFF3 → GameInfo+0xF485
|
| 153 |
+
core::ptr::copy_nonoverlapping(
|
| 154 |
+
rb(va::G_GAMEINFO_BLOCK_F485) as *const u8,
|
| 155 |
+
(gi + 0xF485) as *mut u8,
|
| 156 |
+
64,
|
| 157 |
+
);
|
| 158 |
+
|
| 159 |
+
// Format streams directory and randomize stream indices
|
| 160 |
+
let streams_dest = rb(va::G_STREAMS_DIR) as *mut u8;
|
| 161 |
+
let streams_path = format!("{}\\streams\0", base_str.to_string_lossy());
|
| 162 |
+
core::ptr::copy_nonoverlapping(
|
| 163 |
+
streams_path.as_ptr(),
|
| 164 |
+
streams_dest,
|
| 165 |
+
streams_path.len(),
|
| 166 |
+
);
|
| 167 |
+
|
| 168 |
+
// Randomize stream indices (16 entries, each rand() % 11 + 1)
|
| 169 |
+
let indices = rb(va::G_STREAM_INDICES) as *mut u32;
|
| 170 |
+
let indices_end = rb(va::G_STREAM_INDICES_END) as usize;
|
| 171 |
+
let mut ptr = indices;
|
| 172 |
+
while (ptr as usize) < indices_end {
|
| 173 |
+
extern "cdecl" { fn rand() -> i32; }
|
| 174 |
+
*ptr = (rand() % 11 + 1) as u32;
|
| 175 |
+
ptr = ptr.add(1);
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
// Stream volume: 0x10 if flag set, else 0
|
| 179 |
+
let stream_vol_addr = rb(va::G_STREAM_INDICES_END) as *mut u8;
|
| 180 |
+
*stream_vol_addr = if *(rb(va::G_STREAM_FLAG) as *const u32) != 0 { 0x10 } else { 0 };
|
| 181 |
+
// Secondary volume byte
|
| 182 |
+
*(rb(va::G_STREAM_VOLUME) as *mut u8) = 0x4B;
|
| 183 |
+
|
| 184 |
+
// Copy "data\land.dat" string (14 bytes) → GameInfo+0xDAEC
|
| 185 |
+
core::ptr::copy_nonoverlapping(
|
| 186 |
+
rb(va::G_LAND_DAT_STRING) as *const u8,
|
| 187 |
+
(gi + 0xDAEC) as *mut u8,
|
| 188 |
+
14,
|
| 189 |
+
);
|
| 190 |
+
|
| 191 |
+
// Copy byte from global → GameInfo+0xF3A0
|
| 192 |
+
*((gi + 0xF3A0) as *mut u8) = *(rb(va::G_CONFIG_BYTE_F3A0) as *const u8);
|
| 193 |
+
|
| 194 |
+
// Read registry values from "Options" section
|
| 195 |
+
let detail = read_profile_int("Options", "DetailLevel", 5);
|
| 196 |
+
*((gi + 0xF3A1) as *mut u8) = detail as u8;
|
| 197 |
+
|
| 198 |
+
// Zero 2 bytes at +0xF3F0
|
| 199 |
+
*((gi + 0xF3F0) as *mut u16) = 0;
|
| 200 |
+
|
| 201 |
+
// Copy 5 DWORDs from globals → GameInfo+0xF3B4..+0xF3D0
|
| 202 |
+
let src = rb(va::G_CONFIG_DWORDS_F3B4) as *const u32;
|
| 203 |
+
for i in 0u32..5 {
|
| 204 |
+
let offset = 0xF3B4 + i * 4;
|
| 205 |
+
*((gi + offset) as *mut u32) = *src.add(i as usize);
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
// Conditional copy: 4 DWORDs if guard == 0
|
| 209 |
+
if *(rb(va::G_CONFIG_GUARD) as *const u32) == 0 {
|
| 210 |
+
let src = rb(va::G_CONFIG_DWORDS_F3F4) as *const u32;
|
| 211 |
+
for i in 0u32..4 {
|
| 212 |
+
let offset = 0xF3F4 + i * 4;
|
| 213 |
+
*((gi + offset) as *mut u32) = *src.add(i as usize);
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
// Single DWORDs from globals
|
| 218 |
+
*((gi + 0xDAE8) as *mut u32) = *(rb(va::G_CONFIG_DWORD_DAE8) as *const u32);
|
| 219 |
+
|
| 220 |
+
let src_d4 = rb(va::G_CONFIG_DWORDS_F3D4) as *const u32;
|
| 221 |
+
*((gi + 0xF3D4) as *mut u32) = *src_d4;
|
| 222 |
+
*((gi + 0xF3D8) as *mut u32) = *src_d4.add(1);
|
| 223 |
+
|
| 224 |
+
// EnergyBar
|
| 225 |
+
let energy = read_profile_int("Options", "EnergyBar", 1);
|
| 226 |
+
*((gi + 0xF3A2) as *mut u8) = energy as u8;
|
| 227 |
+
|
| 228 |
+
// 3 DWORDs from globals → +0xF3C4..+0xF3CC
|
| 229 |
+
let src_c4 = rb(va::G_CONFIG_DWORDS_F3C4) as *const u32;
|
| 230 |
+
for i in 0u32..3 {
|
| 231 |
+
let offset = 0xF3C4 + i * 4;
|
| 232 |
+
*((gi + offset) as *mut u32) = *src_c4.add(i as usize);
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
// Remaining registry values
|
| 236 |
+
let info_trans = read_profile_int("Options", "InfoTransparency", 0);
|
| 237 |
+
*((gi + 0xF3A3) as *mut u8) = info_trans as u8;
|
| 238 |
+
|
| 239 |
+
let info_spy = read_profile_int("Options", "InfoSpy", 1);
|
| 240 |
+
*((gi + 0xF3A4) as *mut u8) = if info_spy != 0 { 1 } else { 0 };
|
| 241 |
+
|
| 242 |
+
let chat_pinned = read_profile_int("Options", "ChatPinned", 0);
|
| 243 |
+
*((gi + 0xF3A5) as *mut u8) = chat_pinned as u8;
|
| 244 |
+
|
| 245 |
+
let chat_lines = read_profile_int("Options", "ChatLines", 0);
|
| 246 |
+
*((gi + 0xF3A8) as *mut u32) = chat_lines;
|
| 247 |
+
|
| 248 |
+
let pinned_lines = read_profile_int("Options", "PinnedChatLines", 0xFFFFFFFF);
|
| 249 |
+
*((gi + 0xF3AC) as *mut u32) = pinned_lines;
|
| 250 |
+
|
| 251 |
+
let home_lock = read_profile_int("Options", "HomeLock", 0);
|
| 252 |
+
*((gi + 0xF3B0) as *mut u8) = home_lock as u8;
|
| 253 |
+
|
| 254 |
+
// BackgroundDebrisParallax: clamp to i16 range, then << 16
|
| 255 |
+
let mut parallax = read_profile_int("Options", "BackgroundDebrisParallax", 0x50);
|
| 256 |
+
let parallax_i32 = parallax as i32;
|
| 257 |
+
if parallax_i32 < -0x8000 || parallax_i32 > 0x7FFF {
|
| 258 |
+
if parallax_i32 < 0 {
|
| 259 |
+
parallax = (-0x8000i32) as u32;
|
| 260 |
+
} else {
|
| 261 |
+
parallax = 0x7FFF;
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
*((gi + 0xF3E8) as *mut u32) = parallax << 16;
|
| 265 |
+
|
| 266 |
+
let onomatopoeia = read_profile_int("Options", "TopmostExplosionOnomatopoeia", 0);
|
| 267 |
+
*((gi + 0xF3EC) as *mut u32) = onomatopoeia;
|
| 268 |
+
|
| 269 |
+
let capture_png = read_profile_int("Options", "CaptureTransparentPNGs", 0);
|
| 270 |
+
*((gi + 0xF3DC) as *mut u32) = capture_png;
|
| 271 |
+
|
| 272 |
+
// CameraUnlockMouseSpeed: clamp to max 0xB504, then square
|
| 273 |
+
let mut mouse_speed = read_profile_int("Options", "CameraUnlockMouseSpeed", 0x10);
|
| 274 |
+
if mouse_speed > 0xB504 {
|
| 275 |
+
if (mouse_speed as i32) < 0 {
|
| 276 |
+
mouse_speed = 0;
|
| 277 |
+
} else {
|
| 278 |
+
mouse_speed = 0xB504;
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
*((gi + 0xF3E0) as *mut u32) = mouse_speed * mouse_speed;
|
| 282 |
+
|
| 283 |
+
// Final global DWORD
|
| 284 |
+
*((gi + 0xF3E4) as *mut u32) = *(rb(va::G_CONFIG_DWORD_F3E4) as *const u32);
|
| 285 |
+
|
| 286 |
+
let _ = log_line("[Config] LoadOptions completed (Rust)");
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
// ============================================================
|
| 290 |
// Hook installation
|
| 291 |
// ============================================================
|
|
|
|
| 321 |
va::REGISTRY_CLEAN_ALL,
|
| 322 |
hook_registry_clean_all as *const (),
|
| 323 |
)?;
|
| 324 |
+
|
| 325 |
+
let _ = crate::hook::install(
|
| 326 |
+
"GameInfo__LoadOptions",
|
| 327 |
+
va::GAMEINFO_LOAD_OPTIONS,
|
| 328 |
+
hook_load_options as *const (),
|
| 329 |
+
)?;
|
| 330 |
}
|
| 331 |
|
| 332 |
Ok(())
|