fix: correct ReplayLoader player array strides and map_seed handling
Browse filesTwo 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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) =
|