feat: add CTaskFire and CTaskTurnGame structs with runtime validation
Browse files- CTaskFire (0xD8 bytes, class_type=0x18): spawn coords, timer, burn_rate,
spread_counter, lifetime, slot_index fields
- CTaskTurnGame (0x2E0 bytes): 15 named fields covering timers (turn_timer
+0x18C, idle_timer +0x184, retreat_timer +0x178), team tracking
(current_team, current_worm, arena_team, arena_worm), and frame counters
(active_worm_frames, retreat_frames, _timer_scale); all offsets runtime-
validated via offset_of! checks (15 PASS)
- dump_turngame(): finds TurnGame via vtable scan + SharedDataTable, dumps
known fields and full 0x2E0-byte raw region via dump_region
- dump_region refactored from DDGame-specific to generic *const u8 base
- replay-test.ps1: fix launcher invocation (pass $waExe explicitly so
replay file is forwarded as a WA.exe arg, not misinterpreted as exe path)
- Debug UI inspector sections for CTaskFire and CTaskTurnGame
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@@ -611,6 +611,107 @@ pub struct CTaskFire {
|
|
| 611 |
|
| 612 |
const _: () = assert!(core::mem::size_of::<CTaskFire>() == 0xD8);
|
| 613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
// ============================================================
|
| 615 |
// Derived task overlays — for accessing task-specific fields
|
| 616 |
// beyond or within CGameTask that differ per task type.
|
|
|
|
| 611 |
|
| 612 |
const _: () = assert!(core::mem::size_of::<CTaskFire>() == 0xD8);
|
| 613 |
|
| 614 |
+
/// Root turn-controller task — one instance per game, parent of the entire entity tree.
|
| 615 |
+
///
|
| 616 |
+
/// Every worm, team, projectile, and environment task is a child (direct or indirect)
|
| 617 |
+
/// of this node. `CTaskTurnGame` drives the turn loop: it processes 50 game frames
|
| 618 |
+
/// per second via `CTaskTurnGame__TurnManager_ProcessFrame` (0x55FDA0), which is
|
| 619 |
+
/// called from HandleMessage case 2 (FrameFinish).
|
| 620 |
+
///
|
| 621 |
+
/// Inheritance: CTask → CTaskTeam → CTaskTurnGame. Class type 6.
|
| 622 |
+
/// Constructor: `CTaskTurnGame__Constructor` (0x55B2B1).
|
| 623 |
+
/// vtable: `CTaskTurnGame__vtable` (0x00669F70), 20 slots.
|
| 624 |
+
/// Total size: 0x2E0 bytes.
|
| 625 |
+
///
|
| 626 |
+
/// Key vtable slots:
|
| 627 |
+
/// [0] 0x55B5E0 — task-tree state snapshot serialiser
|
| 628 |
+
/// [1] 0x55B540 — destructor / Free
|
| 629 |
+
/// [2] 0x55DC00 — HandleMessage (30+ message types)
|
| 630 |
+
/// [3] 0x5612E0 — HUD data query (responds to msg 0x7D3)
|
| 631 |
+
///
|
| 632 |
+
/// All timers decrement by 20 ms per frame (= 1000 ms / 50 fps).
|
| 633 |
+
#[repr(C)]
|
| 634 |
+
pub struct CTaskTurnGame {
|
| 635 |
+
/// 0x00-0x2F: CTask base (vtable, parent, children, shared_data, ddgame, …)
|
| 636 |
+
pub base: CTask,
|
| 637 |
+
/// 0x30-0xDB: CTaskTeam base region (opaque — includes secondary interface pointer
|
| 638 |
+
/// at +0x30, team position/physics fields, and per-team bookkeeping).
|
| 639 |
+
pub _ctaskteam: [u8; 0xAC],
|
| 640 |
+
|
| 641 |
+
// ---- CTaskTurnGame-specific fields (0xDC onwards) ----
|
| 642 |
+
|
| 643 |
+
pub _unknown_dc: u32,
|
| 644 |
+
pub _unknown_e0: u32,
|
| 645 |
+
/// 0xE4: turn-seed: ~(DDGame+0x45EC % 9000), used for random initialisation.
|
| 646 |
+
pub _turn_seed: u32,
|
| 647 |
+
/// 0xE8: number of teams in this game (copy of DDGame+0x44C).
|
| 648 |
+
pub num_teams: u32,
|
| 649 |
+
pub _unknown_ec: [u8; 0x1C],
|
| 650 |
+
/// 0x108: "worm active" flag — non-zero while the current worm is shooting or
|
| 651 |
+
/// moving. While non-zero, `turn_timer` is paused.
|
| 652 |
+
/// A copy is also written to DDGame+0x7234 at construction.
|
| 653 |
+
pub worm_active: u32,
|
| 654 |
+
pub _unknown_10c: [u8; 0x20],
|
| 655 |
+
/// 0x12C: active team index, **1-based** (0 = no active team).
|
| 656 |
+
/// Used to index per-team sound tables in DDGame (stride 0xF0 at DDGame+0x774C).
|
| 657 |
+
pub current_team: u32,
|
| 658 |
+
/// 0x130: active worm index within the current team, **0-based** (stride 0x9C).
|
| 659 |
+
pub current_worm: u32,
|
| 660 |
+
/// 0x134: arena team index used for TeamArenaState lookups.
|
| 661 |
+
pub arena_team: u32,
|
| 662 |
+
/// 0x138: arena worm index used for TeamArenaState lookups.
|
| 663 |
+
pub arena_worm: u32,
|
| 664 |
+
pub _unknown_13c: u32,
|
| 665 |
+
/// 0x140: set to 1 at construction; role unclear.
|
| 666 |
+
pub _init_140: u32,
|
| 667 |
+
pub _unknown_144: u32,
|
| 668 |
+
pub _unknown_148: u32,
|
| 669 |
+
/// 0x14C: set to 1 at construction and reset by turn-flow restart; role unclear.
|
| 670 |
+
pub _init_14c: u32,
|
| 671 |
+
/// 0x150: set to 1 when the active worm's firing phase ends and retreat begins.
|
| 672 |
+
pub turn_ended: u32,
|
| 673 |
+
/// 0x154: set to 1 when the scheme turn time is 0 (no time limit).
|
| 674 |
+
pub no_time_limit: u32,
|
| 675 |
+
pub _unknown_158: [u8; 0x14],
|
| 676 |
+
/// 0x16C: set to 1 once the "5 seconds left" warning sound has played.
|
| 677 |
+
pub warning_sound_played: u32,
|
| 678 |
+
pub _unknown_170: u32,
|
| 679 |
+
/// 0x174: set while a worm is transitioning out of a special state (e.g. landing
|
| 680 |
+
/// after a fly animation); cleared once settled.
|
| 681 |
+
pub _worm_state_transition: u32,
|
| 682 |
+
/// 0x178: retreat-phase countdown (ms). Initialised from `retreat_time_max` when
|
| 683 |
+
/// firing ends; decrements 20 ms/frame. Dispatches message 0x35 at zero.
|
| 684 |
+
pub retreat_timer: i32,
|
| 685 |
+
/// 0x17C: initial retreat duration (ms) loaded from the scheme at turn start.
|
| 686 |
+
pub retreat_time_max: i32,
|
| 687 |
+
/// 0x180: last tick when the displayed "seconds remaining" value was updated.
|
| 688 |
+
pub last_second_tick: u32,
|
| 689 |
+
/// 0x184: idle-phase timer (ms). Initialised to `scheme_turn_time × 1000`.
|
| 690 |
+
/// Decrements 20 ms/frame **only while `worm_active == 0`** (timer pauses when
|
| 691 |
+
/// the worm is moving/shooting). When it reaches zero, sudden-death is triggered.
|
| 692 |
+
/// This is NOT the per-turn HUD countdown — see `turn_timer` at +0x18C instead.
|
| 693 |
+
pub idle_timer: i32,
|
| 694 |
+
/// 0x188: smoothed display copy of `turn_timer`; tracks it at ≤ 0xCCC ms/frame
|
| 695 |
+
/// to avoid visual jumping on the HUD.
|
| 696 |
+
pub turn_timer_display: i32,
|
| 697 |
+
/// 0x18C: per-turn countdown timer (ms) — this is what the HUD shows.
|
| 698 |
+
/// Decrements 20 ms/frame every game frame. When it reaches zero,
|
| 699 |
+
/// `FUN_0055f4f0` fires (turn ends / retreat begins).
|
| 700 |
+
pub turn_timer: i32,
|
| 701 |
+
pub _unknown_190: [u8; 0x8],
|
| 702 |
+
/// 0x198: turn-timer visual pulse intensity (Fixed 16.16); grows as time runs low.
|
| 703 |
+
pub timer_flash: u32,
|
| 704 |
+
pub _unknown_19c: [u8; 0x138],
|
| 705 |
+
/// 0x2D4: frame count accumulated while this team held the active worm.
|
| 706 |
+
pub active_worm_frames: u32,
|
| 707 |
+
/// 0x2D8: frame count accumulated during the retreat phase.
|
| 708 |
+
pub retreat_frames: u32,
|
| 709 |
+
/// 0x2DC: timer scale factor derived from scheme (2, 3, or 4; default 4).
|
| 710 |
+
pub _timer_scale: u32,
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
const _: () = assert!(core::mem::size_of::<CTaskTurnGame>() == 0x2E0);
|
| 714 |
+
|
| 715 |
// ============================================================
|
| 716 |
// Derived task overlays — for accessing task-specific fields
|
| 717 |
// beyond or within CGameTask that differ per task type.
|
|
@@ -3,7 +3,7 @@ use openwa_core::address::va;
|
|
| 3 |
use openwa_core::ddgame::DDGame;
|
| 4 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 5 |
use openwa_core::rebase::rb;
|
| 6 |
-
use openwa_core::task::{CTask, CTaskCloud, CTaskFire, CTaskMine, CTaskOilDrum, CTaskWorm};
|
| 7 |
|
| 8 |
use crate::log;
|
| 9 |
|
|
@@ -460,6 +460,30 @@ impl DebugApp {
|
|
| 460 |
});
|
| 461 |
}
|
| 462 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
// --- CTaskFire-specific fields ---
|
| 464 |
if name == "CTaskFire" {
|
| 465 |
let fire = addr as *const CTaskFire;
|
|
|
|
| 3 |
use openwa_core::ddgame::DDGame;
|
| 4 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 5 |
use openwa_core::rebase::rb;
|
| 6 |
+
use openwa_core::task::{CTask, CTaskCloud, CTaskFire, CTaskMine, CTaskOilDrum, CTaskTurnGame, CTaskWorm};
|
| 7 |
|
| 8 |
use crate::log;
|
| 9 |
|
|
|
|
| 460 |
});
|
| 461 |
}
|
| 462 |
|
| 463 |
+
// --- CTaskTurnGame-specific fields ---
|
| 464 |
+
if name == "CTaskTurnGame" {
|
| 465 |
+
let tg = addr as *const CTaskTurnGame;
|
| 466 |
+
egui::CollapsingHeader::new("CTaskTurnGame")
|
| 467 |
+
.default_open(true)
|
| 468 |
+
.show(ui, |ui| {
|
| 469 |
+
egui::Grid::new("tg_grid").striped(true).show(ui, |ui| {
|
| 470 |
+
ui.label("current_team"); ui.label(format!("{}", (*tg).current_team)); ui.end_row();
|
| 471 |
+
ui.label("current_worm"); ui.label(format!("{}", (*tg).current_worm)); ui.end_row();
|
| 472 |
+
ui.label("arena_team"); ui.label(format!("{}", (*tg).arena_team)); ui.end_row();
|
| 473 |
+
ui.label("arena_worm"); ui.label(format!("{}", (*tg).arena_worm)); ui.end_row();
|
| 474 |
+
ui.label("worm_active"); ui.label(format!("{}", (*tg).worm_active != 0)); ui.end_row();
|
| 475 |
+
ui.label("turn_ended"); ui.label(format!("{}", (*tg).turn_ended != 0)); ui.end_row();
|
| 476 |
+
ui.label("no_time_lim"); ui.label(format!("{}", (*tg).no_time_limit != 0)); ui.end_row();
|
| 477 |
+
ui.label("turn_timer"); ui.label(format!("{:.1}s", (*tg).turn_timer as f32 / 1000.0)); ui.end_row();
|
| 478 |
+
ui.label("retreat"); ui.label(format!("{:.1}s", (*tg).retreat_timer as f32 / 1000.0)); ui.end_row();
|
| 479 |
+
ui.label("idle_timer"); ui.label(format!("{:.1}s", (*tg).idle_timer as f32 / 1000.0)); ui.end_row();
|
| 480 |
+
ui.label("num_teams"); ui.label(format!("{}", (*tg).num_teams)); ui.end_row();
|
| 481 |
+
ui.label("active_frm"); ui.label(format!("{}", (*tg).active_worm_frames)); ui.end_row();
|
| 482 |
+
ui.label("retreat_frm"); ui.label(format!("{}", (*tg).retreat_frames)); ui.end_row();
|
| 483 |
+
});
|
| 484 |
+
});
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
// --- CTaskFire-specific fields ---
|
| 488 |
if name == "CTaskFire" {
|
| 489 |
let fire = addr as *const CTaskFire;
|
|
@@ -51,16 +51,28 @@ pub unsafe fn can_read(ptr: u32, size: u32) -> bool {
|
|
| 51 |
ptr >= 0x10000 && IsBadReadPtr(ptr as *const u8, size) == 0
|
| 52 |
}
|
| 53 |
|
| 54 |
-
/// Dump a region
|
| 55 |
///
|
| 56 |
-
///
|
| 57 |
-
///
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
let wa_base = rb(va::IMAGE_BASE);
|
| 61 |
let delta = wa_base.wrapping_sub(va::IMAGE_BASE);
|
| 62 |
|
| 63 |
-
let _ = log_line(&format!("\n===
|
| 64 |
|
| 65 |
let dword_count = size / 4;
|
| 66 |
for i in 0..dword_count {
|
|
|
|
| 51 |
ptr >= 0x10000 && IsBadReadPtr(ptr as *const u8, size) == 0
|
| 52 |
}
|
| 53 |
|
| 54 |
+
/// Dump a memory region as DWORDs with automatic classification.
|
| 55 |
///
|
| 56 |
+
/// Each non-zero DWORD is classified as:
|
| 57 |
+
/// - `[VTABLE]` — points into .rdata (likely a vtable or function pointer table);
|
| 58 |
+
/// also dereferences `vt[0]` if readable
|
| 59 |
+
/// - `[CODE]` — points into .text
|
| 60 |
+
/// - `[DATA]` — points into .data/.bss
|
| 61 |
+
/// - `[OBJECT]` — heap pointer whose first DWORD is a vtable; prints vtable address
|
| 62 |
+
/// - `[ptr]` — any other readable heap pointer; prints the dereferenced DWORD
|
| 63 |
+
/// - `[small=N]` — value < 0x10000 (integer / enum / flag)
|
| 64 |
+
/// - `[value]` — anything else
|
| 65 |
+
///
|
| 66 |
+
/// # Parameters
|
| 67 |
+
/// - `base_ptr`: start of the object to dump (e.g. `turngame as *const u8`)
|
| 68 |
+
/// - `offset`: byte offset within `base_ptr` to start the dump
|
| 69 |
+
/// - `size`: number of bytes to dump (must be a multiple of 4)
|
| 70 |
+
/// - `struct_name`: used as the prefix in log lines (e.g. `"CTaskTurnGame"`)
|
| 71 |
+
pub unsafe fn dump_region(base_ptr: *const u8, offset: usize, size: usize, struct_name: &str) {
|
| 72 |
let wa_base = rb(va::IMAGE_BASE);
|
| 73 |
let delta = wa_base.wrapping_sub(va::IMAGE_BASE);
|
| 74 |
|
| 75 |
+
let _ = log_line(&format!("\n=== {}+0x{:04X}..0x{:04X} ===", struct_name, offset, offset + size));
|
| 76 |
|
| 77 |
let dword_count = size / 4;
|
| 78 |
for i in 0..dword_count {
|
|
@@ -1,6 +1,6 @@
|
|
| 1 |
mod config;
|
| 2 |
mod frontend;
|
| 3 |
-
mod input;
|
| 4 |
mod scheme;
|
| 5 |
mod render;
|
| 6 |
mod sound;
|
|
|
|
| 1 |
mod config;
|
| 2 |
mod frontend;
|
| 3 |
+
pub(crate) mod input;
|
| 4 |
mod scheme;
|
| 5 |
mod render;
|
| 6 |
mod sound;
|
|
@@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicU32, Ordering};
|
|
| 11 |
|
| 12 |
use openwa_core::rebase::rb;
|
| 13 |
use openwa_core::address::va;
|
| 14 |
-
use openwa_core::task::{CTask, CGameTask, SharedDataTable};
|
| 15 |
use openwa_core::ddgame::DDGame;
|
| 16 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 17 |
|
|
@@ -296,6 +296,24 @@ fn validate_struct_offsets(result: &mut ValidationResult) {
|
|
| 296 |
check_offset!(result, DDGameWrapper, _field_4c0, 0x4C0);
|
| 297 |
check_offset!(result, DDGameWrapper, landscape, 0x4CC);
|
| 298 |
check_offset!(result, DDGameWrapper, display, 0x4D0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
}
|
| 300 |
|
| 301 |
// ---------------------------------------------------------------------------
|
|
@@ -920,6 +938,83 @@ fn dump_worm_tasks() {
|
|
| 920 |
}
|
| 921 |
}
|
| 922 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 923 |
// ---------------------------------------------------------------------------
|
| 924 |
// Public entry point — called from wormkit's run()
|
| 925 |
// ---------------------------------------------------------------------------
|
|
@@ -986,6 +1081,8 @@ pub fn run() -> Result<(), String> {
|
|
| 986 |
dump_entity_census();
|
| 987 |
let _ = log_validation(" Running worm entity dump (5s mark)...");
|
| 988 |
dump_worm_tasks();
|
|
|
|
|
|
|
| 989 |
|
| 990 |
// Wait briefly for replay to start, then dump game state
|
| 991 |
// (fast-forward finishes the replay in ~10-15s total, so dump early)
|
|
|
|
| 11 |
|
| 12 |
use openwa_core::rebase::rb;
|
| 13 |
use openwa_core::address::va;
|
| 14 |
+
use openwa_core::task::{CTask, CGameTask, CTaskTurnGame, SharedDataTable};
|
| 15 |
use openwa_core::ddgame::DDGame;
|
| 16 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 17 |
|
|
|
|
| 296 |
check_offset!(result, DDGameWrapper, _field_4c0, 0x4C0);
|
| 297 |
check_offset!(result, DDGameWrapper, landscape, 0x4CC);
|
| 298 |
check_offset!(result, DDGameWrapper, display, 0x4D0);
|
| 299 |
+
|
| 300 |
+
let _ = log_validation("");
|
| 301 |
+
let _ = log_validation(" CTaskTurnGame:");
|
| 302 |
+
check_offset!(result, CTaskTurnGame, worm_active, 0x108);
|
| 303 |
+
check_offset!(result, CTaskTurnGame, current_team, 0x12C);
|
| 304 |
+
check_offset!(result, CTaskTurnGame, current_worm, 0x130);
|
| 305 |
+
check_offset!(result, CTaskTurnGame, arena_team, 0x134);
|
| 306 |
+
check_offset!(result, CTaskTurnGame, arena_worm, 0x138);
|
| 307 |
+
check_offset!(result, CTaskTurnGame, turn_ended, 0x150);
|
| 308 |
+
check_offset!(result, CTaskTurnGame, no_time_limit, 0x154);
|
| 309 |
+
check_offset!(result, CTaskTurnGame, retreat_timer, 0x178);
|
| 310 |
+
check_offset!(result, CTaskTurnGame, retreat_time_max, 0x17C);
|
| 311 |
+
check_offset!(result, CTaskTurnGame, idle_timer, 0x184);
|
| 312 |
+
check_offset!(result, CTaskTurnGame, turn_timer_display, 0x188);
|
| 313 |
+
check_offset!(result, CTaskTurnGame, turn_timer, 0x18C);
|
| 314 |
+
check_offset!(result, CTaskTurnGame, active_worm_frames, 0x2D4);
|
| 315 |
+
check_offset!(result, CTaskTurnGame, retreat_frames, 0x2D8);
|
| 316 |
+
check_offset!(result, CTaskTurnGame, _timer_scale, 0x2DC);
|
| 317 |
}
|
| 318 |
|
| 319 |
// ---------------------------------------------------------------------------
|
|
|
|
| 938 |
}
|
| 939 |
}
|
| 940 |
|
| 941 |
+
// ---------------------------------------------------------------------------
|
| 942 |
+
// CTaskTurnGame dump
|
| 943 |
+
// ---------------------------------------------------------------------------
|
| 944 |
+
|
| 945 |
+
fn dump_turngame() {
|
| 946 |
+
use openwa_core::task::CTaskTurnGame;
|
| 947 |
+
use crate::replacements::input::dump_region;
|
| 948 |
+
|
| 949 |
+
let _ = log_validation("");
|
| 950 |
+
let _ = log_validation("--- CTaskTurnGame Dump ---");
|
| 951 |
+
|
| 952 |
+
unsafe {
|
| 953 |
+
let session_ptr = read_u32(rb(va::G_GAME_SESSION));
|
| 954 |
+
if session_ptr == 0 { let _ = log_validation(" No game session — skipping."); return; }
|
| 955 |
+
let wrapper_addr = read_u32(session_ptr + 0xA0);
|
| 956 |
+
if wrapper_addr == 0 { let _ = log_validation(" No DDGameWrapper."); return; }
|
| 957 |
+
let ddgame_ptr = read_u32(wrapper_addr + 0x488);
|
| 958 |
+
if ddgame_ptr == 0 { let _ = log_validation(" No DDGame."); return; }
|
| 959 |
+
|
| 960 |
+
// Find CTaskTurnGame via the shared_data entity table.
|
| 961 |
+
let task_land_ptr = read_u32(ddgame_ptr + 0x54C);
|
| 962 |
+
if task_land_ptr == 0 { let _ = log_validation(" CTaskLand NULL — skipping."); return; }
|
| 963 |
+
let task_land = task_land_ptr as *const openwa_core::task::CTask;
|
| 964 |
+
let shared_data_ptr = (*task_land).shared_data;
|
| 965 |
+
if shared_data_ptr.is_null() { let _ = log_validation(" shared_data NULL."); return; }
|
| 966 |
+
|
| 967 |
+
let table = SharedDataTable::from_ptr(shared_data_ptr);
|
| 968 |
+
let expected_vt = rb(va::CTASK_TURN_GAME_VTABLE);
|
| 969 |
+
let mut tg_ptr: u32 = 0;
|
| 970 |
+
for node in table.iter() {
|
| 971 |
+
let entity = (*node).entity;
|
| 972 |
+
if entity.is_null() { continue; }
|
| 973 |
+
if !is_in_rdata(read_u32(entity as u32)) { continue; }
|
| 974 |
+
if read_u32(entity as u32) == expected_vt {
|
| 975 |
+
tg_ptr = entity as u32;
|
| 976 |
+
break;
|
| 977 |
+
}
|
| 978 |
+
}
|
| 979 |
+
|
| 980 |
+
if tg_ptr == 0 {
|
| 981 |
+
let _ = log_validation(" CTaskTurnGame not found in entity table.");
|
| 982 |
+
return;
|
| 983 |
+
}
|
| 984 |
+
|
| 985 |
+
let _ = log_validation(&format!(" CTaskTurnGame @ 0x{:08X}", tg_ptr));
|
| 986 |
+
|
| 987 |
+
// Print known named fields for cross-validation.
|
| 988 |
+
let tg = &*(tg_ptr as *const CTaskTurnGame);
|
| 989 |
+
let _ = log_validation(" Known fields:");
|
| 990 |
+
let _ = log_validation(&format!(" +0x108 worm_active = {}", tg.worm_active));
|
| 991 |
+
let _ = log_validation(&format!(" +0x12C current_team = {} (1-based, 0=none)", tg.current_team));
|
| 992 |
+
let _ = log_validation(&format!(" +0x130 current_worm = {} (0-based)", tg.current_worm));
|
| 993 |
+
let _ = log_validation(&format!(" +0x134 arena_team = {}", tg.arena_team));
|
| 994 |
+
let _ = log_validation(&format!(" +0x138 arena_worm = {}", tg.arena_worm));
|
| 995 |
+
let _ = log_validation(&format!(" +0x150 turn_ended = {}", tg.turn_ended));
|
| 996 |
+
let _ = log_validation(&format!(" +0x154 no_time_limit = {}", tg.no_time_limit));
|
| 997 |
+
let _ = log_validation(&format!(" +0x178 retreat_timer = {} ms", tg.retreat_timer));
|
| 998 |
+
let _ = log_validation(&format!(" +0x17C retreat_time_max = {} ms", tg.retreat_time_max));
|
| 999 |
+
let _ = log_validation(&format!(" +0x184 idle_timer = {} ms", tg.idle_timer));
|
| 1000 |
+
let _ = log_validation(&format!(" +0x188 turn_timer_display = {} ms", tg.turn_timer_display));
|
| 1001 |
+
let _ = log_validation(&format!(" +0x18C turn_timer = {} ms", tg.turn_timer));
|
| 1002 |
+
let _ = log_validation(&format!(" +0x2D4 active_worm_frames = {}", tg.active_worm_frames));
|
| 1003 |
+
let _ = log_validation(&format!(" +0x2D8 retreat_frames = {}", tg.retreat_frames));
|
| 1004 |
+
let _ = log_validation(&format!(" +0x2DC _timer_scale = {}", tg._timer_scale));
|
| 1005 |
+
|
| 1006 |
+
// Full memory dump — classify every DWORD to discover unknown fields.
|
| 1007 |
+
// Dump in chunks to keep log lines manageable.
|
| 1008 |
+
let base = tg_ptr as *const u8;
|
| 1009 |
+
dump_region(base, 0x00, 0x30, "CTaskTurnGame"); // CTask base
|
| 1010 |
+
dump_region(base, 0x30, 0xAC, "CTaskTurnGame"); // CTaskTeam region
|
| 1011 |
+
dump_region(base, 0xDC, 0x80, "CTaskTurnGame"); // 0xDC..0x15B
|
| 1012 |
+
dump_region(base, 0x15C, 0x80, "CTaskTurnGame"); // 0x15C..0x1DB
|
| 1013 |
+
dump_region(base, 0x1DC, 0x80, "CTaskTurnGame"); // 0x1DC..0x25B
|
| 1014 |
+
dump_region(base, 0x25C, 0x84, "CTaskTurnGame"); // 0x25C..0x2DF
|
| 1015 |
+
}
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
// ---------------------------------------------------------------------------
|
| 1019 |
// Public entry point — called from wormkit's run()
|
| 1020 |
// ---------------------------------------------------------------------------
|
|
|
|
| 1081 |
dump_entity_census();
|
| 1082 |
let _ = log_validation(" Running worm entity dump (5s mark)...");
|
| 1083 |
dump_worm_tasks();
|
| 1084 |
+
let _ = log_validation(" Running TurnGame dump (5s mark)...");
|
| 1085 |
+
dump_turngame();
|
| 1086 |
|
| 1087 |
// Wait briefly for replay to start, then dump game state
|
| 1088 |
// (fast-forward finishes the replay in ~10-15s total, so dump early)
|
|
@@ -51,7 +51,7 @@ $env:OPENWA_REPLAY_TEST = "1"
|
|
| 51 |
$env:OPENWA_WA_PATH = $waExe
|
| 52 |
|
| 53 |
# The launcher blocks until WA.exe exits, so run it as a background job for timeout support.
|
| 54 |
-
$proc = Start-Process -FilePath $launcher -ArgumentList "--minimized `"$ReplayFile`"" -PassThru
|
| 55 |
$timeout = 150 # seconds
|
| 56 |
if (-not $proc.WaitForExit($timeout * 1000)) {
|
| 57 |
Write-Host "WARNING: launcher did not exit within ${timeout}s, killing..." -ForegroundColor Red
|
|
|
|
| 51 |
$env:OPENWA_WA_PATH = $waExe
|
| 52 |
|
| 53 |
# The launcher blocks until WA.exe exits, so run it as a background job for timeout support.
|
| 54 |
+
$proc = Start-Process -FilePath $launcher -ArgumentList "--minimized `"$waExe`" `"$ReplayFile`"" -PassThru
|
| 55 |
$timeout = 150 # seconds
|
| 56 |
if (-not $proc.WaitForExit($timeout * 1000)) {
|
| 57 |
Write-Host "WARNING: launcher did not exit within ${timeout}s, killing..." -ForegroundColor Red
|