paavohtl Claude Opus 4.6 (1M context) commited on
Commit
6287626
Β·
1 Parent(s): d6d409e

fix: _field_4dc reads game_info+0x44C, not net_game+0x44C

Browse files

Ghidra mislabeled param8 as net_game in the DDGame constructor, but
the actual code reads game_info+0x44C for the loading progress total.
net_game is null in offline/bot replays; game_info is always valid.

Wrapper+0x04DC now matches original (0x39B). Only remaining wrapper
diff is +0x04D8 tick counter (off by 2).

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -992,10 +992,8 @@ pub unsafe fn create_ddgame(
992
 
993
  // Verify game_info is set
994
  let _ = crate::log::log_line(&format!(
995
- "[DDGame] exposed: ddgame=0x{:08X}, game_info=0x{:08X}, ddgame+0x24=0x{:08X}",
996
- ddgame as u32,
997
- game_info as u32,
998
- (*ddgame).game_info as u32,
999
  ));
1000
 
1001
  // ── 5. Set g_GameInfo global ──
@@ -1237,12 +1235,13 @@ unsafe fn init_graphics_and_resources(
1237
 
1238
  // ── DDGameWrapper field inits ──
1239
  (*wrapper)._field_4d8 = 0;
1240
- // Original checks `display == NULL`, but our headless display is non-null.
1241
- // Use net_game null check as equivalent (net_game is null in headless/offline modes).
1242
- if net_game.is_null() {
 
1243
  (*wrapper)._field_4dc = 0x2AD;
1244
  } else {
1245
- let byte_val = *net_game.add(0x44C) as u32;
1246
  (*wrapper)._field_4dc = byte_val * 0x38 + 0x7E + 0x2AD;
1247
  }
1248
  (*wrapper)._field_4e0 = 0xFFFFFF9C; // -100
 
992
 
993
  // Verify game_info is set
994
  let _ = crate::log::log_line(&format!(
995
+ "[DDGame] exposed: ddgame=0x{:08X}, game_info=0x{:08X}, net_game=0x{:08X}",
996
+ ddgame as u32, game_info as u32, net_game as u32,
 
 
997
  ));
998
 
999
  // ── 5. Set g_GameInfo global ──
 
1235
 
1236
  // ── DDGameWrapper field inits ──
1237
  (*wrapper)._field_4d8 = 0;
1238
+ // Original: if (display == NULL) iVar4=0 else iVar4=*(byte*)(game_info+0x44C)*0x38+0x7E
1239
+ // Ghidra mislabeled game_info as param8 β€” the original reads game_info, not net_game.
1240
+ // Our headless display is non-null, so use is_headless as the condition.
1241
+ if is_headless {
1242
  (*wrapper)._field_4dc = 0x2AD;
1243
  } else {
1244
+ let byte_val = *(game_info as *const u8).add(0x44C) as u32;
1245
  (*wrapper)._field_4dc = byte_val * 0x38 + 0x7E + 0x2AD;
1246
  }
1247
  (*wrapper)._field_4e0 = 0xFFFFFF9C; // -100
crates/openwa-wormkit/src/replacements/game_session.rs CHANGED
@@ -107,9 +107,18 @@ pub(crate) unsafe fn construct_ddgame_wrapper(
107
  let timer_obj = (*session).timer_obj;
108
  let net_game = (*session).net_game;
109
 
 
 
110
  let _ = log_line(&format!(
111
- "[GameSession] Before create_ddgame: wrapper+0x4D0=0x{:08X}, display=0x{:08X}",
112
- *(this as *const u8).add(0x4D0).cast::<u32>(), display as u32,
 
 
 
 
 
 
 
113
  ));
114
 
115
  // Toggle: use Rust constructor (true) or original (false)
 
107
  let timer_obj = (*session).timer_obj;
108
  let net_game = (*session).net_game;
109
 
110
+ // Debug: dump session fields around net_game (0xBC-0xC8)
111
+ let session_base = session as *const u8;
112
  let _ = log_line(&format!(
113
+ "[GameSession] session=0x{:08X} +0xBC=0x{:08X} +0xC0=0x{:08X} +0xC4=0x{:08X}",
114
+ session as u32,
115
+ *(session_base.add(0xBC) as *const u32),
116
+ *(session_base.add(0xC0) as *const u32),
117
+ *(session_base.add(0xC4) as *const u32),
118
+ ));
119
+ let _ = log_line(&format!(
120
+ "[GameSession] display=0x{:08X}, net_game=0x{:08X}, timer=0x{:08X}, game_info(EDI)=0x{:08X}",
121
+ display as u32, net_game as u32, timer_obj as u32, game_info as u32,
122
  ));
123
 
124
  // Toggle: use Rust constructor (true) or original (false)