feat: extract TurnGameCtx struct from CTaskTurnGame._game_ctx
Browse filesFull constructor analysis of CTaskTeam__Constructor_Maybe (0x550EB0) and
CTaskTurnGame__Constructor (0x55B2A0) revealed the layout of all 0xAC bytes:
land_height / land_height_2 (+0x10/+0x14): landscape height as Fixed16.16
from DDGame+0x5E0, duplicated; 696.0 confirmed at runtime.
_sentinel_18/28/38 (+0x18/+0x28/+0x38): always -1 at construction,
evenly spaced 0x10 bytes apart; role unknown.
team_count (+0x4C): *(byte)(GameInfo+0x44C) at construction time.
_slot_d0 (+0xA0): return value of FUN_00525f50(0), observed 15.
_hud_textbox_a/b (+0xA4/+0xA8): DDDisplay textbox handles created by
FUN_005514d0 when DDGame+0x7EF8 != 0 (display active).
Replaces the opaque _game_ctx: [u8; 0xAC] with the named TurnGameCtx struct.
Adds 9 offset checks to validation; 127/127 PASS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@@ -683,6 +683,77 @@ pub struct CTaskFire {
|
|
| 683 |
|
| 684 |
const _: () = assert!(core::mem::size_of::<CTaskFire>() == 0xD8);
|
| 685 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 686 |
/// Root turn-controller task β one instance per game, parent of the entire entity tree.
|
| 687 |
///
|
| 688 |
/// Every worm, team, projectile, and environment task is a child (direct or indirect)
|
|
@@ -706,22 +777,9 @@ const _: () = assert!(core::mem::size_of::<CTaskFire>() == 0xD8);
|
|
| 706 |
pub struct CTaskTurnGame {
|
| 707 |
/// 0x00-0x2F: CTask base (vtable, parent, children, shared_data, ddgame, β¦)
|
| 708 |
pub base: CTask,
|
| 709 |
-
/// 0x30
|
| 710 |
-
///
|
| 711 |
-
|
| 712 |
-
/// class_type-5 object with primary vtable 0x669E34 and secondary interface vtable
|
| 713 |
-
/// 0x669C44 at +0x30. CTaskTurnGame then overrides the primary vtable and
|
| 714 |
-
/// class_type.
|
| 715 |
-
///
|
| 716 |
-
/// Known non-zero fields inside this region (all offsets relative to object base):
|
| 717 |
-
/// +0x30: secondary interface vtable ptr (0x669C44 Ghidra)
|
| 718 |
-
/// +0x40 / +0x44: landscape height as Fixed16.16 (e.g. Fixed(696.0) for 696px map)
|
| 719 |
-
/// +0x48 / +0x58 / +0x68: Fixed(-1) β boundary sentinels
|
| 720 |
-
/// +0x70 / +0x74: Fixed(1.0) β x/y scale factors
|
| 721 |
-
/// +0x7C: team count (copy of DDGame+0x44C)
|
| 722 |
-
/// +0xD0: small integer (β total worm count)
|
| 723 |
-
/// +0xD4 / +0xD8: heap pointers (role unknown)
|
| 724 |
-
pub _game_ctx: [u8; 0xAC],
|
| 725 |
|
| 726 |
// ---- CTaskTurnGame-specific fields (0xDC onwards) ----
|
| 727 |
|
|
|
|
| 683 |
|
| 684 |
const _: () = assert!(core::mem::size_of::<CTaskFire>() == 0xD8);
|
| 685 |
|
| 686 |
+
/// Embedded intermediate game-context sub-object within `CTaskTurnGame`.
|
| 687 |
+
///
|
| 688 |
+
/// This is the memory region at `CTaskTurnGame+0x30..+0xDB` (0xAC bytes).
|
| 689 |
+
/// It is initialised by `CTaskTeam__Constructor_Maybe` (0x550EB0), which:
|
| 690 |
+
/// 1. Calls `CTask::Constructor(this, nullptr, ddgame)`
|
| 691 |
+
/// 2. Sets the primary vtable to 0x669E34 and class_type to 5
|
| 692 |
+
/// 3. Sets a **secondary interface vtable** pointer (Ghidra 0x669C44) at +0x30
|
| 693 |
+
/// inside the object (i.e. `TurnGameCtx` base +0x00)
|
| 694 |
+
/// 4. Copies `landscape_height = DDGame+0x5E0` as Fixed16.16 to both
|
| 695 |
+
/// `land_height` and `land_height_2`
|
| 696 |
+
/// 5. Writes -1 sentinels to `_sentinel_18`, `_sentinel_28`, `_sentinel_38`
|
| 697 |
+
/// 6. Writes `team_count = *(byte*)(GameInfo+0x44C)` to `team_count`
|
| 698 |
+
///
|
| 699 |
+
/// `CTaskTurnGame__Constructor` (0x55B2A0) then overrides the primary vtable
|
| 700 |
+
/// to 0x669F70 and class_type to 6 but **leaves this sub-object intact**.
|
| 701 |
+
///
|
| 702 |
+
/// The three -1 sentinels at offsets 0x18 / 0x28 / 0x38 are evenly spaced
|
| 703 |
+
/// 0x10 bytes apart. Their role is unknown β candidates are water-level
|
| 704 |
+
/// Y-coordinates (initial -1 = "no water"), zone boundaries, or AI markers.
|
| 705 |
+
///
|
| 706 |
+
/// The final 0x44 bytes (0x68β0xAB, `_unknown_68`) are not initialised by
|
| 707 |
+
/// either constructor; they appear to be set by `FUN_005514d0` or similar
|
| 708 |
+
/// helpers called later. Observed runtime values: +0xA0 = 15, +0xA4/+0xA8
|
| 709 |
+
/// are heap pointers.
|
| 710 |
+
#[repr(C)]
|
| 711 |
+
pub struct TurnGameCtx {
|
| 712 |
+
/// +0x00 (= CTaskTurnGame+0x30): Secondary interface vtable pointer.
|
| 713 |
+
/// Ghidra: 0x00669C44. Set by both constructors; always 0x669C44 at runtime.
|
| 714 |
+
pub _secondary_vtable: u32,
|
| 715 |
+
/// +0x04: Unknown β not set by constructors (remains 0).
|
| 716 |
+
pub _unknown_04: u32,
|
| 717 |
+
/// +0x08β0x0F: Unknown β explicitly zeroed by constructor.
|
| 718 |
+
pub _unknown_08: [u32; 2],
|
| 719 |
+
/// +0x10: Landscape height as Fixed16.16. `DDGame+0x5E0 << 16`.
|
| 720 |
+
pub land_height: Fixed,
|
| 721 |
+
/// +0x14: Landscape height duplicate β same value as `land_height`.
|
| 722 |
+
pub land_height_2: Fixed,
|
| 723 |
+
/// +0x18: Sentinel, always -1 at construction. Role unknown.
|
| 724 |
+
pub _sentinel_18: i32,
|
| 725 |
+
/// +0x1Cβ0x27: Unknown β explicitly zeroed by constructor.
|
| 726 |
+
pub _unknown_1c: [u32; 3],
|
| 727 |
+
/// +0x28: Sentinel, always -1 at construction. Role unknown.
|
| 728 |
+
pub _sentinel_28: i32,
|
| 729 |
+
/// +0x2Cβ0x37: Unknown β explicitly zeroed by constructor.
|
| 730 |
+
pub _unknown_2c: [u32; 3],
|
| 731 |
+
/// +0x38: Sentinel, always -1 at construction. Role unknown.
|
| 732 |
+
pub _sentinel_38: i32,
|
| 733 |
+
/// +0x3Cβ0x4B: Unknown β explicitly zeroed by constructor.
|
| 734 |
+
pub _unknown_3c: [u32; 4],
|
| 735 |
+
/// +0x4C: Number of teams. `*(byte*)(GameInfo+0x44C)` at construction time.
|
| 736 |
+
pub team_count: u32,
|
| 737 |
+
/// +0x50β0x64: Unknown β explicitly zeroed by constructor (param_1[0x20..0x25]).
|
| 738 |
+
pub _unknown_50: [u32; 6],
|
| 739 |
+
/// +0x68β0x9F: Unknown β all explicitly zeroed by `FUN_005514d0` during game
|
| 740 |
+
/// setup. Purpose unknown; may be per-team or per-worm state slots.
|
| 741 |
+
pub _unknown_68: [u32; 14],
|
| 742 |
+
/// +0xA0: Return value of `FUN_00525f50(0)` stored here during game setup.
|
| 743 |
+
/// Observed value: 15. `FUN_00525f50` is a slot-allocation helper; this may
|
| 744 |
+
/// be a pool slot index or a pre-computed game-state token.
|
| 745 |
+
pub _slot_d0: u32,
|
| 746 |
+
/// +0xA4: `DDDisplay` textbox handle β created by `DDDisplay__ConstructTextbox`
|
| 747 |
+
/// with params `(buf, -1280, 2)` if `DDGame+0x7EF8 != 0` (display active).
|
| 748 |
+
/// Likely the HUD timer textbox. NULL when display is disabled.
|
| 749 |
+
pub _hud_textbox_a: u32,
|
| 750 |
+
/// +0xA8: `DDDisplay` textbox handle β created with params `(buf, 8, 4)`.
|
| 751 |
+
/// Likely a secondary HUD element. NULL when display is disabled.
|
| 752 |
+
pub _hud_textbox_b: u32,
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
const _: () = assert!(core::mem::size_of::<TurnGameCtx>() == 0xAC);
|
| 756 |
+
|
| 757 |
/// Root turn-controller task β one instance per game, parent of the entire entity tree.
|
| 758 |
///
|
| 759 |
/// Every worm, team, projectile, and environment task is a child (direct or indirect)
|
|
|
|
| 777 |
pub struct CTaskTurnGame {
|
| 778 |
/// 0x00-0x2F: CTask base (vtable, parent, children, shared_data, ddgame, β¦)
|
| 779 |
pub base: CTask,
|
| 780 |
+
/// 0x30β0xDB: Embedded `TurnGameCtx` sub-object (0xAC bytes).
|
| 781 |
+
/// See [`TurnGameCtx`] for field details.
|
| 782 |
+
pub game_ctx: TurnGameCtx,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 783 |
|
| 784 |
// ---- CTaskTurnGame-specific fields (0xDC onwards) ----
|
| 785 |
|
|
@@ -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, CTaskTeam, CTaskTurnGame, SharedDataTable};
|
| 15 |
use openwa_core::ddgame::DDGame;
|
| 16 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 17 |
|
|
@@ -307,7 +307,19 @@ fn validate_struct_offsets(result: &mut ValidationResult) {
|
|
| 307 |
check_offset!(result, CTaskTeam, pos_y, 0x408);
|
| 308 |
|
| 309 |
let _ = log_validation("");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
let _ = log_validation(" CTaskTurnGame:");
|
|
|
|
| 311 |
check_offset!(result, CTaskTurnGame, worm_active, 0x108);
|
| 312 |
check_offset!(result, CTaskTurnGame, current_team, 0x12C);
|
| 313 |
check_offset!(result, CTaskTurnGame, current_worm, 0x130);
|
|
@@ -995,6 +1007,17 @@ fn dump_turngame() {
|
|
| 995 |
|
| 996 |
// Print known named fields for cross-validation.
|
| 997 |
let tg = &*(tg_ptr as *const CTaskTurnGame);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
let _ = log_validation(" Known fields:");
|
| 999 |
let _ = log_validation(&format!(" +0x108 worm_active = {}", tg.worm_active));
|
| 1000 |
let _ = log_validation(&format!(" +0x12C current_team = {} (1-based, 0=none)", tg.current_team));
|
|
@@ -1016,7 +1039,8 @@ fn dump_turngame() {
|
|
| 1016 |
// Dump in chunks to keep log lines manageable.
|
| 1017 |
let base = tg_ptr as *const u8;
|
| 1018 |
dump_region(base, 0x00, 0x30, "CTaskTurnGame"); // CTask base
|
| 1019 |
-
dump_region(base, 0x30,
|
|
|
|
| 1020 |
dump_region(base, 0xDC, 0x80, "CTaskTurnGame"); // 0xDC..0x15B
|
| 1021 |
dump_region(base, 0x15C, 0x80, "CTaskTurnGame"); // 0x15C..0x1DB
|
| 1022 |
dump_region(base, 0x1DC, 0x80, "CTaskTurnGame"); // 0x1DC..0x25B
|
|
|
|
| 11 |
|
| 12 |
use openwa_core::rebase::rb;
|
| 13 |
use openwa_core::address::va;
|
| 14 |
+
use openwa_core::task::{CTask, CGameTask, CTaskTeam, CTaskTurnGame, TurnGameCtx, SharedDataTable};
|
| 15 |
use openwa_core::ddgame::DDGame;
|
| 16 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 17 |
|
|
|
|
| 307 |
check_offset!(result, CTaskTeam, pos_y, 0x408);
|
| 308 |
|
| 309 |
let _ = log_validation("");
|
| 310 |
+
let _ = log_validation(" TurnGameCtx (embedded at CTaskTurnGame+0x30):");
|
| 311 |
+
check_offset!(result, TurnGameCtx, land_height, 0x10);
|
| 312 |
+
check_offset!(result, TurnGameCtx, land_height_2, 0x14);
|
| 313 |
+
check_offset!(result, TurnGameCtx, _sentinel_18, 0x18);
|
| 314 |
+
check_offset!(result, TurnGameCtx, _sentinel_28, 0x28);
|
| 315 |
+
check_offset!(result, TurnGameCtx, _sentinel_38, 0x38);
|
| 316 |
+
check_offset!(result, TurnGameCtx, team_count, 0x4C);
|
| 317 |
+
check_offset!(result, TurnGameCtx, _slot_d0, 0xA0);
|
| 318 |
+
check_offset!(result, TurnGameCtx, _hud_textbox_a, 0xA4);
|
| 319 |
+
check_offset!(result, TurnGameCtx, _hud_textbox_b, 0xA8);
|
| 320 |
+
|
| 321 |
let _ = log_validation(" CTaskTurnGame:");
|
| 322 |
+
check_offset!(result, CTaskTurnGame, game_ctx, 0x30);
|
| 323 |
check_offset!(result, CTaskTurnGame, worm_active, 0x108);
|
| 324 |
check_offset!(result, CTaskTurnGame, current_team, 0x12C);
|
| 325 |
check_offset!(result, CTaskTurnGame, current_worm, 0x130);
|
|
|
|
| 1007 |
|
| 1008 |
// Print known named fields for cross-validation.
|
| 1009 |
let tg = &*(tg_ptr as *const CTaskTurnGame);
|
| 1010 |
+
let ctx = &tg.game_ctx;
|
| 1011 |
+
let _ = log_validation(" TurnGameCtx fields:");
|
| 1012 |
+
let _ = log_validation(&format!(" +0x40 land_height = {} ({:.4})", ctx.land_height.0, ctx.land_height.to_f32()));
|
| 1013 |
+
let _ = log_validation(&format!(" +0x44 land_height_2 = {} ({:.4})", ctx.land_height_2.0, ctx.land_height_2.to_f32()));
|
| 1014 |
+
let _ = log_validation(&format!(" +0x48 _sentinel_18 = {}", ctx._sentinel_18));
|
| 1015 |
+
let _ = log_validation(&format!(" +0x58 _sentinel_28 = {}", ctx._sentinel_28));
|
| 1016 |
+
let _ = log_validation(&format!(" +0x68 _sentinel_38 = {}", ctx._sentinel_38));
|
| 1017 |
+
let _ = log_validation(&format!(" +0x7C team_count = {}", ctx.team_count));
|
| 1018 |
+
let _ = log_validation(&format!(" +0xD0 _slot_d0 = {}", ctx._slot_d0));
|
| 1019 |
+
let _ = log_validation(&format!(" +0xD4 _hud_textbox_a = 0x{:08X}", ctx._hud_textbox_a));
|
| 1020 |
+
let _ = log_validation(&format!(" +0xD8 _hud_textbox_b = 0x{:08X}", ctx._hud_textbox_b));
|
| 1021 |
let _ = log_validation(" Known fields:");
|
| 1022 |
let _ = log_validation(&format!(" +0x108 worm_active = {}", tg.worm_active));
|
| 1023 |
let _ = log_validation(&format!(" +0x12C current_team = {} (1-based, 0=none)", tg.current_team));
|
|
|
|
| 1039 |
// Dump in chunks to keep log lines manageable.
|
| 1040 |
let base = tg_ptr as *const u8;
|
| 1041 |
dump_region(base, 0x00, 0x30, "CTaskTurnGame"); // CTask base
|
| 1042 |
+
dump_region(base, 0x30, 0x38, "TurnGameCtx"); // 0x30..0x67 (vtable + sentinels)
|
| 1043 |
+
dump_region(base, 0x68, 0x74, "TurnGameCtx"); // 0x68..0xDB (team_count + unknowns)
|
| 1044 |
dump_region(base, 0xDC, 0x80, "CTaskTurnGame"); // 0xDC..0x15B
|
| 1045 |
dump_region(base, 0x15C, 0x80, "CTaskTurnGame"); // 0x15C..0x1DB
|
| 1046 |
dump_region(base, 0x1DC, 0x80, "CTaskTurnGame"); // 0x1DC..0x25B
|