paavohtl Claude Opus 4.6 (1M context) commited on
Commit
d40f088
·
1 Parent(s): 6b181ec

fix: correct ReplayLoader player array strides and map_seed handling

Browse files

Two replay parsing bugs:

1. Player array stride: Ghidra decompiler shows element indices for typed
arrays (i*0x3C for u16, i*0x1E for u32) but code used them as byte
offsets. Correct byte stride is i*0x78 for all player fields. Caused
data corruption for player index 1+, leading to wrong team order.

2. Non-zero map_seed path: per-team weapon config reads from stream were
unimplemented. For map_seed != 0, fall back to original WA parser.
This fixed the longbow_alt_theme Frame 0 checksum mismatch.

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

crates/openwa-wormkit/src/replacements/replay.rs CHANGED
@@ -415,13 +415,15 @@ unsafe fn parse_and_write_v2plus(
415
  ptr::copy_nonoverlapping(config.as_ptr(), rb(va::G_PLAYER_ARRAY + 0x42 + i * 0x78) as *mut u8, 0x29);
416
 
417
  let u16_val = s.read_u16()?;
418
- *(rb(va::G_PLAYER_ARRAY + 0x6C + i * 0x3C) as *mut u16) = u16_val;
 
419
 
420
  let byte1 = s.read_u8()?;
421
  wb(va::G_PLAYER_ARRAY + 0x6E + i * 0x78, byte1);
422
 
423
  let u32_val = s.read_u32()?;
424
- wd(va::G_PLAYER_ARRAY + 0x70 + i * 0x1E, u32_val);
 
425
 
426
  let byte2 = s.read_u8()?;
427
  wb(va::G_PLAYER_ARRAY + 0x77 + i * 0x78, byte2);
@@ -515,7 +517,12 @@ unsafe fn parse_and_write_v2plus(
515
  if map_seed == 0 || map_seed == 0xFFFF {
516
  call_process_scheme_defaults(gi, rb(va::REPLAY_PROCESS_SCHEME_DEFAULTS));
517
  }
518
- // TODO: else branch — per-team weapon config reads from stream
 
 
 
 
 
519
 
520
  // ValidateTeamSetup: stdcall(1 param = game_info)
521
  let validate_setup: unsafe extern "stdcall" fn(*mut GameInfo) =
 
415
  ptr::copy_nonoverlapping(config.as_ptr(), rb(va::G_PLAYER_ARRAY + 0x42 + i * 0x78) as *mut u8, 0x29);
416
 
417
  let u16_val = s.read_u16()?;
418
+ // Ghidra shows (&DAT[i*0x3C]) with u16 elements → byte offset = i*0x3C*2 = i*0x78
419
+ *(rb(va::G_PLAYER_ARRAY + 0x6C + i * 0x78) as *mut u16) = u16_val;
420
 
421
  let byte1 = s.read_u8()?;
422
  wb(va::G_PLAYER_ARRAY + 0x6E + i * 0x78, byte1);
423
 
424
  let u32_val = s.read_u32()?;
425
+ // Ghidra shows (&DAT[i*0x1E]) with u32 elements → byte offset = i*0x1E*4 = i*0x78
426
+ wd(va::G_PLAYER_ARRAY + 0x70 + i * 0x78, u32_val);
427
 
428
  let byte2 = s.read_u8()?;
429
  wb(va::G_PLAYER_ARRAY + 0x77 + i * 0x78, byte2);
 
517
  if map_seed == 0 || map_seed == 0xFFFF {
518
  call_process_scheme_defaults(gi, rb(va::REPLAY_PROCESS_SCHEME_DEFAULTS));
519
  }
520
+ if map_seed != 0 {
521
+ // Per-team weapon config reads from stream — not yet implemented in Rust.
522
+ // Fall back to the original parser which handles this from scratch.
523
+ let _ = log_line(&format!("[Replay] Non-zero map_seed (0x{:04X}), delegating to original", map_seed));
524
+ return Err(ReplayError::InvalidFormat); // triggers delegate_to_original
525
+ }
526
 
527
  // ValidateTeamSetup: stdcall(1 param = game_info)
528
  let validate_setup: unsafe extern "stdcall" fn(*mut GameInfo) =