paavohtl Claude Opus 4.6 (1M context) commited on
Commit
b7ab092
Β·
1 Parent(s): 9140c85

refactor: add GameInfo fields, replace pointer arithmetic

Browse files

Add typed fields to GameInfo:
- game_version (0xD778): renamed from crosshair_overflow_threshold
(it's a game mode value, not a crosshair threshold)
- net_config_1/2 (0xD944/0xD946): network config bytes
- landscape_data_path (0xDAAC): path data for PCLandscape

Replace raw pointer arithmetic in create_ddgame/init_graphics_and_resources:
- (*game_info).headless_mode instead of *(ptr+0xF914)
- (*game_info).game_version instead of *(ptr+0xD778)
- (*game_info).display_flags instead of *(ptr+0xF374)
- (*game_info).landscape_data_path.as_ptr() instead of ptr+0xDAAC
- (*game_info).net_config_1/2 instead of *(ptr+0xD944/D946)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -470,7 +470,7 @@ pub unsafe fn create_ddgame(
470
  *(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
471
 
472
  // ── 6. Sound available + always-1 flags ──
473
- let is_headless = *(game_info as *const u8).add(0xF914).cast::<i32>() != 0;
474
  // Defer sound_available β€” keep at 0 during construction.
475
  // DDGame__LoadWeaponSprites checks this flag; when set, it calls
476
  // FUN_572E30 which pumps PeekMessage/DispatchMessage. That can
@@ -482,15 +482,13 @@ pub unsafe fn create_ddgame(
482
  // ── 7. DDGameWrapper+0x48C init ──
483
  (*wrapper).ddgame_secondary = core::ptr::null_mut();
484
 
485
- // ── 8. Conditional network object (game_info+0xD778 == -2) ──
486
- let d778 = *(game_info as *const u8).add(0xD778).cast::<i32>();
487
- if d778 == -2 {
488
  let net_obj = wa_malloc(0x2C);
489
  core::ptr::write_bytes(net_obj, 0, 0x2C);
490
  *(net_obj as *mut *mut DDGame) = ddgame;
491
- let gi = (*ddgame).game_info as *const u8;
492
- *net_obj.add(0x28) = *gi.add(0xD944);
493
- *net_obj.add(0x29) = *gi.add(0xD946);
494
  (*wrapper).ddgame_secondary = net_obj;
495
  if network_ecx != 0 {
496
  *((network_ecx as *mut u8).add(0x18) as *mut *mut u8) = net_obj;
@@ -538,8 +536,8 @@ unsafe fn init_graphics_and_resources(
538
  (*wrapper)._field_4c4 = core::ptr::null_mut();
539
 
540
  // Build path list (order depends on headless + GameInfo+0xF374)
541
- let f914 = *(game_info as *const u8).add(0xF914).cast::<i32>();
542
- let f374 = *(game_info as *const u8).add(0xF374).cast::<i32>();
543
  let paths: [&[u8]; 3] = if f914 != 0 {
544
  if f374 == 0 {
545
  [b"data\\Gfx\\Gfx.dir\0", b"data\\Gfx\\Gfx0.dir\0", b"data\\Gfx\\Gfx1.dir\0"]
@@ -569,7 +567,7 @@ unsafe fn init_graphics_and_resources(
569
  (*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
570
 
571
  // ── GfxHandler #2 (conditional) ──
572
- let d778 = *(game_info as *const u8).add(0xD778).cast::<i32>();
573
  let threshold = if (*wrapper).gfx_mode != 0 {
574
  (0x23u32.wrapping_sub(2)) as i32
575
  } else {
@@ -684,10 +682,10 @@ unsafe fn init_graphics_and_resources(
684
  *(gfxdir2.add(0x708) as *mut u16) = 0;
685
  (*ddgame).secondary_gfxdir = gfxdir2;
686
  // GFX_HANDLER_LOAD_SPRITES: stdcall(wrapper, ddgame+0x7308, game_info+0xF374, 0), RET 0x10
687
- let gi = (*ddgame).game_info as *const u8;
688
  let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
689
  core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
690
- f(wrapper, (ddgame as *mut u8).add(0x7308), *(gi.add(0xF374) as *const u32), 0);
691
  }
692
 
693
  // ── DDGameWrapper field inits ──
@@ -769,7 +767,7 @@ unsafe fn init_graphics_and_resources(
769
  *mut u8, *mut u8, // 10=&ddgame+0x777C, 11=&ddgame+0x7780
770
  ) -> *mut u8 = core::mem::transmute(rb(va::PC_LANDSCAPE_CONSTRUCTOR) as usize);
771
 
772
- let gi = (*ddgame).game_info as *const u8;
773
  // Param 8 is a stack local in the original's 4KB frame.
774
  // Heap-allocate a generous buffer since we don't know the exact size.
775
  let stack_local_8 = wa_malloc(0x1000);
@@ -779,7 +777,7 @@ unsafe fn init_graphics_and_resources(
779
  ddgame,
780
  gfx_resource,
781
  (*wrapper).display, // param 4: display (NOT wrapper!)
782
- gi.add(0xDAAC), // param 5
783
  landscape_byte_buf, // param 6
784
  (*wrapper).gfx_mode, // param 7
785
  stack_local_8, // param 8
@@ -1015,8 +1013,8 @@ unsafe fn init_graphics_and_resources(
1015
  );
1016
 
1017
  // Set global flag based on game version
1018
- let d778 = *((*ddgame).game_info as *const u8).add(0xD778).cast::<i32>();
1019
- *(rb(0x6AF050) as *mut u32) = if d778 < 8 { 0 } else { 0x10 };
1020
 
1021
  // Load resources for layer 1 with different table
1022
  load_resource_list(
 
470
  *(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
471
 
472
  // ── 6. Sound available + always-1 flags ──
473
+ let is_headless = (*game_info).headless_mode != 0;
474
  // Defer sound_available β€” keep at 0 during construction.
475
  // DDGame__LoadWeaponSprites checks this flag; when set, it calls
476
  // FUN_572E30 which pumps PeekMessage/DispatchMessage. That can
 
482
  // ── 7. DDGameWrapper+0x48C init ──
483
  (*wrapper).ddgame_secondary = core::ptr::null_mut();
484
 
485
+ // ── 8. Conditional network object (game_version == -2) ──
486
+ if (*game_info).game_version == -2 {
 
487
  let net_obj = wa_malloc(0x2C);
488
  core::ptr::write_bytes(net_obj, 0, 0x2C);
489
  *(net_obj as *mut *mut DDGame) = ddgame;
490
+ *net_obj.add(0x28) = (*game_info).net_config_1;
491
+ *net_obj.add(0x29) = (*game_info).net_config_2;
 
492
  (*wrapper).ddgame_secondary = net_obj;
493
  if network_ecx != 0 {
494
  *((network_ecx as *mut u8).add(0x18) as *mut *mut u8) = net_obj;
 
536
  (*wrapper)._field_4c4 = core::ptr::null_mut();
537
 
538
  // Build path list (order depends on headless + GameInfo+0xF374)
539
+ let f914 = (*game_info).headless_mode as i32;
540
+ let f374 = (*game_info).display_flags as i32;
541
  let paths: [&[u8]; 3] = if f914 != 0 {
542
  if f374 == 0 {
543
  [b"data\\Gfx\\Gfx.dir\0", b"data\\Gfx\\Gfx0.dir\0", b"data\\Gfx\\Gfx1.dir\0"]
 
567
  (*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
568
 
569
  // ── GfxHandler #2 (conditional) ──
570
+ let d778 = (*game_info).game_version;
571
  let threshold = if (*wrapper).gfx_mode != 0 {
572
  (0x23u32.wrapping_sub(2)) as i32
573
  } else {
 
682
  *(gfxdir2.add(0x708) as *mut u16) = 0;
683
  (*ddgame).secondary_gfxdir = gfxdir2;
684
  // GFX_HANDLER_LOAD_SPRITES: stdcall(wrapper, ddgame+0x7308, game_info+0xF374, 0), RET 0x10
685
+
686
  let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
687
  core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
688
+ f(wrapper, (ddgame as *mut u8).add(0x7308), (*game_info).display_flags, 0);
689
  }
690
 
691
  // ── DDGameWrapper field inits ──
 
767
  *mut u8, *mut u8, // 10=&ddgame+0x777C, 11=&ddgame+0x7780
768
  ) -> *mut u8 = core::mem::transmute(rb(va::PC_LANDSCAPE_CONSTRUCTOR) as usize);
769
 
770
+
771
  // Param 8 is a stack local in the original's 4KB frame.
772
  // Heap-allocate a generous buffer since we don't know the exact size.
773
  let stack_local_8 = wa_malloc(0x1000);
 
777
  ddgame,
778
  gfx_resource,
779
  (*wrapper).display, // param 4: display (NOT wrapper!)
780
+ (*game_info).landscape_data_path.as_ptr(), // param 5
781
  landscape_byte_buf, // param 6
782
  (*wrapper).gfx_mode, // param 7
783
  stack_local_8, // param 8
 
1013
  );
1014
 
1015
  // Set global flag based on game version
1016
+ let gv = (*(*ddgame).game_info).game_version;
1017
+ *(rb(0x6AF050) as *mut u32) = if gv < 8 { 0 } else { 0x10 };
1018
 
1019
  // Load resources for layer 1 with different table
1020
  load_resource_list(
crates/openwa-core/src/engine/game_info.rs CHANGED
@@ -21,12 +21,23 @@ pub struct GameInfo {
21
  /// 0x044D-0xD777: 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.
@@ -36,8 +47,12 @@ pub struct GameInfo {
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
 
 
21
  /// 0x044D-0xD777: Unknown
22
  pub _unknown_044d: [u8; 0xD778 - 0x44D],
23
 
24
+ /// 0xD778: Game version/mode. Compared against various thresholds:
25
+ /// -2 = network game, -3..0 = different game modes, 8+ = new versions.
26
+ /// Used by DDGame constructor for conditional initialization.
27
+ pub game_version: i32,
28
 
29
+ /// 0xD77C-0xD943: Unknown
30
+ pub _unknown_d77c: [u8; 0xD944 - 0xD77C],
31
+
32
+ /// 0xD944: Network config byte 1 (copied to network object+0x28).
33
+ pub net_config_1: u8,
34
+ /// 0xD945: Unknown
35
+ pub _unknown_d945: u8,
36
+ /// 0xD946: Network config byte 2 (copied to network object+0x29).
37
+ pub net_config_2: u8,
38
+
39
+ /// 0xD947-0xD9DF: Unknown
40
+ pub _unknown_d947: [u8; 0xD9E0 - 0xD947],
41
 
42
  /// 0xD9E0: Streaming audio config data (path config passed to streaming audio ctor).
43
  /// Address of this field is passed as a pointer parameter.
 
47
  /// If nonzero, streaming audio subsystem is created in InitHardware.
48
  pub speech_enabled: u8,
49
 
50
+ /// 0xDAA5-0xDAAB: Unknown
51
+ pub _unknown_daa5: [u8; 0xDAAC - 0xDAA5],
52
+
53
+ /// 0xDAAC: Landscape data path (passed to PCLandscape constructor).
54
+ /// Points to a path string used for loading level terrain data.
55
+ pub landscape_data_path: [u8; 0xDAE8 - 0xDAAC],
56
 
57
  // --- Cluster 1: data paths ---
58
 
crates/openwa-wormkit/src/replacements/hardware_init.rs CHANGED
@@ -252,7 +252,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
252
  let _ = log_line("[hardware_init] GameEngine::InitHardware");
253
  let session = *(rb(va::G_GAME_SESSION) as *const *mut GameSession);
254
  let gi = &mut *game_info;
255
- let crosshair_threshold = gi.crosshair_overflow_threshold as u32;
256
 
257
  // ── Input controller (if param4 != 0) ────────────────────────────────────
258
  if param4 == 0 {
 
252
  let _ = log_line("[hardware_init] GameEngine::InitHardware");
253
  let session = *(rb(va::G_GAME_SESSION) as *const *mut GameSession);
254
  let gi = &mut *game_info;
255
+ let crosshair_threshold = gi.game_version as u32;
256
 
257
  // ── Input controller (if param4 != 0) ────────────────────────────────────
258
  if param4 == 0 {
crates/openwa-wormkit/src/replacements/render.rs CHANGED
@@ -529,7 +529,7 @@ unsafe extern "cdecl" fn draw_crosshair_line_impl(task_ptr: u32) {
529
  let mut overflowed = false;
530
  let mut clamp_factor = 0i32;
531
 
532
- let threshold = (*ddgame.game_info).crosshair_overflow_threshold;
533
 
534
  if threshold > 0x11E {
535
  // Check X overflow: sin > 0 but endpoint wrapped below start
 
529
  let mut overflowed = false;
530
  let mut clamp_factor = 0i32;
531
 
532
+ let threshold = (*ddgame.game_info).game_version;
533
 
534
  if threshold > 0x11E {
535
  // Check X overflow: sin > 0 but endpoint wrapped below start