feat: add TurnOrderWidget structs and expand DDGame field coverage
Browse filesAdd turn_order.rs with AnimatedItemList, TurnOrderWidget, TurnOrderAllianceGroup,
and TurnOrderTeamEntry structs for the in-game team banner health bar UI.
Retype DDGame.turn_order_widget from *mut u8 to *mut TurnOrderWidget.
DDGame fields verified via runtime DWORD dumps and Ghidra cross-references:
- team_health_ratio[6] at 0x45F0 (fixed-point, renders health bar width)
- game_speed/game_speed_target at 0x72D8/0x72DC
- rng_state_1/2 at 0x72EC/0x72F0
- viewport_width/height at 0x7380-0x738C
- camera_x/y/target at 0x73A0-0x73AC
- level_bound_min/max at 0x779C-0x77A8
- task_land (CTaskLand) at 0x54C, hud_panel at 0x534
- turn_status_text at 0x818C, turn_time_limit at 0x7EA8
Remove diagnostic dump invocations from input.rs, keep utilities for reuse.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@@ -47,6 +47,8 @@ pub mod va {
|
|
| 47 |
pub const OPENGL_CPU_VTABLE: u32 = 0x0067_74C0;
|
| 48 |
/// WaterEffect vtable (0xBC-byte object)
|
| 49 |
pub const WATER_EFFECT_VTABLE: u32 = 0x0066_B268;
|
|
|
|
|
|
|
| 50 |
/// Sprite vtable (0x70-byte objects, 8 entries)
|
| 51 |
pub const SPRITE_VTABLE: u32 = 0x0066_418C;
|
| 52 |
|
|
|
|
| 47 |
pub const OPENGL_CPU_VTABLE: u32 = 0x0067_74C0;
|
| 48 |
/// WaterEffect vtable (0xBC-byte object)
|
| 49 |
pub const WATER_EFFECT_VTABLE: u32 = 0x0066_B268;
|
| 50 |
+
/// CTaskLand vtable - 12 entries, landscape/terrain task (DDGame+0x054C)
|
| 51 |
+
pub const CTASK_LAND_VTABLE: u32 = 0x0066_4388;
|
| 52 |
/// Sprite vtable (0x70-byte objects, 8 entries)
|
| 53 |
pub const SPRITE_VTABLE: u32 = 0x0066_418C;
|
| 54 |
|
|
@@ -8,6 +8,7 @@ use crate::music::Music;
|
|
| 8 |
use crate::palette::Palette;
|
| 9 |
use crate::render::RenderQueue;
|
| 10 |
use crate::speech::SpeechSlotTable;
|
|
|
|
| 11 |
|
| 12 |
/// DDGame — the main game engine object.
|
| 13 |
///
|
|
@@ -62,12 +63,16 @@ pub struct DDGame {
|
|
| 62 |
pub arrow_gfxdirs: [*mut u8; 32],
|
| 63 |
/// 0x138: DisplayGfx object pointer (vtable 0x664144)
|
| 64 |
pub display_gfx: *mut u8,
|
| 65 |
-
/// 0x13C-0x37F:
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
| 67 |
/// 0x380: TaskStateMachine pointer (vtable 0x664118, 0x2C bytes)
|
| 68 |
pub task_state_machine: *mut u8,
|
| 69 |
-
/// 0x384-0x467:
|
| 70 |
-
|
|
|
|
| 71 |
/// 0x468: Landscape-derived value
|
| 72 |
pub _landscape_val: *mut u8,
|
| 73 |
/// 0x46C-0x488: 8 SpriteRegion pointers (0x9C bytes each, vtable 0x66B268)
|
|
@@ -80,24 +85,98 @@ pub struct DDGame {
|
|
| 80 |
pub coord_list: *mut u8,
|
| 81 |
/// 0x510: Weapon table pointer
|
| 82 |
pub weapon_table: *mut u8,
|
| 83 |
-
/// 0x514
|
| 84 |
-
pub _unknown_514:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
/// 0x524: RenderQueue pointer (passed as `this` to all Draw* functions)
|
| 86 |
pub render_queue: *mut RenderQueue,
|
| 87 |
-
/// 0x528
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
/// 0x548: Weapon panel pointer
|
| 90 |
pub weapon_panel: *mut u8,
|
| 91 |
-
/// 0x54C
|
|
|
|
|
|
|
|
|
|
| 92 |
///
|
| 93 |
-
///
|
| 94 |
-
/// -
|
| 95 |
-
/// -
|
| 96 |
-
/// -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
///
|
| 98 |
-
///
|
| 99 |
/// 0x379C, 0x3930, 0x3AC4, 0x3C58, 0x3DEC, 0x3F80, 0x4114, 0x42A8, 0x443C, 0x45D0
|
| 100 |
-
pub
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
/// 0x7324: Crosshair line color param (DrawPolygon param_2).
|
| 103 |
/// Part of GfxDir color entries at 0x730C.
|
|
@@ -110,8 +189,56 @@ pub struct DDGame {
|
|
| 110 |
pub _unknown_7330: [u8; 8],
|
| 111 |
/// 0x7338: Fill pixel value
|
| 112 |
pub fill_pixel: u32,
|
| 113 |
-
/// 0x733C-
|
| 114 |
-
pub _unknown_733c: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
/// 0x77C0: Level width in pixels (set by PCLandscape constructor).
|
| 117 |
pub level_width: u32,
|
|
@@ -127,12 +254,27 @@ pub struct DDGame {
|
|
| 127 |
/// Cleared by DSSound_LoadAllSpeechBanks (0x571A70), filled by DSSound_LoadSpeechWAV (0x571530).
|
| 128 |
pub speech_slot_table: SpeechSlotTable,
|
| 129 |
|
| 130 |
-
/// 0x7D84-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
///
|
| 134 |
-
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
// === Sound queue (0x7F00-0x8143) ===
|
| 138 |
/// 0x7F00: Sound queue (16 entries, stride 0x24). Appended by PlaySoundGlobal.
|
|
@@ -145,14 +287,39 @@ pub struct DDGame {
|
|
| 145 |
/// 0x8150: Scale factor used by DrawCrosshairLine (multiplied by 0x140000).
|
| 146 |
pub crosshair_scale: i32,
|
| 147 |
|
| 148 |
-
/// 0x8154-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
///
|
| 150 |
/// Known landmarks:
|
| 151 |
-
/// -
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
/// -
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
/// 0x98B0: Fast-forward active flag.
|
| 158 |
/// When set to 1, FUN_005307A0 processes up to 50 game frames per render
|
|
@@ -217,14 +384,54 @@ pub mod offsets {
|
|
| 217 |
pub const INIT_TABLE_BASE: usize = 0x379C;
|
| 218 |
pub const INIT_TABLE_STRIDE: usize = 0x194;
|
| 219 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
// === Sparse fields in upper region ===
|
| 221 |
-
pub const FIELD_64D8: usize = 0x64D8;
|
| 222 |
-
pub const FIELD_72A4: usize = 0x72A4;
|
| 223 |
pub const GFX_COLOR_ENTRIES: usize = 0x730C;
|
| 224 |
-
|
| 225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
/// Scale factor used by DrawCrosshairLine (multiplied by 0x140000).
|
| 227 |
pub const CROSSHAIR_SCALE: usize = 0x8150;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
// === Speech slot table (DDGame + 0x77E4) ===
|
| 230 |
/// Speech slot table: maps (team, speech_line_id) → DSSound buffer index.
|
|
|
|
| 8 |
use crate::palette::Palette;
|
| 9 |
use crate::render::RenderQueue;
|
| 10 |
use crate::speech::SpeechSlotTable;
|
| 11 |
+
use crate::turn_order::TurnOrderWidget;
|
| 12 |
|
| 13 |
/// DDGame — the main game engine object.
|
| 14 |
///
|
|
|
|
| 63 |
pub arrow_gfxdirs: [*mut u8; 32],
|
| 64 |
/// 0x138: DisplayGfx object pointer (vtable 0x664144)
|
| 65 |
pub display_gfx: *mut u8,
|
| 66 |
+
/// 0x13C-0x37F: Sprite/image object cache (145 pointer slots).
|
| 67 |
+
/// All populated entries have vtable 0x664144 (same class as `display_gfx`).
|
| 68 |
+
/// Not initialized in DDGame__Constructor — filled during gameplay with
|
| 69 |
+
/// weapon sprites, effect images, cursor graphics, etc.
|
| 70 |
+
pub sprite_cache: [*mut u8; 145],
|
| 71 |
/// 0x380: TaskStateMachine pointer (vtable 0x664118, 0x2C bytes)
|
| 72 |
pub task_state_machine: *mut u8,
|
| 73 |
+
/// 0x384-0x467: Additional sprite/image object slots.
|
| 74 |
+
/// Same vtable 0x664144 as sprite_cache. ~20 entries populated at runtime.
|
| 75 |
+
pub sprite_cache_2: [*mut u8; 57],
|
| 76 |
/// 0x468: Landscape-derived value
|
| 77 |
pub _landscape_val: *mut u8,
|
| 78 |
/// 0x46C-0x488: 8 SpriteRegion pointers (0x9C bytes each, vtable 0x66B268)
|
|
|
|
| 85 |
pub coord_list: *mut u8,
|
| 86 |
/// 0x510: Weapon table pointer
|
| 87 |
pub weapon_table: *mut u8,
|
| 88 |
+
/// 0x514: Unknown pointer (populated at runtime)
|
| 89 |
+
pub _unknown_514: *mut u8,
|
| 90 |
+
/// 0x518: Unknown pointer (populated at runtime)
|
| 91 |
+
pub _unknown_518: *mut u8,
|
| 92 |
+
/// 0x51C: Unknown pointer (populated at runtime)
|
| 93 |
+
pub _unknown_51c: *mut u8,
|
| 94 |
+
/// 0x520: Unknown (zero in runtime dump)
|
| 95 |
+
pub _unknown_520: u32,
|
| 96 |
/// 0x524: RenderQueue pointer (passed as `this` to all Draw* functions)
|
| 97 |
pub render_queue: *mut RenderQueue,
|
| 98 |
+
/// 0x528: Game state stream object (vtable 0x664194, vt[0]=0x4FB5C0).
|
| 99 |
+
/// Created in DDGame_InitGameState_Maybe (0x526690), constructor 0x4FB5F0.
|
| 100 |
+
/// Reads from replay/packet data stream.
|
| 101 |
+
pub game_state_stream: *mut u8,
|
| 102 |
+
/// 0x52C: Unknown pointer
|
| 103 |
+
pub _unknown_52c: *mut u8,
|
| 104 |
+
/// 0x530: Turn order widget (vtable 0x66A088, vt[0]=0x563E90).
|
| 105 |
+
/// Constructor 0x563D40. UI component that renders team banners with
|
| 106 |
+
/// animated sliding transitions (sin-table interpolation). Groups teams
|
| 107 |
+
/// by alliance, creates per-team entries with textbox + DisplayGfx.
|
| 108 |
+
pub turn_order_widget: *mut TurnOrderWidget,
|
| 109 |
+
/// 0x534: HUD panel object (vtable 0x664698, vt[0]=0x5241F0).
|
| 110 |
+
/// Constructor 0x524070. 104×28 px, 3 DisplayGfx layers, 2296-byte LUT.
|
| 111 |
+
pub hud_panel: *mut u8,
|
| 112 |
+
/// 0x538-0x53F: Unknown (zero in runtime dump)
|
| 113 |
+
pub _unknown_538: [u8; 8],
|
| 114 |
+
/// 0x540: Unknown pointer
|
| 115 |
+
pub _unknown_540: *mut u8,
|
| 116 |
+
/// 0x544: Unknown pointer (*=0x1BC at runtime)
|
| 117 |
+
pub _unknown_544: *mut u8,
|
| 118 |
/// 0x548: Weapon panel pointer
|
| 119 |
pub weapon_panel: *mut u8,
|
| 120 |
+
/// 0x54C: CTaskLand pointer (set by CTaskLand__InitLandscape at 0x5056F0).
|
| 121 |
+
/// The landscape/terrain task. Vtable at 0x664388.
|
| 122 |
+
pub task_land: *mut u8,
|
| 123 |
+
/// 0x550-0x25FF: Large unverified region.
|
| 124 |
///
|
| 125 |
+
/// Runtime observations (not yet linked to code):
|
| 126 |
+
/// - 0x5C4: value matches code address 0x5755D0 (fixed-point normalize fn)
|
| 127 |
+
/// - 0x5C8-0x5FF: small config-like values (2048, 150, 3000, 696, 896, 100, 300)
|
| 128 |
+
/// - 0x600-0x25FF: identity permutation [0,1,2,...,~2048] — purpose unknown
|
| 129 |
+
pub _unknown_550: [u8; 0x2600 - 0x550],
|
| 130 |
+
|
| 131 |
+
/// 0x2600-0x2DFF: Block of 0xFFFFFFFF values at runtime (512 i32 entries).
|
| 132 |
+
/// May be unused slots in a parallel table to the 0x600 permutation.
|
| 133 |
+
pub _unknown_2600: [u8; 0x2E00 - 0x2600],
|
| 134 |
+
|
| 135 |
+
/// 0x2E00-0x45EB: Unknown (mostly zero at runtime)
|
| 136 |
///
|
| 137 |
+
/// Contains FUN_00526120 zeroed offsets at stride 0x194:
|
| 138 |
/// 0x379C, 0x3930, 0x3AC4, 0x3C58, 0x3DEC, 0x3F80, 0x4114, 0x42A8, 0x443C, 0x45D0
|
| 139 |
+
pub _unknown_2e00: [u8; 0x45EC - 0x2E00],
|
| 140 |
+
|
| 141 |
+
/// 0x45EC: Unknown (0xA307A169 at runtime — not part of team scale arrays).
|
| 142 |
+
pub _unknown_45ec: u32,
|
| 143 |
+
/// 0x45F0-0x4607: Per-team health ratio (Fixed-point, 6 entries, 1-indexed).
|
| 144 |
+
/// 0x10000 = 100% health. Rendered as bar width: `value * 100 >> 16 + 4` pixels.
|
| 145 |
+
/// Read by TurnOrderTeamEntry render method (0x563620).
|
| 146 |
+
/// Initialized to 0x10000 (1.0) by TurnOrderTeamEntry constructor (0x5630B0).
|
| 147 |
+
pub team_health_ratio: [i32; 6],
|
| 148 |
+
/// 0x4608-0x461F: Per-team health ratio 2 (Fixed-point, 6 entries, 1-indexed).
|
| 149 |
+
/// Initialized to 0x10000 (1.0). Not read by the render method — may be
|
| 150 |
+
/// target/previous value for interpolation, or used by update logic.
|
| 151 |
+
pub team_health_ratio_2: [i32; 6],
|
| 152 |
+
/// 0x4620-0x64D7: Unknown
|
| 153 |
+
///
|
| 154 |
+
/// Known landmarks:
|
| 155 |
+
/// - 0x64D8: cleared by init
|
| 156 |
+
pub _unknown_4620: [u8; 0x64D8 - 0x4620],
|
| 157 |
+
/// 0x64D8: Cleared by init.
|
| 158 |
+
pub _field_64d8: u32,
|
| 159 |
+
/// 0x64DC-0x72A3: Unknown
|
| 160 |
+
pub _unknown_64dc: [u8; 0x72A4 - 0x64DC],
|
| 161 |
+
/// 0x72A4: Cleared by init.
|
| 162 |
+
pub _field_72a4: u32,
|
| 163 |
+
/// 0x72A8-0x72D7: Unknown
|
| 164 |
+
pub _unknown_72a8: [u8; 0x72D8 - 0x72A8],
|
| 165 |
+
|
| 166 |
+
/// 0x72D8: Game speed multiplier (Fixed-point, 0x10000 = 1.0x).
|
| 167 |
+
pub game_speed: i32,
|
| 168 |
+
/// 0x72DC: Game speed target (Fixed-point, 0x10000 = 1.0x).
|
| 169 |
+
pub game_speed_target: i32,
|
| 170 |
+
/// 0x72E0-0x72EB: Unknown
|
| 171 |
+
pub _unknown_72e0: [u8; 0x72EC - 0x72E0],
|
| 172 |
+
/// 0x72EC: RNG state word 1 (changes every frame).
|
| 173 |
+
pub rng_state_1: u32,
|
| 174 |
+
/// 0x72F0: RNG state word 2 (changes every frame).
|
| 175 |
+
pub rng_state_2: u32,
|
| 176 |
+
/// 0x72F4-0x730B: Unknown
|
| 177 |
+
pub _unknown_72f4: [u8; 0x730C - 0x72F4],
|
| 178 |
+
/// 0x730C-0x731C: 5 GfxDir color entries
|
| 179 |
+
pub _gfx_color_entries: [u8; 0x7324 - 0x730C],
|
| 180 |
|
| 181 |
/// 0x7324: Crosshair line color param (DrawPolygon param_2).
|
| 182 |
/// Part of GfxDir color entries at 0x730C.
|
|
|
|
| 189 |
pub _unknown_7330: [u8; 8],
|
| 190 |
/// 0x7338: Fill pixel value
|
| 191 |
pub fill_pixel: u32,
|
| 192 |
+
/// 0x733C-0x737F: Unknown
|
| 193 |
+
pub _unknown_733c: [u8; 0x7380 - 0x733C],
|
| 194 |
+
|
| 195 |
+
/// 0x7380: Viewport width (Fixed-point, e.g. 960.0 = 0x03C00000).
|
| 196 |
+
pub viewport_width: i32,
|
| 197 |
+
/// 0x7384: Viewport height (Fixed-point, e.g. 348.0 = 0x015C0000).
|
| 198 |
+
pub viewport_height: i32,
|
| 199 |
+
/// 0x7388: Viewport width max/duplicate (Fixed-point).
|
| 200 |
+
pub viewport_width_2: i32,
|
| 201 |
+
/// 0x738C: Viewport height max/duplicate (Fixed-point).
|
| 202 |
+
pub viewport_height_2: i32,
|
| 203 |
+
/// 0x7390-0x739F: Unknown
|
| 204 |
+
pub _unknown_7390: [u8; 0x73A0 - 0x7390],
|
| 205 |
+
|
| 206 |
+
/// 0x73A0: Camera X position (Fixed-point, e.g. 393.0).
|
| 207 |
+
pub camera_x: i32,
|
| 208 |
+
/// 0x73A4: Camera Y position (Fixed-point, e.g. 532.0).
|
| 209 |
+
pub camera_y: i32,
|
| 210 |
+
/// 0x73A8: Camera target X (Fixed-point, duplicate/interpolation target).
|
| 211 |
+
pub camera_target_x: i32,
|
| 212 |
+
/// 0x73AC: Camera target Y (Fixed-point).
|
| 213 |
+
pub camera_target_y: i32,
|
| 214 |
+
|
| 215 |
+
/// 0x73B0-0x764F: Unknown
|
| 216 |
+
pub _unknown_73b0: [u8; 0x7650 - 0x73B0],
|
| 217 |
+
|
| 218 |
+
/// 0x7650-0x768F: Team index mapping table 1.
|
| 219 |
+
/// Packed u16 pairs: [0,1], [2,3], ..., [14,15]. Team-to-slot or turn order.
|
| 220 |
+
pub team_index_map_1: [u8; 0x7690 - 0x7650],
|
| 221 |
+
/// 0x7690-0x76AF: Unknown
|
| 222 |
+
pub _unknown_7690: [u8; 0x76B0 - 0x7690],
|
| 223 |
+
/// 0x76B0-0x76EF: Team index mapping table 2 (same pattern).
|
| 224 |
+
pub team_index_map_2: [u8; 0x76F0 - 0x76B0],
|
| 225 |
+
/// 0x76F0-0x7717: Unknown
|
| 226 |
+
pub _unknown_76f0: [u8; 0x7718 - 0x76F0],
|
| 227 |
+
/// 0x7718-0x7757: Team index mapping table 3 (similar pattern, slightly different end).
|
| 228 |
+
pub team_index_map_3: [u8; 0x7758 - 0x7718],
|
| 229 |
+
/// 0x7758-0x779B: Unknown
|
| 230 |
+
pub _unknown_7758: [u8; 0x779C - 0x7758],
|
| 231 |
+
|
| 232 |
+
/// 0x779C: Level bound min X (Fixed-point, negative = off-screen left).
|
| 233 |
+
pub level_bound_min_x: i32,
|
| 234 |
+
/// 0x77A0: Level bound max X (Fixed-point).
|
| 235 |
+
pub level_bound_max_x: i32,
|
| 236 |
+
/// 0x77A4: Level bound min Y (Fixed-point, same as min_x typically).
|
| 237 |
+
pub level_bound_min_y: i32,
|
| 238 |
+
/// 0x77A8: Level bound max Y (Fixed-point).
|
| 239 |
+
pub level_bound_max_y: i32,
|
| 240 |
+
/// 0x77AC-0x77BF: Unknown
|
| 241 |
+
pub _unknown_77ac: [u8; 0x77C0 - 0x77AC],
|
| 242 |
|
| 243 |
/// 0x77C0: Level width in pixels (set by PCLandscape constructor).
|
| 244 |
pub level_width: u32,
|
|
|
|
| 254 |
/// Cleared by DSSound_LoadAllSpeechBanks (0x571A70), filled by DSSound_LoadSpeechWAV (0x571530).
|
| 255 |
pub speech_slot_table: SpeechSlotTable,
|
| 256 |
|
| 257 |
+
/// 0x7D84-0x7E9F: Unknown
|
| 258 |
+
pub _unknown_7d84: [u8; 0x7EA0 - 0x7D84],
|
| 259 |
+
|
| 260 |
+
/// 0x7EA0: Unknown flag/counter (value 4 at runtime — team count?)
|
| 261 |
+
pub _field_7ea0: u32,
|
| 262 |
+
/// 0x7EA4: Unknown
|
| 263 |
+
pub _unknown_7ea4: u32,
|
| 264 |
+
/// 0x7EA8: Turn time limit in seconds (150 = 2:30 default).
|
| 265 |
+
pub turn_time_limit: u32,
|
| 266 |
+
/// 0x7EAC-0x7ECF: Unknown
|
| 267 |
+
pub _unknown_7eac: [u8; 0x7ED0 - 0x7EAC],
|
| 268 |
+
/// 0x7ED0-0x7EEF: Unknown
|
| 269 |
+
pub _unknown_7ed0: [u8; 0x7EF0 - 0x7ED0],
|
| 270 |
+
/// 0x7EF0: Unknown flag (-1 = 0xFFFFFFFF at runtime)
|
| 271 |
+
pub _field_7ef0: i32,
|
| 272 |
+
/// 0x7EF4: Unknown
|
| 273 |
+
pub _unknown_7ef4: u32,
|
| 274 |
+
/// 0x7EF8: Sound available flag (1 when game_info+0xF914 == 0, i.e. not headless).
|
| 275 |
+
pub sound_available: u32,
|
| 276 |
+
/// 0x7EFC: Always initialized to 1 in constructor.
|
| 277 |
+
pub _field_7efc: u32,
|
| 278 |
|
| 279 |
// === Sound queue (0x7F00-0x8143) ===
|
| 280 |
/// 0x7F00: Sound queue (16 entries, stride 0x24). Appended by PlaySoundGlobal.
|
|
|
|
| 287 |
/// 0x8150: Scale factor used by DrawCrosshairLine (multiplied by 0x140000).
|
| 288 |
pub crosshair_scale: i32,
|
| 289 |
|
| 290 |
+
/// 0x8154-0x818B: Unknown
|
| 291 |
+
pub _unknown_8154: [u8; 0x818C - 0x8154],
|
| 292 |
+
|
| 293 |
+
/// 0x818C: Turn status text buffer (null-terminated ASCII).
|
| 294 |
+
/// Shows on screen during gameplay, e.g. "Cheesy harkitseee siirtoaan"
|
| 295 |
+
/// ("Cheesy is considering their move" in Finnish).
|
| 296 |
+
pub turn_status_text: [u8; 64],
|
| 297 |
+
|
| 298 |
+
/// 0x81CC-0x8CBB: Unknown
|
| 299 |
///
|
| 300 |
/// Known landmarks:
|
| 301 |
+
/// - 0x8174: value 0x3FC (1020) at runtime
|
| 302 |
+
pub _unknown_81cc: [u8; 0x8CBC - 0x81CC],
|
| 303 |
+
|
| 304 |
+
/// 0x8CBC-0x8CF8: 4× coordinate entries (0x10-byte stride).
|
| 305 |
+
/// InitFields zeroes +0 and +4 of each. At runtime contains fixed-point
|
| 306 |
+
/// screen coordinates (e.g. 393.0, 532.0, 960.0, 348.0).
|
| 307 |
+
pub coord_entries_8cbc: [u8; 0x8CFC - 0x8CBC],
|
| 308 |
+
|
| 309 |
+
/// 0x8CFC-0x984F: Unknown
|
| 310 |
+
pub _unknown_8cfc: [u8; 0x9850 - 0x8CFC],
|
| 311 |
+
|
| 312 |
+
/// 0x9850-0x988F: 4× coordinate entries (0x10-byte stride, zeroed by InitFields).
|
| 313 |
+
pub coord_entries_9850: [u8; 0x9890 - 0x9850],
|
| 314 |
+
|
| 315 |
+
/// 0x9890-0x98A3: Flags
|
| 316 |
+
pub _unknown_9890: [u8; 0x98A4 - 0x9890],
|
| 317 |
+
/// 0x98A4: Checkpoint active flag.
|
| 318 |
+
pub checkpoint_active: u32,
|
| 319 |
+
/// 0x98A8: Unknown
|
| 320 |
+
pub _unknown_98a8: u32,
|
| 321 |
+
/// 0x98AC: Fast-forward request flag.
|
| 322 |
+
pub fast_forward_request: u32,
|
| 323 |
|
| 324 |
/// 0x98B0: Fast-forward active flag.
|
| 325 |
/// When set to 1, FUN_005307A0 processes up to 50 game frames per render
|
|
|
|
| 384 |
pub const INIT_TABLE_BASE: usize = 0x379C;
|
| 385 |
pub const INIT_TABLE_STRIDE: usize = 0x194;
|
| 386 |
|
| 387 |
+
// === Game objects (0x528-0x54C) ===
|
| 388 |
+
pub const GAME_STATE_STREAM: usize = 0x528;
|
| 389 |
+
pub const TURN_ORDER_WIDGET: usize = 0x530;
|
| 390 |
+
pub const HUD_PANEL: usize = 0x534;
|
| 391 |
+
/// CTaskLand pointer (landscape/terrain task, vtable 0x664388).
|
| 392 |
+
pub const TASK_LAND: usize = 0x54C;
|
| 393 |
+
|
| 394 |
+
// === Per-team health ratio (turn order health bar) ===
|
| 395 |
+
/// Per-team health ratio array (6 × i32, 1-indexed by team).
|
| 396 |
+
/// 0x10000 = 100%. Rendered as `value * 100 >> 16` pixel width.
|
| 397 |
+
pub const TEAM_HEALTH_RATIO: usize = 0x45F0;
|
| 398 |
+
/// Per-team health ratio 2 (6 × i32, 1-indexed by team).
|
| 399 |
+
pub const TEAM_HEALTH_RATIO_2: usize = 0x4608;
|
| 400 |
+
|
| 401 |
+
// === RNG (0x72EC) ===
|
| 402 |
+
pub const RNG_STATE_1: usize = 0x72EC;
|
| 403 |
+
pub const RNG_STATE_2: usize = 0x72F0;
|
| 404 |
+
|
| 405 |
// === Sparse fields in upper region ===
|
|
|
|
|
|
|
| 406 |
pub const GFX_COLOR_ENTRIES: usize = 0x730C;
|
| 407 |
+
|
| 408 |
+
// === Camera/viewport (0x7380-0x73AC) ===
|
| 409 |
+
pub const VIEWPORT_WIDTH: usize = 0x7380;
|
| 410 |
+
pub const VIEWPORT_HEIGHT: usize = 0x7384;
|
| 411 |
+
pub const CAMERA_X: usize = 0x73A0;
|
| 412 |
+
pub const CAMERA_Y: usize = 0x73A4;
|
| 413 |
+
pub const CAMERA_TARGET_X: usize = 0x73A8;
|
| 414 |
+
pub const CAMERA_TARGET_Y: usize = 0x73AC;
|
| 415 |
+
|
| 416 |
+
// === Game speed (0x72D8) ===
|
| 417 |
+
pub const GAME_SPEED: usize = 0x72D8;
|
| 418 |
+
pub const GAME_SPEED_TARGET: usize = 0x72DC;
|
| 419 |
+
|
| 420 |
+
// === Level bounds (0x779C-0x77A8) ===
|
| 421 |
+
pub const LEVEL_BOUND_MIN_X: usize = 0x779C;
|
| 422 |
+
pub const LEVEL_BOUND_MAX_X: usize = 0x77A0;
|
| 423 |
+
pub const LEVEL_BOUND_MIN_Y: usize = 0x77A4;
|
| 424 |
+
pub const LEVEL_BOUND_MAX_Y: usize = 0x77A8;
|
| 425 |
+
pub const TURN_TIME_LIMIT: usize = 0x7EA8;
|
| 426 |
+
pub const SOUND_AVAILABLE: usize = 0x7EF8;
|
| 427 |
/// Scale factor used by DrawCrosshairLine (multiplied by 0x140000).
|
| 428 |
pub const CROSSHAIR_SCALE: usize = 0x8150;
|
| 429 |
+
/// Turn status text (null-terminated ASCII, shown during gameplay).
|
| 430 |
+
pub const TURN_STATUS_TEXT: usize = 0x818C;
|
| 431 |
+
/// Checkpoint active flag.
|
| 432 |
+
pub const CHECKPOINT_ACTIVE: usize = 0x98A4;
|
| 433 |
+
/// Fast-forward request flag.
|
| 434 |
+
pub const FAST_FORWARD_REQUEST: usize = 0x98AC;
|
| 435 |
|
| 436 |
// === Speech slot table (DDGame + 0x77E4) ===
|
| 437 |
/// Speech slot table: maps (team, speech_line_id) → DSSound buffer index.
|
|
@@ -22,6 +22,7 @@ pub mod frontend;
|
|
| 22 |
pub mod game_info;
|
| 23 |
pub mod scheme;
|
| 24 |
pub mod speech;
|
|
|
|
| 25 |
pub mod address;
|
| 26 |
pub mod rebase;
|
| 27 |
pub mod wa;
|
|
|
|
| 22 |
pub mod game_info;
|
| 23 |
pub mod scheme;
|
| 24 |
pub mod speech;
|
| 25 |
+
pub mod turn_order;
|
| 26 |
pub mod address;
|
| 27 |
pub mod rebase;
|
| 28 |
pub mod wa;
|
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Turn order display widget — the in-game team banner list with health bars.
|
| 2 |
+
//!
|
| 3 |
+
//! Three-level hierarchy of UI components:
|
| 4 |
+
//! - [`TurnOrderWidget`] — top level, groups teams by alliance
|
| 5 |
+
//! - [`TurnOrderAllianceGroup`] — per-alliance, holds teams sharing an alliance
|
| 6 |
+
//! - [`TurnOrderTeamEntry`] — per-team, renders banner + health bar
|
| 7 |
+
//!
|
| 8 |
+
//! All inherit from an animated item list base class (vtable 0x669C90) that
|
| 9 |
+
//! provides sin-table interpolated sliding transitions between items.
|
| 10 |
+
|
| 11 |
+
use crate::ddgame::DDGame;
|
| 12 |
+
|
| 13 |
+
/// Animated item list — base class for the turn order hierarchy.
|
| 14 |
+
///
|
| 15 |
+
/// Generic container with count/capacity/item array and animation state
|
| 16 |
+
/// for smooth sliding transitions between items using sin-table interpolation.
|
| 17 |
+
///
|
| 18 |
+
/// Vtable: 0x669C90. Constructor: 0x5457D0.
|
| 19 |
+
/// Size: 0x2C bytes (11 DWORDs).
|
| 20 |
+
#[repr(C)]
|
| 21 |
+
pub struct AnimatedItemList {
|
| 22 |
+
/// 0x00: Vtable pointer.
|
| 23 |
+
pub vtable: u32,
|
| 24 |
+
/// 0x04: Number of items currently in the list.
|
| 25 |
+
pub count: i32,
|
| 26 |
+
/// 0x08: Maximum capacity (6 for turn order lists).
|
| 27 |
+
pub capacity: i32,
|
| 28 |
+
/// 0x0C: Pointer to allocated array of item pointers.
|
| 29 |
+
pub items: *mut *mut u8,
|
| 30 |
+
/// 0x10: Current active item index (-1 = none).
|
| 31 |
+
pub current_index: i32,
|
| 32 |
+
/// 0x14: Animation progress counter.
|
| 33 |
+
pub animation_progress: i32,
|
| 34 |
+
/// 0x18: Animation mode (0 = none, 1 = slide out, 2 = slide in).
|
| 35 |
+
pub animation_mode: i32,
|
| 36 |
+
/// 0x1C: Target item index for animation (-1 = none).
|
| 37 |
+
pub animation_target: i32,
|
| 38 |
+
/// 0x20: Animation offset.
|
| 39 |
+
pub animation_offset: i32,
|
| 40 |
+
/// 0x24: Unknown.
|
| 41 |
+
pub _unknown_24: i32,
|
| 42 |
+
/// 0x28: Initialized flag (set to 1 after setup).
|
| 43 |
+
pub initialized: i32,
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
const _: () = assert!(core::mem::size_of::<AnimatedItemList>() == 0x2C);
|
| 47 |
+
|
| 48 |
+
/// Turn order widget — top-level UI component at DDGame+0x530.
|
| 49 |
+
///
|
| 50 |
+
/// Groups teams by alliance. Each alliance gets a [`TurnOrderAllianceGroup`].
|
| 51 |
+
/// Inherits animated sliding from [`AnimatedItemList`].
|
| 52 |
+
///
|
| 53 |
+
/// Vtable: 0x66A088. Constructor: 0x563D40 (stdcall, params: this, DDGame*).
|
| 54 |
+
/// Destructor: 0x563E90. Size: 0x4C bytes.
|
| 55 |
+
#[repr(C)]
|
| 56 |
+
pub struct TurnOrderWidget {
|
| 57 |
+
/// 0x00-0x2B: AnimatedItemList base class.
|
| 58 |
+
pub base: AnimatedItemList,
|
| 59 |
+
/// 0x2C: Number of unique alliances.
|
| 60 |
+
pub alliance_count: i32,
|
| 61 |
+
/// 0x30-0x44: Alliance group pointers (up to 6).
|
| 62 |
+
pub alliance_groups: [*mut TurnOrderAllianceGroup; 6],
|
| 63 |
+
/// 0x48: DDGame pointer (back-reference).
|
| 64 |
+
pub ddgame: *mut DDGame,
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
const _: () = assert!(core::mem::size_of::<TurnOrderWidget>() == 0x4C);
|
| 68 |
+
|
| 69 |
+
/// Turn order alliance group — per-alliance container.
|
| 70 |
+
///
|
| 71 |
+
/// Holds a list of teams that share the same alliance.
|
| 72 |
+
/// Calls [`AnimatedItemList`] constructor but repurposes base fields.
|
| 73 |
+
///
|
| 74 |
+
/// Vtable: 0x66A04C. Constructor: 0x563B50. Size: 0x30 bytes.
|
| 75 |
+
#[repr(C)]
|
| 76 |
+
pub struct TurnOrderAllianceGroup {
|
| 77 |
+
/// 0x00: Vtable pointer (0x66A04C).
|
| 78 |
+
pub vtable: u32,
|
| 79 |
+
/// 0x04: Alliance ID.
|
| 80 |
+
pub alliance_id: i32,
|
| 81 |
+
/// 0x08: DDGame pointer.
|
| 82 |
+
pub ddgame: *mut DDGame,
|
| 83 |
+
/// 0x0C: Inner team list (AnimatedItemList holding TurnOrderTeamEntry items).
|
| 84 |
+
pub team_list: *mut AnimatedItemList,
|
| 85 |
+
/// 0x10-0x2F: Unknown (zeroed, not set by constructor).
|
| 86 |
+
pub _unknown_10: [u8; 0x30 - 0x10],
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
const _: () = assert!(core::mem::size_of::<TurnOrderAllianceGroup>() == 0x30);
|
| 90 |
+
|
| 91 |
+
/// Turn order team entry — per-team banner with health bar.
|
| 92 |
+
///
|
| 93 |
+
/// Renders the team banner, name text, and health bar in the turn order panel.
|
| 94 |
+
/// The health bar width comes from DDGame.team_health_ratio[team_index]:
|
| 95 |
+
/// `bar_pixels = ratio * 100 >> 16 + 4`.
|
| 96 |
+
///
|
| 97 |
+
/// Vtable: 0x669FA8. Constructor: 0x5630B0. Render: 0x563620. Size: 0x24 bytes.
|
| 98 |
+
#[repr(C)]
|
| 99 |
+
pub struct TurnOrderTeamEntry {
|
| 100 |
+
/// 0x00: Vtable pointer (0x669FA8).
|
| 101 |
+
pub vtable: u32,
|
| 102 |
+
/// 0x04: Team index (1-based).
|
| 103 |
+
pub team_index: i32,
|
| 104 |
+
/// 0x08: Color/style index (from game_info per-team data).
|
| 105 |
+
pub color_index: i32,
|
| 106 |
+
/// 0x0C: DDGame pointer (back-reference for accessing health ratios etc).
|
| 107 |
+
pub ddgame: *mut DDGame,
|
| 108 |
+
/// 0x10: Textbox object pointer (DDDisplay text renderer, 0x158 bytes).
|
| 109 |
+
/// NULL when sound is disabled (headless mode).
|
| 110 |
+
pub textbox: *mut u8,
|
| 111 |
+
/// 0x14: DisplayGfx sprite for the health bar (vtable 0x664144).
|
| 112 |
+
pub health_bar_sprite: *mut u8,
|
| 113 |
+
/// 0x18: Cached bar width (pixels). Redrawn when this changes.
|
| 114 |
+
pub cached_bar_width: i32,
|
| 115 |
+
/// 0x1C: Cached flash state (eliminated team flashing).
|
| 116 |
+
pub cached_flash_state: i32,
|
| 117 |
+
/// 0x20: Bar height (pixels).
|
| 118 |
+
pub bar_height: i32,
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
const _: () = assert!(core::mem::size_of::<TurnOrderTeamEntry>() == 0x24);
|
|
@@ -10,7 +10,9 @@
|
|
| 10 |
//!
|
| 11 |
//! This is the same mechanism triggered by key 0x35 (spacebar) during replay.
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
| 14 |
|
| 15 |
use crate::hook;
|
| 16 |
use crate::log_line;
|
|
@@ -19,9 +21,16 @@ use openwa_core::address::va;
|
|
| 19 |
use openwa_core::ddgame::DDGame;
|
| 20 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
/// Trampoline to the original TurnManager_ProcessFrame.
|
| 23 |
static ORIG_TURN_MANAGER: AtomicU32 = AtomicU32::new(0);
|
| 24 |
|
|
|
|
|
|
|
|
|
|
| 25 |
/// Get the DDGame pointer (session+0xA0 → DDGameWrapper.ddgame).
|
| 26 |
#[inline]
|
| 27 |
unsafe fn get_ddgame() -> *mut DDGame {
|
|
@@ -36,10 +45,94 @@ unsafe fn get_ddgame() -> *mut DDGame {
|
|
| 36 |
(*wrapper_ptr).ddgame
|
| 37 |
}
|
| 38 |
|
| 39 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
///
|
| 41 |
-
///
|
| 42 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
unsafe extern "stdcall" fn hook_turn_manager(turngame: u32) {
|
| 44 |
// Call original first
|
| 45 |
let orig: unsafe extern "stdcall" fn(u32) =
|
|
@@ -51,16 +144,19 @@ unsafe extern "stdcall" fn hook_turn_manager(turngame: u32) {
|
|
| 51 |
return;
|
| 52 |
}
|
| 53 |
|
| 54 |
-
//
|
| 55 |
-
(
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
|
| 58 |
pub fn install() -> Result<(), String> {
|
| 59 |
-
if std::env::var("OPENWA_REPLAY_TEST").
|
| 60 |
-
|
|
|
|
| 61 |
}
|
| 62 |
|
| 63 |
-
let _ = log_line("[Input]
|
| 64 |
|
| 65 |
unsafe {
|
| 66 |
let trampoline = hook::install(
|
|
|
|
| 10 |
//!
|
| 11 |
//! This is the same mechanism triggered by key 0x35 (spacebar) during replay.
|
| 12 |
|
| 13 |
+
#![allow(dead_code)]
|
| 14 |
+
|
| 15 |
+
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
| 16 |
|
| 17 |
use crate::hook;
|
| 18 |
use crate::log_line;
|
|
|
|
| 21 |
use openwa_core::ddgame::DDGame;
|
| 22 |
use openwa_core::ddgame_wrapper::DDGameWrapper;
|
| 23 |
|
| 24 |
+
extern "system" {
|
| 25 |
+
fn IsBadReadPtr(lp: *const u8, ucb: u32) -> i32;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
/// Trampoline to the original TurnManager_ProcessFrame.
|
| 29 |
static ORIG_TURN_MANAGER: AtomicU32 = AtomicU32::new(0);
|
| 30 |
|
| 31 |
+
/// Whether to set fast-forward flag (only in replay test mode).
|
| 32 |
+
static FAST_FORWARD: AtomicBool = AtomicBool::new(false);
|
| 33 |
+
|
| 34 |
/// Get the DDGame pointer (session+0xA0 → DDGameWrapper.ddgame).
|
| 35 |
#[inline]
|
| 36 |
unsafe fn get_ddgame() -> *mut DDGame {
|
|
|
|
| 45 |
(*wrapper_ptr).ddgame
|
| 46 |
}
|
| 47 |
|
| 48 |
+
/// Check if a pointer is safe to read.
|
| 49 |
+
#[inline]
|
| 50 |
+
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 of DDGame as DWORDs with classification.
|
| 55 |
///
|
| 56 |
+
/// Classifies each non-zero DWORD as vtable (.rdata), code (.text), data,
|
| 57 |
+
/// heap object (dereferences to check for vtable), small integer, or raw value.
|
| 58 |
+
pub unsafe fn dump_region(ddgame: *mut DDGame, offset: usize, size: usize, label: &str) {
|
| 59 |
+
let base_ptr = ddgame as *const u8;
|
| 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=== DDGame+0x{:04X}..0x{:04X}: {} ===", offset, offset + size, label));
|
| 64 |
+
|
| 65 |
+
let dword_count = size / 4;
|
| 66 |
+
for i in 0..dword_count {
|
| 67 |
+
let field_offset = offset + i * 4;
|
| 68 |
+
let val = *(base_ptr.add(field_offset) as *const u32);
|
| 69 |
+
if val == 0 {
|
| 70 |
+
continue; // Skip zeros to reduce noise
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
let ghidra_val = val.wrapping_sub(delta);
|
| 74 |
+
|
| 75 |
+
// Check if value itself is in .rdata (direct vtable pointer)
|
| 76 |
+
if ghidra_val >= va::RDATA_START && ghidra_val < va::DATA_START {
|
| 77 |
+
if can_read(val, 4) {
|
| 78 |
+
let vt0 = *(val as *const u32);
|
| 79 |
+
let _ = log_line(&format!(
|
| 80 |
+
" +0x{:04X}: 0x{:08X} [VTABLE] g:0x{:08X} vt[0]=g:0x{:08X}",
|
| 81 |
+
field_offset, val, ghidra_val, vt0.wrapping_sub(delta)
|
| 82 |
+
));
|
| 83 |
+
} else {
|
| 84 |
+
let _ = log_line(&format!(
|
| 85 |
+
" +0x{:04X}: 0x{:08X} [VTABLE] g:0x{:08X} (unreadable)",
|
| 86 |
+
field_offset, val, ghidra_val
|
| 87 |
+
));
|
| 88 |
+
}
|
| 89 |
+
} else if ghidra_val >= va::TEXT_START && ghidra_val <= va::TEXT_END {
|
| 90 |
+
let _ = log_line(&format!(
|
| 91 |
+
" +0x{:04X}: 0x{:08X} [CODE] g:0x{:08X}",
|
| 92 |
+
field_offset, val, ghidra_val
|
| 93 |
+
));
|
| 94 |
+
} else if ghidra_val >= va::DATA_START && ghidra_val < 0x008C5000 {
|
| 95 |
+
let _ = log_line(&format!(
|
| 96 |
+
" +0x{:04X}: 0x{:08X} [DATA] g:0x{:08X}",
|
| 97 |
+
field_offset, val, ghidra_val
|
| 98 |
+
));
|
| 99 |
+
} else if val < 0x10000 {
|
| 100 |
+
let _ = log_line(&format!(
|
| 101 |
+
" +0x{:04X}: 0x{:08X} [small={}]",
|
| 102 |
+
field_offset, val, val
|
| 103 |
+
));
|
| 104 |
+
} else if can_read(val, 4) {
|
| 105 |
+
// Heap pointer — safely read first DWORD to check for vtable
|
| 106 |
+
let first = *(val as *const u32);
|
| 107 |
+
let ghidra_first = first.wrapping_sub(delta);
|
| 108 |
+
if ghidra_first >= va::RDATA_START && ghidra_first < va::DATA_START {
|
| 109 |
+
// It's an object with a vtable!
|
| 110 |
+
let vt0_str = if can_read(first, 4) {
|
| 111 |
+
let vt0 = *(first as *const u32);
|
| 112 |
+
format!("vt[0]=g:0x{:08X}", vt0.wrapping_sub(delta))
|
| 113 |
+
} else {
|
| 114 |
+
"vt[0]=?".to_string()
|
| 115 |
+
};
|
| 116 |
+
let _ = log_line(&format!(
|
| 117 |
+
" +0x{:04X}: 0x{:08X} [OBJECT] vtable=g:0x{:08X} {}",
|
| 118 |
+
field_offset, val, ghidra_first, vt0_str
|
| 119 |
+
));
|
| 120 |
+
} else {
|
| 121 |
+
let _ = log_line(&format!(
|
| 122 |
+
" +0x{:04X}: 0x{:08X} [ptr] *=0x{:08X}",
|
| 123 |
+
field_offset, val, first
|
| 124 |
+
));
|
| 125 |
+
}
|
| 126 |
+
} else {
|
| 127 |
+
let _ = log_line(&format!(
|
| 128 |
+
" +0x{:04X}: 0x{:08X} [value]",
|
| 129 |
+
field_offset, val
|
| 130 |
+
));
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
/// Hook for TurnManager_ProcessFrame (stdcall, 1 param = TurnGame*).
|
| 136 |
unsafe extern "stdcall" fn hook_turn_manager(turngame: u32) {
|
| 137 |
// Call original first
|
| 138 |
let orig: unsafe extern "stdcall" fn(u32) =
|
|
|
|
| 144 |
return;
|
| 145 |
}
|
| 146 |
|
| 147 |
+
// Fast-forward for replay test
|
| 148 |
+
if FAST_FORWARD.load(Ordering::Relaxed) {
|
| 149 |
+
(*ddgame).fast_forward_active = 1;
|
| 150 |
+
}
|
| 151 |
}
|
| 152 |
|
| 153 |
pub fn install() -> Result<(), String> {
|
| 154 |
+
if std::env::var("OPENWA_REPLAY_TEST").is_ok() {
|
| 155 |
+
FAST_FORWARD.store(true, Ordering::Relaxed);
|
| 156 |
+
let _ = log_line("[Input] Replay test mode — fast-forward enabled");
|
| 157 |
}
|
| 158 |
|
| 159 |
+
let _ = log_line("[Input] Hooking TurnManager_ProcessFrame");
|
| 160 |
|
| 161 |
unsafe {
|
| 162 |
let trampoline = hook::install(
|