refactor: split input.rs into frame_hook, replay_test, debug_utils
Browse filesThe old input.rs was a grab-bag of unrelated concerns. Split into:
- frame_hook.rs: TurnManager_ProcessFrame hook, debug_sync, watchpoint
arming. Always installed (including baseline/trace-desync mode).
- replay_test.rs: Fast-forward + gameplay milestone tracking. Only for
headful replay testing — headless runs at max CPU speed already.
- debug_utils.rs: dump_region memory inspection (used by validation).
Moved get_ddgame()/get_wrapper() to openwa-core::engine::game_session
as shared #[cfg(x86)] accessors, eliminating duplicates in wormkit
and debugui.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/engine/game_session.rs +36 -0
- crates/openwa-debugui/src/app.rs +3 -14
- crates/openwa-wormkit/src/replacements/debug_utils.rs +78 -0
- crates/openwa-wormkit/src/replacements/frame_hook.rs +83 -0
- crates/openwa-wormkit/src/replacements/mod.rs +6 -4
- crates/openwa-wormkit/src/replacements/{input.rs → replay_test.rs} +36 -199
- crates/openwa-wormkit/src/validation/mod.rs +3 -3
crates/openwa-core/src/engine/game_session.rs
CHANGED
|
@@ -93,3 +93,39 @@ pub struct GameSession {
|
|
| 93 |
}
|
| 94 |
|
| 95 |
const _: () = assert!(core::mem::size_of::<GameSession>() == 0x120);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
}
|
| 94 |
|
| 95 |
const _: () = assert!(core::mem::size_of::<GameSession>() == 0x120);
|
| 96 |
+
|
| 97 |
+
// ─── Runtime accessors (DLL-injected context only) ──────────────────────
|
| 98 |
+
|
| 99 |
+
#[cfg(target_arch = "x86")]
|
| 100 |
+
use crate::address::va;
|
| 101 |
+
#[cfg(target_arch = "x86")]
|
| 102 |
+
use crate::engine::ddgame::DDGame;
|
| 103 |
+
#[cfg(target_arch = "x86")]
|
| 104 |
+
use crate::rebase::rb;
|
| 105 |
+
|
| 106 |
+
/// Get the DDGameWrapper pointer from the global game session.
|
| 107 |
+
///
|
| 108 |
+
/// Returns null if the session or wrapper hasn't been initialized yet.
|
| 109 |
+
#[cfg(target_arch = "x86")]
|
| 110 |
+
#[inline]
|
| 111 |
+
pub unsafe fn get_wrapper() -> *mut DDGameWrapper {
|
| 112 |
+
let session = *(rb(va::G_GAME_SESSION) as *const *mut GameSession);
|
| 113 |
+
if session.is_null() {
|
| 114 |
+
return core::ptr::null_mut();
|
| 115 |
+
}
|
| 116 |
+
(*session).ddgame_wrapper
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
/// Get the DDGame pointer from the global game session.
|
| 120 |
+
///
|
| 121 |
+
/// Follows the chain: G_GAME_SESSION → GameSession.ddgame_wrapper → DDGameWrapper.ddgame.
|
| 122 |
+
/// Returns null if any link in the chain is uninitialized.
|
| 123 |
+
#[cfg(target_arch = "x86")]
|
| 124 |
+
#[inline]
|
| 125 |
+
pub unsafe fn get_ddgame() -> *mut DDGame {
|
| 126 |
+
let wrapper = get_wrapper();
|
| 127 |
+
if wrapper.is_null() {
|
| 128 |
+
return core::ptr::null_mut();
|
| 129 |
+
}
|
| 130 |
+
(*wrapper).ddgame
|
| 131 |
+
}
|
crates/openwa-debugui/src/app.rs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
use eframe::egui;
|
| 2 |
use openwa_core::address::va;
|
| 3 |
-
use openwa_core::engine::{
|
| 4 |
use openwa_core::rebase::rb;
|
| 5 |
use openwa_core::registry;
|
| 6 |
use openwa_core::task::{
|
|
@@ -51,19 +51,8 @@ unsafe fn entity_label(addr: u32) -> String {
|
|
| 51 |
|
| 52 |
/// Returns a pointer to DDGame, or None if not in-game.
|
| 53 |
unsafe fn get_ddgame() -> Option<*const DDGame> {
|
| 54 |
-
let
|
| 55 |
-
if
|
| 56 |
-
return None;
|
| 57 |
-
}
|
| 58 |
-
let wrapper_addr = *((session_ptr + 0xA0) as *const u32);
|
| 59 |
-
if wrapper_addr == 0 {
|
| 60 |
-
return None;
|
| 61 |
-
}
|
| 62 |
-
let ddgame_ptr = (*(wrapper_addr as *const DDGameWrapper)).ddgame;
|
| 63 |
-
if ddgame_ptr.is_null() {
|
| 64 |
-
return None;
|
| 65 |
-
}
|
| 66 |
-
Some(ddgame_ptr)
|
| 67 |
}
|
| 68 |
|
| 69 |
/// Unlock all weapons: set ammo to unlimited (-1) and delays to 0 for all teams.
|
|
|
|
| 1 |
use eframe::egui;
|
| 2 |
use openwa_core::address::va;
|
| 3 |
+
use openwa_core::engine::{game_session, DDGame};
|
| 4 |
use openwa_core::rebase::rb;
|
| 5 |
use openwa_core::registry;
|
| 6 |
use openwa_core::task::{
|
|
|
|
| 51 |
|
| 52 |
/// Returns a pointer to DDGame, or None if not in-game.
|
| 53 |
unsafe fn get_ddgame() -> Option<*const DDGame> {
|
| 54 |
+
let ptr = game_session::get_ddgame();
|
| 55 |
+
if ptr.is_null() { None } else { Some(ptr) }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
|
| 58 |
/// Unlock all weapons: set ammo to unlimited (-1) and delays to 0 for all teams.
|
crates/openwa-wormkit/src/replacements/debug_utils.rs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Debug memory inspection utilities.
|
| 2 |
+
//!
|
| 3 |
+
//! Contains `dump_region` for logging classified DWORD dumps of memory regions.
|
| 4 |
+
//! Used by the validation module for struct inspection.
|
| 5 |
+
|
| 6 |
+
use crate::log_line;
|
| 7 |
+
use openwa_core::address::va;
|
| 8 |
+
use openwa_core::rebase::rb;
|
| 9 |
+
|
| 10 |
+
/// Dump a memory region as DWORDs with automatic pointer classification.
|
| 11 |
+
///
|
| 12 |
+
/// Uses `openwa_core::mem::classify_pointer` for pointer detection.
|
| 13 |
+
pub unsafe fn dump_region(base_ptr: *const u8, offset: usize, size: usize, struct_name: &str) {
|
| 14 |
+
use openwa_core::mem;
|
| 15 |
+
use openwa_debug_proto::PointerKind;
|
| 16 |
+
|
| 17 |
+
let wa_base = rb(va::IMAGE_BASE);
|
| 18 |
+
let delta = wa_base.wrapping_sub(va::IMAGE_BASE);
|
| 19 |
+
|
| 20 |
+
let _ = log_line(&format!(
|
| 21 |
+
"\n=== {}+0x{:04X}..0x{:04X} ===",
|
| 22 |
+
struct_name,
|
| 23 |
+
offset,
|
| 24 |
+
offset + size
|
| 25 |
+
));
|
| 26 |
+
|
| 27 |
+
let dword_count = size / 4;
|
| 28 |
+
for i in 0..dword_count {
|
| 29 |
+
let field_offset = offset + i * 4;
|
| 30 |
+
let val = *(base_ptr.add(field_offset) as *const u32);
|
| 31 |
+
if val == 0 {
|
| 32 |
+
continue;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
if let Some(info) = mem::classify_pointer(val, delta) {
|
| 36 |
+
let detail_str = info.detail.as_deref().unwrap_or("");
|
| 37 |
+
match info.kind {
|
| 38 |
+
PointerKind::Vtable => {
|
| 39 |
+
let _ = log_line(&format!(
|
| 40 |
+
" +0x{:04X}: 0x{:08X} [VTABLE] g:0x{:08X} {}",
|
| 41 |
+
field_offset, val, info.ghidra_value, detail_str
|
| 42 |
+
));
|
| 43 |
+
}
|
| 44 |
+
PointerKind::Code => {
|
| 45 |
+
let _ = log_line(&format!(
|
| 46 |
+
" +0x{:04X}: 0x{:08X} [CODE] g:0x{:08X}",
|
| 47 |
+
field_offset, val, info.ghidra_value
|
| 48 |
+
));
|
| 49 |
+
}
|
| 50 |
+
PointerKind::Data => {
|
| 51 |
+
let _ = log_line(&format!(
|
| 52 |
+
" +0x{:04X}: 0x{:08X} [DATA] g:0x{:08X}",
|
| 53 |
+
field_offset, val, info.ghidra_value
|
| 54 |
+
));
|
| 55 |
+
}
|
| 56 |
+
PointerKind::Object => {
|
| 57 |
+
let _ = log_line(&format!(
|
| 58 |
+
" +0x{:04X}: 0x{:08X} [OBJECT] {}",
|
| 59 |
+
field_offset, val, detail_str
|
| 60 |
+
));
|
| 61 |
+
}
|
| 62 |
+
PointerKind::Heap => {
|
| 63 |
+
let _ = log_line(&format!(
|
| 64 |
+
" +0x{:04X}: 0x{:08X} [ptr] {}",
|
| 65 |
+
field_offset, val, detail_str
|
| 66 |
+
));
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
} else if val < 0x10000 {
|
| 70 |
+
let _ = log_line(&format!(
|
| 71 |
+
" +0x{:04X}: 0x{:08X} [small={}]",
|
| 72 |
+
field_offset, val, val
|
| 73 |
+
));
|
| 74 |
+
} else {
|
| 75 |
+
let _ = log_line(&format!(" +0x{:04X}: 0x{:08X} [value]", field_offset, val));
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
}
|
crates/openwa-wormkit/src/replacements/frame_hook.rs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Per-frame hook on TurnManager_ProcessFrame (0x55FDA0).
|
| 2 |
+
//!
|
| 3 |
+
//! Provides the central frame boundary hook used by multiple subsystems:
|
| 4 |
+
//! - Debug synchronization (pause/resume/step via debug CLI)
|
| 5 |
+
//! - Hardware watchpoint arming at specific frames
|
| 6 |
+
//!
|
| 7 |
+
//! This hook is always installed (both normal and baseline modes).
|
| 8 |
+
|
| 9 |
+
use std::sync::atomic::{AtomicU32, Ordering};
|
| 10 |
+
|
| 11 |
+
use crate::hook;
|
| 12 |
+
use crate::log_line;
|
| 13 |
+
use openwa_core::address::va;
|
| 14 |
+
use openwa_core::engine::game_session;
|
| 15 |
+
|
| 16 |
+
/// Trampoline to the original TurnManager_ProcessFrame.
|
| 17 |
+
static ORIG_TURN_MANAGER: AtomicU32 = AtomicU32::new(0);
|
| 18 |
+
|
| 19 |
+
/// Total frames processed by TurnManager_ProcessFrame.
|
| 20 |
+
static FRAMES_PROCESSED: AtomicU32 = AtomicU32::new(0);
|
| 21 |
+
|
| 22 |
+
/// Get the number of frames processed so far.
|
| 23 |
+
pub fn frames_processed() -> u32 {
|
| 24 |
+
FRAMES_PROCESSED.load(Ordering::Relaxed)
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
/// Hook for TurnManager_ProcessFrame (stdcall, 1 param = TurnGame*).
|
| 28 |
+
unsafe extern "stdcall" fn hook_turn_manager(turngame: u32) {
|
| 29 |
+
// Check debug sync BEFORE processing the frame — allows pausing at frame boundary
|
| 30 |
+
let ddgame = game_session::get_ddgame();
|
| 31 |
+
if !ddgame.is_null() {
|
| 32 |
+
let game_frame = (*ddgame).frame_counter;
|
| 33 |
+
crate::debug_sync::on_frame_start(game_frame);
|
| 34 |
+
|
| 35 |
+
// Hardware watchpoint: arm once at the watch frame
|
| 36 |
+
static WATCH_ARMED: core::sync::atomic::AtomicBool =
|
| 37 |
+
core::sync::atomic::AtomicBool::new(false);
|
| 38 |
+
if !WATCH_ARMED.load(Ordering::Relaxed) {
|
| 39 |
+
if let Ok(val) = std::env::var("OPENWA_WATCH_FRAME") {
|
| 40 |
+
let target: i32 = val.parse().unwrap_or(0);
|
| 41 |
+
if game_frame >= target {
|
| 42 |
+
WATCH_ARMED.store(true, Ordering::Relaxed);
|
| 43 |
+
crate::debug_watchpoint::prepare();
|
| 44 |
+
// Select watchpoint base: DDGame, DDGameWrapper, or Display
|
| 45 |
+
let watch_base = if std::env::var("OPENWA_WATCH_DISPLAY").is_ok() {
|
| 46 |
+
let wrapper = game_session::get_wrapper();
|
| 47 |
+
*(wrapper.byte_add(0x4D0) as *const *mut u8)
|
| 48 |
+
} else if std::env::var("OPENWA_WATCH_WRAPPER").is_ok() {
|
| 49 |
+
game_session::get_wrapper() as *mut u8
|
| 50 |
+
} else {
|
| 51 |
+
ddgame as *mut u8
|
| 52 |
+
};
|
| 53 |
+
crate::debug_watchpoint::on_ddgame_alloc(watch_base);
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// Call original
|
| 60 |
+
let orig: unsafe extern "stdcall" fn(u32) =
|
| 61 |
+
core::mem::transmute(ORIG_TURN_MANAGER.load(Ordering::Relaxed));
|
| 62 |
+
orig(turngame);
|
| 63 |
+
|
| 64 |
+
let frame = FRAMES_PROCESSED.fetch_add(1, Ordering::Relaxed) + 1;
|
| 65 |
+
|
| 66 |
+
// Replay test: fast-forward + milestone tracking (no-op if not in replay test mode)
|
| 67 |
+
super::replay_test::on_frame(frame);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
pub fn install() -> Result<(), String> {
|
| 71 |
+
let _ = log_line("[FrameHook] Hooking TurnManager_ProcessFrame");
|
| 72 |
+
|
| 73 |
+
unsafe {
|
| 74 |
+
let trampoline = hook::install(
|
| 75 |
+
"TurnManager_ProcessFrame",
|
| 76 |
+
va::TURN_MANAGER_PROCESS_FRAME,
|
| 77 |
+
hook_turn_manager as *const (),
|
| 78 |
+
)?;
|
| 79 |
+
ORIG_TURN_MANAGER.store(trampoline as u32, Ordering::Relaxed);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
Ok(())
|
| 83 |
+
}
|
crates/openwa-wormkit/src/replacements/mod.rs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
| 1 |
mod config;
|
|
|
|
| 2 |
mod ddgame_init;
|
| 3 |
mod display;
|
| 4 |
pub(crate) mod file_isolation;
|
|
|
|
| 5 |
mod frontend;
|
| 6 |
mod game_session;
|
| 7 |
mod game_state_hooks;
|
| 8 |
mod hardware_init;
|
| 9 |
mod headless;
|
| 10 |
-
pub(crate) mod input;
|
| 11 |
mod render;
|
| 12 |
mod replay;
|
|
|
|
| 13 |
mod scheme;
|
| 14 |
mod sound;
|
| 15 |
mod speech;
|
|
@@ -21,7 +23,7 @@ mod weapon_release;
|
|
| 21 |
|
| 22 |
/// Write gameplay milestone report and clean up. Called from DLL_PROCESS_DETACH.
|
| 23 |
pub fn write_gameplay_report() {
|
| 24 |
-
|
| 25 |
trace_desync::flush();
|
| 26 |
file_isolation::cleanup();
|
| 27 |
}
|
|
@@ -30,17 +32,18 @@ pub fn install_all() -> Result<(), String> {
|
|
| 30 |
// Infrastructure hooks — always installed
|
| 31 |
headless::install()?;
|
| 32 |
file_isolation::install()?;
|
|
|
|
| 33 |
trace_desync::install()?;
|
| 34 |
|
| 35 |
// Baseline mode: only install minimal hooks needed for replay playback.
|
| 36 |
// Skips all gameplay hooks to provide a "nearly vanilla" reference run.
|
| 37 |
if std::env::var("OPENWA_TRACE_BASELINE").is_ok() {
|
| 38 |
-
input::install()?;
|
| 39 |
replay::install()?;
|
| 40 |
return Ok(());
|
| 41 |
}
|
| 42 |
|
| 43 |
// Normal mode: install all hooks
|
|
|
|
| 44 |
display::install()?;
|
| 45 |
game_session::install()?;
|
| 46 |
hardware_init::install()?;
|
|
@@ -52,7 +55,6 @@ pub fn install_all() -> Result<(), String> {
|
|
| 52 |
render::install()?;
|
| 53 |
sound::install()?;
|
| 54 |
speech::install()?;
|
| 55 |
-
input::install()?;
|
| 56 |
ddgame_init::install()?;
|
| 57 |
game_state_hooks::install()?;
|
| 58 |
replay::install()?;
|
|
|
|
| 1 |
mod config;
|
| 2 |
+
pub(crate) mod debug_utils;
|
| 3 |
mod ddgame_init;
|
| 4 |
mod display;
|
| 5 |
pub(crate) mod file_isolation;
|
| 6 |
+
mod frame_hook;
|
| 7 |
mod frontend;
|
| 8 |
mod game_session;
|
| 9 |
mod game_state_hooks;
|
| 10 |
mod hardware_init;
|
| 11 |
mod headless;
|
|
|
|
| 12 |
mod render;
|
| 13 |
mod replay;
|
| 14 |
+
mod replay_test;
|
| 15 |
mod scheme;
|
| 16 |
mod sound;
|
| 17 |
mod speech;
|
|
|
|
| 23 |
|
| 24 |
/// Write gameplay milestone report and clean up. Called from DLL_PROCESS_DETACH.
|
| 25 |
pub fn write_gameplay_report() {
|
| 26 |
+
replay_test::write_gameplay_report();
|
| 27 |
trace_desync::flush();
|
| 28 |
file_isolation::cleanup();
|
| 29 |
}
|
|
|
|
| 32 |
// Infrastructure hooks — always installed
|
| 33 |
headless::install()?;
|
| 34 |
file_isolation::install()?;
|
| 35 |
+
frame_hook::install()?;
|
| 36 |
trace_desync::install()?;
|
| 37 |
|
| 38 |
// Baseline mode: only install minimal hooks needed for replay playback.
|
| 39 |
// Skips all gameplay hooks to provide a "nearly vanilla" reference run.
|
| 40 |
if std::env::var("OPENWA_TRACE_BASELINE").is_ok() {
|
|
|
|
| 41 |
replay::install()?;
|
| 42 |
return Ok(());
|
| 43 |
}
|
| 44 |
|
| 45 |
// Normal mode: install all hooks
|
| 46 |
+
replay_test::install()?;
|
| 47 |
display::install()?;
|
| 48 |
game_session::install()?;
|
| 49 |
hardware_init::install()?;
|
|
|
|
| 55 |
render::install()?;
|
| 56 |
sound::install()?;
|
| 57 |
speech::install()?;
|
|
|
|
| 58 |
ddgame_init::install()?;
|
| 59 |
game_state_hooks::install()?;
|
| 60 |
replay::install()?;
|
crates/openwa-wormkit/src/replacements/{input.rs → replay_test.rs}
RENAMED
|
@@ -1,49 +1,33 @@
|
|
| 1 |
-
//!
|
| 2 |
//!
|
| 3 |
-
//! When `OPENWA_REPLAY_TEST=1`,
|
| 4 |
-
//!
|
|
|
|
|
|
|
|
|
|
| 5 |
//!
|
| 6 |
-
//!
|
| 7 |
-
//!
|
| 8 |
-
//!
|
| 9 |
-
//! FUN_0055BDD0), so we re-set it every frame.
|
| 10 |
-
//!
|
| 11 |
-
//! This is the same mechanism triggered by key 0x35 (spacebar) during replay.
|
| 12 |
-
//!
|
| 13 |
-
//! ## Gameplay Milestones
|
| 14 |
-
//!
|
| 15 |
-
//! The frame hook also tracks gameplay milestones via atomic flags:
|
| 16 |
-
//! - **Game initialized**: TurnManager_ProcessFrame called at least once
|
| 17 |
-
//! - **Match started**: Multiple teams with alive worms detected
|
| 18 |
-
//! - **Match completed**: Only one (or zero) teams have alive worms remaining
|
| 19 |
-
//!
|
| 20 |
-
//! These milestones are reported by `write_gameplay_report()` at DLL detach,
|
| 21 |
-
//! providing reliable hook-based game progress detection without timers.
|
| 22 |
-
|
| 23 |
-
#![allow(dead_code)]
|
| 24 |
|
| 25 |
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
|
| 26 |
|
| 27 |
-
use crate::hook;
|
| 28 |
use crate::log_line;
|
| 29 |
-
use openwa_core::address::va;
|
| 30 |
use openwa_core::engine::ddgame::{offsets, TeamArenaRef};
|
| 31 |
-
use openwa_core::engine::
|
| 32 |
-
use openwa_core::
|
| 33 |
|
| 34 |
-
//
|
| 35 |
-
|
|
|
|
| 36 |
|
| 37 |
-
/// Whether
|
| 38 |
static FAST_FORWARD: AtomicBool = AtomicBool::new(false);
|
| 39 |
|
| 40 |
// ---------------------------------------------------------------------------
|
| 41 |
// Gameplay milestone tracking
|
| 42 |
// ---------------------------------------------------------------------------
|
| 43 |
|
| 44 |
-
/// Total frames processed by TurnManager_ProcessFrame.
|
| 45 |
-
static FRAMES_PROCESSED: AtomicU32 = AtomicU32::new(0);
|
| 46 |
-
|
| 47 |
/// Milestone: at least 2 teams with alive worms detected.
|
| 48 |
static MATCH_STARTED: AtomicBool = AtomicBool::new(false);
|
| 49 |
|
|
@@ -59,99 +43,29 @@ static COMPLETION_FRAME: AtomicU32 = AtomicU32::new(0);
|
|
| 59 |
/// Number of alive teams when match completed (0 = draw, 1 = winner).
|
| 60 |
static ALIVE_AT_END: AtomicI32 = AtomicI32::new(-1);
|
| 61 |
|
| 62 |
-
///
|
| 63 |
-
#[inline]
|
| 64 |
-
unsafe fn get_ddgame() -> *mut DDGame {
|
| 65 |
-
let session = *(rb(va::G_GAME_SESSION) as *const u32);
|
| 66 |
-
if session == 0 {
|
| 67 |
-
return core::ptr::null_mut();
|
| 68 |
-
}
|
| 69 |
-
let wrapper_ptr = *((session + 0xA0) as *const *const DDGameWrapper);
|
| 70 |
-
if wrapper_ptr.is_null() {
|
| 71 |
-
return core::ptr::null_mut();
|
| 72 |
-
}
|
| 73 |
-
(*wrapper_ptr).ddgame
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
/// Dump a memory region as DWORDs with automatic classification.
|
| 77 |
///
|
| 78 |
-
///
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
use openwa_debug_proto::PointerKind;
|
| 85 |
-
|
| 86 |
-
let wa_base = rb(va::IMAGE_BASE);
|
| 87 |
-
let delta = wa_base.wrapping_sub(va::IMAGE_BASE);
|
| 88 |
-
|
| 89 |
-
let _ = log_line(&format!(
|
| 90 |
-
"\n=== {}+0x{:04X}..0x{:04X} ===",
|
| 91 |
-
struct_name,
|
| 92 |
-
offset,
|
| 93 |
-
offset + size
|
| 94 |
-
));
|
| 95 |
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
if val == 0 {
|
| 101 |
-
continue;
|
| 102 |
-
}
|
| 103 |
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
let _ = log_line(&format!(
|
| 109 |
-
" +0x{:04X}: 0x{:08X} [VTABLE] g:0x{:08X} {}",
|
| 110 |
-
field_offset, val, info.ghidra_value, detail_str
|
| 111 |
-
));
|
| 112 |
-
}
|
| 113 |
-
PointerKind::Code => {
|
| 114 |
-
let _ = log_line(&format!(
|
| 115 |
-
" +0x{:04X}: 0x{:08X} [CODE] g:0x{:08X}",
|
| 116 |
-
field_offset, val, info.ghidra_value
|
| 117 |
-
));
|
| 118 |
-
}
|
| 119 |
-
PointerKind::Data => {
|
| 120 |
-
let _ = log_line(&format!(
|
| 121 |
-
" +0x{:04X}: 0x{:08X} [DATA] g:0x{:08X}",
|
| 122 |
-
field_offset, val, info.ghidra_value
|
| 123 |
-
));
|
| 124 |
-
}
|
| 125 |
-
PointerKind::Object => {
|
| 126 |
-
let _ = log_line(&format!(
|
| 127 |
-
" +0x{:04X}: 0x{:08X} [OBJECT] {}",
|
| 128 |
-
field_offset, val, detail_str
|
| 129 |
-
));
|
| 130 |
-
}
|
| 131 |
-
PointerKind::Heap => {
|
| 132 |
-
let _ = log_line(&format!(
|
| 133 |
-
" +0x{:04X}: 0x{:08X} [ptr] {}",
|
| 134 |
-
field_offset, val, detail_str
|
| 135 |
-
));
|
| 136 |
-
}
|
| 137 |
-
}
|
| 138 |
-
} else if val < 0x10000 {
|
| 139 |
-
let _ = log_line(&format!(
|
| 140 |
-
" +0x{:04X}: 0x{:08X} [small={}]",
|
| 141 |
-
field_offset, val, val
|
| 142 |
-
));
|
| 143 |
-
} else {
|
| 144 |
-
let _ = log_line(&format!(" +0x{:04X}: 0x{:08X} [value]", field_offset, val));
|
| 145 |
-
}
|
| 146 |
}
|
| 147 |
}
|
| 148 |
|
| 149 |
/// Count how many teams have at least one alive worm.
|
| 150 |
///
|
| 151 |
-
/// Iterates all 8 worm slots per team and checks `health > 0` (reliable
|
| 152 |
-
/// even before sentinel headers are fully populated). This is more
|
| 153 |
-
/// conservative than checking headers, which may be populated lazily.
|
| 154 |
-
///
|
| 155 |
/// Returns (alive_team_count, total_team_count).
|
| 156 |
unsafe fn count_alive_teams(ddgame: *const DDGame) -> (i32, i32) {
|
| 157 |
let arena_base = (ddgame as u32) + offsets::TEAM_ARENA_STATE as u32;
|
|
@@ -162,7 +76,6 @@ unsafe fn count_alive_teams(ddgame: *const DDGame) -> (i32, i32) {
|
|
| 162 |
}
|
| 163 |
|
| 164 |
let mut alive_teams = 0i32;
|
| 165 |
-
// Teams are 1-indexed in the arena (index 0 is unused preamble).
|
| 166 |
for t in 1..=team_count as usize {
|
| 167 |
let mut team_has_alive = false;
|
| 168 |
for w in 1..=8usize {
|
|
@@ -179,63 +92,6 @@ unsafe fn count_alive_teams(ddgame: *const DDGame) -> (i32, i32) {
|
|
| 179 |
(alive_teams, team_count)
|
| 180 |
}
|
| 181 |
|
| 182 |
-
/// Hook for TurnManager_ProcessFrame (stdcall, 1 param = TurnGame*).
|
| 183 |
-
unsafe extern "stdcall" fn hook_turn_manager(turngame: u32) {
|
| 184 |
-
// Check debug sync BEFORE processing the frame — allows pausing at frame boundary
|
| 185 |
-
let ddgame = get_ddgame();
|
| 186 |
-
if !ddgame.is_null() {
|
| 187 |
-
let game_frame = (*ddgame).frame_counter;
|
| 188 |
-
crate::debug_sync::on_frame_start(game_frame);
|
| 189 |
-
|
| 190 |
-
// Hardware watchpoint: arm once at the watch frame
|
| 191 |
-
static WATCH_ARMED: core::sync::atomic::AtomicBool =
|
| 192 |
-
core::sync::atomic::AtomicBool::new(false);
|
| 193 |
-
if !WATCH_ARMED.load(Ordering::Relaxed) {
|
| 194 |
-
if let Ok(val) = std::env::var("OPENWA_WATCH_FRAME") {
|
| 195 |
-
let target: i32 = val.parse().unwrap_or(0);
|
| 196 |
-
if game_frame >= target {
|
| 197 |
-
WATCH_ARMED.store(true, Ordering::Relaxed);
|
| 198 |
-
crate::debug_watchpoint::prepare();
|
| 199 |
-
// Select watchpoint base: DDGame, DDGameWrapper, or Display
|
| 200 |
-
let watch_base = if std::env::var("OPENWA_WATCH_DISPLAY").is_ok() {
|
| 201 |
-
let session = *(rb(va::G_GAME_SESSION) as *const u32);
|
| 202 |
-
let wrapper = *((session + 0xA0) as *const *const u8);
|
| 203 |
-
*(wrapper.add(0x4D0) as *const *mut u8)
|
| 204 |
-
} else if std::env::var("OPENWA_WATCH_WRAPPER").is_ok() {
|
| 205 |
-
let session = *(rb(va::G_GAME_SESSION) as *const u32);
|
| 206 |
-
*((session + 0xA0) as *const *mut u8)
|
| 207 |
-
} else {
|
| 208 |
-
ddgame as *mut u8
|
| 209 |
-
};
|
| 210 |
-
crate::debug_watchpoint::on_ddgame_alloc(watch_base);
|
| 211 |
-
}
|
| 212 |
-
}
|
| 213 |
-
}
|
| 214 |
-
}
|
| 215 |
-
|
| 216 |
-
// Call original
|
| 217 |
-
let orig: unsafe extern "stdcall" fn(u32) =
|
| 218 |
-
core::mem::transmute(ORIG_TURN_MANAGER.load(Ordering::Relaxed));
|
| 219 |
-
orig(turngame);
|
| 220 |
-
|
| 221 |
-
let frame = FRAMES_PROCESSED.fetch_add(1, Ordering::Relaxed) + 1;
|
| 222 |
-
|
| 223 |
-
if ddgame.is_null() {
|
| 224 |
-
return;
|
| 225 |
-
}
|
| 226 |
-
|
| 227 |
-
// Fast-forward for replay test
|
| 228 |
-
if FAST_FORWARD.load(Ordering::Relaxed) {
|
| 229 |
-
(*ddgame).fast_forward_active = 1;
|
| 230 |
-
}
|
| 231 |
-
|
| 232 |
-
// Milestone detection (check every 50 frames to reduce overhead).
|
| 233 |
-
// Skip the first 100 frames to let game state fully initialize.
|
| 234 |
-
if frame >= 100 && frame.is_multiple_of(50) {
|
| 235 |
-
check_milestones(ddgame, frame);
|
| 236 |
-
}
|
| 237 |
-
}
|
| 238 |
-
|
| 239 |
/// Check and update gameplay milestones.
|
| 240 |
unsafe fn check_milestones(ddgame: *const DDGame, frame: u32) {
|
| 241 |
if MATCH_COMPLETED.load(Ordering::Relaxed) {
|
|
@@ -249,32 +105,26 @@ unsafe fn check_milestones(ddgame: *const DDGame, frame: u32) {
|
|
| 249 |
MATCH_STARTED.store(true, Ordering::Relaxed);
|
| 250 |
TEAMS_AT_START.store(alive as u32, Ordering::Relaxed);
|
| 251 |
}
|
| 252 |
-
} else {
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
COMPLETION_FRAME.store(frame, Ordering::Relaxed);
|
| 257 |
-
ALIVE_AT_END.store(alive, Ordering::Relaxed);
|
| 258 |
-
}
|
| 259 |
}
|
| 260 |
}
|
| 261 |
|
| 262 |
/// Write gameplay milestone report to the validation log.
|
| 263 |
///
|
| 264 |
/// Called from DLL_PROCESS_DETACH to provide a final summary of game progress.
|
| 265 |
-
/// Uses `[GAMEPLAY PASS]` / `[GAMEPLAY FAIL]` markers so the PowerShell script
|
| 266 |
-
/// can distinguish these from static checks.
|
| 267 |
pub fn write_gameplay_report() {
|
| 268 |
use crate::validation::log_validation;
|
| 269 |
|
| 270 |
-
let frames =
|
| 271 |
let started = MATCH_STARTED.load(Ordering::Relaxed);
|
| 272 |
let completed = MATCH_COMPLETED.load(Ordering::Relaxed);
|
| 273 |
|
| 274 |
let _ = log_validation("");
|
| 275 |
let _ = log_validation("--- Gameplay Checks ---");
|
| 276 |
|
| 277 |
-
// Milestone 1: Game initialized (frame hook was called)
|
| 278 |
if frames > 0 {
|
| 279 |
let _ = log_validation(&format!(
|
| 280 |
"[GAMEPLAY PASS] Game initialized - {} frames processed",
|
|
@@ -286,7 +136,6 @@ pub fn write_gameplay_report() {
|
|
| 286 |
);
|
| 287 |
}
|
| 288 |
|
| 289 |
-
// Milestone 2: Match started (multiple teams with alive worms)
|
| 290 |
if started {
|
| 291 |
let teams = TEAMS_AT_START.load(Ordering::Relaxed);
|
| 292 |
let _ = log_validation(&format!(
|
|
@@ -300,7 +149,6 @@ pub fn write_gameplay_report() {
|
|
| 300 |
let _ = log_validation("[GAMEPLAY FAIL] Match started - game never initialized");
|
| 301 |
}
|
| 302 |
|
| 303 |
-
// Milestone 3: Match completed (one or zero teams remain)
|
| 304 |
if completed {
|
| 305 |
let end_frame = COMPLETION_FRAME.load(Ordering::Relaxed);
|
| 306 |
let alive = ALIVE_AT_END.load(Ordering::Relaxed);
|
|
@@ -324,18 +172,7 @@ pub fn write_gameplay_report() {
|
|
| 324 |
pub fn install() -> Result<(), String> {
|
| 325 |
if std::env::var("OPENWA_REPLAY_TEST").is_ok() {
|
| 326 |
FAST_FORWARD.store(true, Ordering::Relaxed);
|
| 327 |
-
let _ = log_line("[
|
| 328 |
-
}
|
| 329 |
-
|
| 330 |
-
let _ = log_line("[Input] Hooking TurnManager_ProcessFrame");
|
| 331 |
-
|
| 332 |
-
unsafe {
|
| 333 |
-
let trampoline = hook::install(
|
| 334 |
-
"TurnManager_ProcessFrame",
|
| 335 |
-
va::TURN_MANAGER_PROCESS_FRAME,
|
| 336 |
-
hook_turn_manager as *const (),
|
| 337 |
-
)?;
|
| 338 |
-
ORIG_TURN_MANAGER.store(trampoline as u32, Ordering::Relaxed);
|
| 339 |
}
|
| 340 |
|
| 341 |
Ok(())
|
|
|
|
| 1 |
+
//! Headful replay test support: fast-forward and gameplay milestone tracking.
|
| 2 |
//!
|
| 3 |
+
//! When `OPENWA_REPLAY_TEST=1`, enables:
|
| 4 |
+
//! - **Fast-forward**: Sets DDGame+0x98B0 each frame so WA processes up to 50
|
| 5 |
+
//! game frames per render cycle (same as spacebar during replay playback).
|
| 6 |
+
//! - **Gameplay milestones**: Tracks match start/completion via alive team counting,
|
| 7 |
+
//! reported at DLL detach for machine-readable test validation.
|
| 8 |
//!
|
| 9 |
+
//! These features are specific to headful (interactive) replay testing.
|
| 10 |
+
//! Headless tests run at maximum CPU speed without needing fast-forward,
|
| 11 |
+
//! and use log comparison instead of milestone markers.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
|
| 14 |
|
|
|
|
| 15 |
use crate::log_line;
|
|
|
|
| 16 |
use openwa_core::engine::ddgame::{offsets, TeamArenaRef};
|
| 17 |
+
use openwa_core::engine::game_session;
|
| 18 |
+
use openwa_core::engine::DDGame;
|
| 19 |
|
| 20 |
+
// ---------------------------------------------------------------------------
|
| 21 |
+
// Fast-forward
|
| 22 |
+
// ---------------------------------------------------------------------------
|
| 23 |
|
| 24 |
+
/// Whether fast-forward is active (set once at install, read every frame).
|
| 25 |
static FAST_FORWARD: AtomicBool = AtomicBool::new(false);
|
| 26 |
|
| 27 |
// ---------------------------------------------------------------------------
|
| 28 |
// Gameplay milestone tracking
|
| 29 |
// ---------------------------------------------------------------------------
|
| 30 |
|
|
|
|
|
|
|
|
|
|
| 31 |
/// Milestone: at least 2 teams with alive worms detected.
|
| 32 |
static MATCH_STARTED: AtomicBool = AtomicBool::new(false);
|
| 33 |
|
|
|
|
| 43 |
/// Number of alive teams when match completed (0 = draw, 1 = winner).
|
| 44 |
static ALIVE_AT_END: AtomicI32 = AtomicI32::new(-1);
|
| 45 |
|
| 46 |
+
/// Called every frame from frame_hook (after the original TurnManager runs).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
///
|
| 48 |
+
/// Handles fast-forward flag setting and periodic milestone checks.
|
| 49 |
+
pub unsafe fn on_frame(frame: u32) {
|
| 50 |
+
let ddgame = game_session::get_ddgame();
|
| 51 |
+
if ddgame.is_null() {
|
| 52 |
+
return;
|
| 53 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
+
// Re-set fast-forward each frame (WA clears it at turn boundaries)
|
| 56 |
+
if FAST_FORWARD.load(Ordering::Relaxed) {
|
| 57 |
+
(*ddgame).fast_forward_active = 1;
|
| 58 |
+
}
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
// Milestone detection (check every 50 frames to reduce overhead).
|
| 61 |
+
// Skip the first 100 frames to let game state fully initialize.
|
| 62 |
+
if frame >= 100 && frame.is_multiple_of(50) {
|
| 63 |
+
check_milestones(ddgame, frame);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
}
|
| 65 |
}
|
| 66 |
|
| 67 |
/// Count how many teams have at least one alive worm.
|
| 68 |
///
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
/// Returns (alive_team_count, total_team_count).
|
| 70 |
unsafe fn count_alive_teams(ddgame: *const DDGame) -> (i32, i32) {
|
| 71 |
let arena_base = (ddgame as u32) + offsets::TEAM_ARENA_STATE as u32;
|
|
|
|
| 76 |
}
|
| 77 |
|
| 78 |
let mut alive_teams = 0i32;
|
|
|
|
| 79 |
for t in 1..=team_count as usize {
|
| 80 |
let mut team_has_alive = false;
|
| 81 |
for w in 1..=8usize {
|
|
|
|
| 92 |
(alive_teams, team_count)
|
| 93 |
}
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
/// Check and update gameplay milestones.
|
| 96 |
unsafe fn check_milestones(ddgame: *const DDGame, frame: u32) {
|
| 97 |
if MATCH_COMPLETED.load(Ordering::Relaxed) {
|
|
|
|
| 105 |
MATCH_STARTED.store(true, Ordering::Relaxed);
|
| 106 |
TEAMS_AT_START.store(alive as u32, Ordering::Relaxed);
|
| 107 |
}
|
| 108 |
+
} else if alive <= 1 {
|
| 109 |
+
MATCH_COMPLETED.store(true, Ordering::Relaxed);
|
| 110 |
+
COMPLETION_FRAME.store(frame, Ordering::Relaxed);
|
| 111 |
+
ALIVE_AT_END.store(alive, Ordering::Relaxed);
|
|
|
|
|
|
|
|
|
|
| 112 |
}
|
| 113 |
}
|
| 114 |
|
| 115 |
/// Write gameplay milestone report to the validation log.
|
| 116 |
///
|
| 117 |
/// Called from DLL_PROCESS_DETACH to provide a final summary of game progress.
|
|
|
|
|
|
|
| 118 |
pub fn write_gameplay_report() {
|
| 119 |
use crate::validation::log_validation;
|
| 120 |
|
| 121 |
+
let frames = super::frame_hook::frames_processed();
|
| 122 |
let started = MATCH_STARTED.load(Ordering::Relaxed);
|
| 123 |
let completed = MATCH_COMPLETED.load(Ordering::Relaxed);
|
| 124 |
|
| 125 |
let _ = log_validation("");
|
| 126 |
let _ = log_validation("--- Gameplay Checks ---");
|
| 127 |
|
|
|
|
| 128 |
if frames > 0 {
|
| 129 |
let _ = log_validation(&format!(
|
| 130 |
"[GAMEPLAY PASS] Game initialized - {} frames processed",
|
|
|
|
| 136 |
);
|
| 137 |
}
|
| 138 |
|
|
|
|
| 139 |
if started {
|
| 140 |
let teams = TEAMS_AT_START.load(Ordering::Relaxed);
|
| 141 |
let _ = log_validation(&format!(
|
|
|
|
| 149 |
let _ = log_validation("[GAMEPLAY FAIL] Match started - game never initialized");
|
| 150 |
}
|
| 151 |
|
|
|
|
| 152 |
if completed {
|
| 153 |
let end_frame = COMPLETION_FRAME.load(Ordering::Relaxed);
|
| 154 |
let alive = ALIVE_AT_END.load(Ordering::Relaxed);
|
|
|
|
| 172 |
pub fn install() -> Result<(), String> {
|
| 173 |
if std::env::var("OPENWA_REPLAY_TEST").is_ok() {
|
| 174 |
FAST_FORWARD.store(true, Ordering::Relaxed);
|
| 175 |
+
let _ = log_line("[ReplayTest] Replay test mode — fast-forward enabled");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
}
|
| 177 |
|
| 178 |
Ok(())
|
crates/openwa-wormkit/src/validation/mod.rs
CHANGED
|
@@ -1190,7 +1190,7 @@ fn dump_worm_tasks() {
|
|
| 1190 |
// ---------------------------------------------------------------------------
|
| 1191 |
|
| 1192 |
fn dump_turngame() {
|
| 1193 |
-
use crate::replacements::
|
| 1194 |
use openwa_core::task::CTaskTurnGame;
|
| 1195 |
|
| 1196 |
let _ = log_validation("");
|
|
@@ -1366,7 +1366,7 @@ fn dump_turngame() {
|
|
| 1366 |
// we can verify against what WA actually stores at runtime.
|
| 1367 |
|
| 1368 |
fn dump_ctask_children() {
|
| 1369 |
-
use crate::replacements::
|
| 1370 |
|
| 1371 |
let _ = log_validation("");
|
| 1372 |
let _ = log_validation("--- CTask children sub-struct dump ---");
|
|
@@ -1458,7 +1458,7 @@ fn dump_ctask_children() {
|
|
| 1458 |
// ---------------------------------------------------------------------------
|
| 1459 |
|
| 1460 |
fn dump_ctaskteam_entities() {
|
| 1461 |
-
use crate::replacements::
|
| 1462 |
use openwa_core::task::CTaskTeam;
|
| 1463 |
|
| 1464 |
let _ = log_validation("");
|
|
|
|
| 1190 |
// ---------------------------------------------------------------------------
|
| 1191 |
|
| 1192 |
fn dump_turngame() {
|
| 1193 |
+
use crate::replacements::debug_utils::dump_region;
|
| 1194 |
use openwa_core::task::CTaskTurnGame;
|
| 1195 |
|
| 1196 |
let _ = log_validation("");
|
|
|
|
| 1366 |
// we can verify against what WA actually stores at runtime.
|
| 1367 |
|
| 1368 |
fn dump_ctask_children() {
|
| 1369 |
+
use crate::replacements::debug_utils::dump_region;
|
| 1370 |
|
| 1371 |
let _ = log_validation("");
|
| 1372 |
let _ = log_validation("--- CTask children sub-struct dump ---");
|
|
|
|
| 1458 |
// ---------------------------------------------------------------------------
|
| 1459 |
|
| 1460 |
fn dump_ctaskteam_entities() {
|
| 1461 |
+
use crate::replacements::debug_utils::dump_region;
|
| 1462 |
use openwa_core::task::CTaskTeam;
|
| 1463 |
|
| 1464 |
let _ = log_validation("");
|