paavohtl Claude Opus 4.6 commited on
Commit
dfc6e85
·
1 Parent(s): 055dbb3

feat: add GameInfo__LoadOptions hook (registry reads + global copies)

Browse files

Full 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(())