feat: type all 35 DDDisplay vtable slots, add stack traces to traps
Browse filesType all non-stub DDDisplay vtable slots (35/38, up from 8). Slots use
Fixed for 16.16 fixed-point coordinates where the function does camera
offset multiplication. Rename 24 functions in Ghidra with signatures and
DDDisplay struct type. Create DDDisplay struct in Ghidra (0x24E28 bytes).
Key findings from slot 19 (blit_sprite) investigation:
- Called from LoadingProgressTick via intermediate vtable dispatch
- EBX/EDI "register params" are LTCG artifacts: the compiler proved
display width/height survive in callee-saved registers from a prior
get_dimensions call and reads them directly as clip bounds.
Add log_stack_trace() to hook.rs and wire it into install_trap! macro
so traps log a symbolicated EBP-chain call stack before panicking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -429,6 +429,8 @@ pub mod va {
|
|
| 429 |
crate::define_addresses! {
|
| 430 |
/// DDDisplay::Init
|
| 431 |
fn/Usercall DDISPLAY_INIT = 0x0056_9D00;
|
|
|
|
|
|
|
| 432 |
/// Streaming audio constructor
|
| 433 |
fn/Stdcall STREAMING_AUDIO_CTOR = 0x0058_BC10;
|
| 434 |
/// DDNetGameWrapper constructor
|
|
|
|
| 429 |
crate::define_addresses! {
|
| 430 |
/// DDDisplay::Init
|
| 431 |
fn/Usercall DDISPLAY_INIT = 0x0056_9D00;
|
| 432 |
+
/// DDDisplay vtable slot 19 — blit sprite (possibly dead code)
|
| 433 |
+
fn/Thiscall DDISPLAY_BLIT_SPRITE = 0x0056_B080;
|
| 434 |
/// Streaming audio constructor
|
| 435 |
fn/Stdcall STREAMING_AUDIO_CTOR = 0x0058_BC10;
|
| 436 |
/// DDNetGameWrapper constructor
|
|
@@ -1,14 +1,23 @@
|
|
|
|
|
|
|
|
| 1 |
/// DDDisplay — display/rendering subsystem.
|
| 2 |
///
|
| 3 |
/// Constructor: DDDisplay__Init (0x569D00).
|
| 4 |
-
/// Vtable: 0x66A218 (38
|
| 5 |
-
/// Destructor:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
///
|
| 7 |
-
///
|
| 8 |
-
///
|
| 9 |
-
///
|
|
|
|
|
|
|
|
|
|
| 10 |
///
|
| 11 |
-
/// OPAQUE: Full struct
|
| 12 |
#[repr(C)]
|
| 13 |
pub struct DDDisplay {
|
| 14 |
/// 0x000: Vtable pointer (0x66A218)
|
|
@@ -17,23 +26,202 @@ pub struct DDDisplay {
|
|
| 17 |
|
| 18 |
/// DDDisplay vtable (0x66A218, 38 slots).
|
| 19 |
///
|
| 20 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
#[openwa_core::vtable(size = 38, va = 0x0066_A218, class = "DDDisplay")]
|
| 22 |
pub struct DDDisplayVtable {
|
| 23 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
#[slot(4)]
|
| 25 |
pub set_layer_color: fn(this: *mut DDDisplay, layer: i32, color: i32),
|
| 26 |
-
/// set active layer, returns layer context ptr
|
| 27 |
#[slot(5)]
|
| 28 |
pub set_active_layer: fn(this: *mut DDDisplay, layer: i32) -> *mut u8,
|
| 29 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
#[slot(23)]
|
| 31 |
-
pub set_layer_visibility: fn(this: *mut DDDisplay, layer: i32,
|
| 32 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
///
|
| 34 |
-
///
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
#[slot(34)]
|
| 38 |
pub load_font: fn(
|
| 39 |
this: *mut DDDisplay,
|
|
@@ -42,12 +230,7 @@ pub struct DDDisplayVtable {
|
|
| 42 |
gfx: *mut u8,
|
| 43 |
filename: *const core::ffi::c_char,
|
| 44 |
) -> u32,
|
| 45 |
-
/// load .fex font extension
|
| 46 |
-
///
|
| 47 |
-
/// DDDisplay__LoadFontExtension (0x523620).
|
| 48 |
-
/// font_id: slot index. path: filesystem path to .fex file.
|
| 49 |
-
/// char_map: 61-byte character code table. palette_value: color from GfxColorTable.
|
| 50 |
-
/// flag: unknown (always 0 from DDGameWrapper__LoadFontExtension).
|
| 51 |
#[slot(35)]
|
| 52 |
pub load_font_extension: fn(
|
| 53 |
this: *mut DDDisplay,
|
|
@@ -57,24 +240,10 @@ pub struct DDDisplayVtable {
|
|
| 57 |
palette_value: u32,
|
| 58 |
flag: i32,
|
| 59 |
) -> u32,
|
| 60 |
-
/// set font palette for all loaded fonts (RET 0x8)
|
| 61 |
-
///
|
| 62 |
-
/// DDDisplay__SetFontPalette (0x523690).
|
| 63 |
-
/// font_count: total number of font slots loaded.
|
| 64 |
-
/// palette_value: palette/color entry from DDGame GfxColorTable.
|
| 65 |
#[slot(36)]
|
| 66 |
pub set_font_palette: fn(this: *mut DDDisplay, font_count: u32, palette_value: u32),
|
| 67 |
-
/// load sprite with
|
| 68 |
-
#[slot(31)]
|
| 69 |
-
pub load_sprite: fn(
|
| 70 |
-
this: *mut DDDisplay,
|
| 71 |
-
layer: u32,
|
| 72 |
-
id: u32,
|
| 73 |
-
flag: u32,
|
| 74 |
-
gfx: *mut u8,
|
| 75 |
-
name: *const core::ffi::c_char,
|
| 76 |
-
) -> i32,
|
| 77 |
-
/// load sprite by layer (RET 0x10)
|
| 78 |
#[slot(37)]
|
| 79 |
pub load_sprite_by_layer: fn(
|
| 80 |
this: *mut DDDisplay,
|
|
|
|
| 1 |
+
use crate::fixed::Fixed;
|
| 2 |
+
|
| 3 |
/// DDDisplay — display/rendering subsystem.
|
| 4 |
///
|
| 5 |
/// Constructor: DDDisplay__Init (0x569D00).
|
| 6 |
+
/// Vtable: 0x66A218 (38 slots).
|
| 7 |
+
/// Destructor: 0x569CE0.
|
| 8 |
+
///
|
| 9 |
+
/// Actual runtime type is DisplayGfx (0x24E28 bytes), which extends DisplayBase.
|
| 10 |
+
/// Manages layers, sprites, fonts, palettes, and delegates to a renderer backend
|
| 11 |
+
/// (CompatRenderer for D3D/DDraw, OpenGLCPU for OpenGL).
|
| 12 |
///
|
| 13 |
+
/// Key internal fields (offsets from DisplayBase):
|
| 14 |
+
/// - 0x3548/0x354C: display width/height
|
| 15 |
+
/// - 0x3550-0x355C: clip rect (x1, y1, x2, y2)
|
| 16 |
+
/// - 0x3560/0x3564: camera offset (x, y)
|
| 17 |
+
/// - 0x3580-0x3584: bitmap vector (ptr, end)
|
| 18 |
+
/// - 0x3D9C: renderer backend pointer
|
| 19 |
///
|
| 20 |
+
/// OPAQUE: Full struct layout not yet mapped (see DisplayGfx for size).
|
| 21 |
#[repr(C)]
|
| 22 |
pub struct DDDisplay {
|
| 23 |
/// 0x000: Vtable pointer (0x66A218)
|
|
|
|
| 26 |
|
| 27 |
/// DDDisplay vtable (0x66A218, 38 slots).
|
| 28 |
///
|
| 29 |
+
/// Slots 2, 3, 25, 29 are stubs (CGameTask no-ops).
|
| 30 |
+
/// All other slots are standard thiscall.
|
| 31 |
+
///
|
| 32 |
+
/// Coordinate conventions:
|
| 33 |
+
/// - Methods that add `camera * 0x10000` take Fixed (16.16) coordinates.
|
| 34 |
+
/// - Methods that add `camera` directly take pixel-integer coordinates.
|
| 35 |
#[openwa_core::vtable(size = 38, va = 0x0066_A218, class = "DDDisplay")]
|
| 36 |
pub struct DDDisplayVtable {
|
| 37 |
+
/// destructor (0x569CE0, RET 0x4)
|
| 38 |
+
#[slot(0)]
|
| 39 |
+
pub destructor: fn(this: *mut DDDisplay, flags: u8) -> *mut DDDisplay,
|
| 40 |
+
/// get display dimensions in pixels (0x56A460, RET 0x8)
|
| 41 |
+
#[slot(1)]
|
| 42 |
+
pub get_dimensions: fn(this: *mut DDDisplay, out_w: *mut u32, out_h: *mut u32),
|
| 43 |
+
/// set layer color (0x5231E0, RET 0x8)
|
| 44 |
#[slot(4)]
|
| 45 |
pub set_layer_color: fn(this: *mut DDDisplay, layer: i32, color: i32),
|
| 46 |
+
/// set active layer, returns layer context ptr (0x523270, RET 0x4)
|
| 47 |
#[slot(5)]
|
| 48 |
pub set_active_layer: fn(this: *mut DDDisplay, layer: i32) -> *mut u8,
|
| 49 |
+
/// get sprite info by layer and id (0x523500, RET 0x10)
|
| 50 |
+
#[slot(6)]
|
| 51 |
+
pub get_sprite_info: fn(this: *mut DDDisplay, layer: i32, p3: u32, p4: u32) -> u32,
|
| 52 |
+
/// draw text onto a bitmap surface (0x5236B0, RET 0x1C)
|
| 53 |
+
///
|
| 54 |
+
/// font_id low 16 bits = font slot (1-based), high 16 bits = extra flags.
|
| 55 |
+
#[slot(7)]
|
| 56 |
+
pub draw_text_on_bitmap: fn(
|
| 57 |
+
this: *mut DDDisplay,
|
| 58 |
+
font_id: i32,
|
| 59 |
+
bitmap: i32,
|
| 60 |
+
h_align: i32,
|
| 61 |
+
v_align: i32,
|
| 62 |
+
msg: *const core::ffi::c_char,
|
| 63 |
+
a7: i32,
|
| 64 |
+
a8: i32,
|
| 65 |
+
) -> i32,
|
| 66 |
+
/// get font info for a font slot (0x523790, RET 0xC)
|
| 67 |
+
#[slot(8)]
|
| 68 |
+
pub get_font_info: fn(this: *mut DDDisplay, font_id: i32, p3: u32) -> u32,
|
| 69 |
+
/// get font metric (0x523750, RET 0x10)
|
| 70 |
+
#[slot(9)]
|
| 71 |
+
pub get_font_metric: fn(this: *mut DDDisplay, font_id: i32, p3: u32, p4: u32) -> u32,
|
| 72 |
+
/// set font rendering parameter (0x523710, RET 0x10)
|
| 73 |
+
#[slot(10)]
|
| 74 |
+
pub set_font_param: fn(this: *mut DDDisplay, font_id: i32, p3: u32, p4: u32, p5: u32) -> u32,
|
| 75 |
+
/// create bitmap object (0x56B8C0, RET 0xC)
|
| 76 |
+
///
|
| 77 |
+
/// Allocates CBitmap, initializes from sprite data.
|
| 78 |
+
#[slot(11)]
|
| 79 |
+
pub create_bitmap: fn(this: *mut DDDisplay, count: u32, p3: i32, p4: i32),
|
| 80 |
+
/// draw polyline with camera offset (0x56BCC0, RET 0xC)
|
| 81 |
+
///
|
| 82 |
+
/// Transforms point array by camera offset, then draws connected line segments.
|
| 83 |
+
/// Points are pixel-integer pairs.
|
| 84 |
+
#[slot(12)]
|
| 85 |
+
pub draw_polyline: fn(this: *mut DDDisplay, points: *mut i32, count: i32, color: u32),
|
| 86 |
+
/// draw line with camera offset (0x56BDB0, RET 0x18)
|
| 87 |
+
///
|
| 88 |
+
/// Coordinates are fixed-point; camera is applied as `camera * 0x10000 + coord`.
|
| 89 |
+
#[slot(13)]
|
| 90 |
+
pub draw_line: fn(
|
| 91 |
+
this: *mut DDDisplay,
|
| 92 |
+
x1: Fixed,
|
| 93 |
+
y1: Fixed,
|
| 94 |
+
x2: Fixed,
|
| 95 |
+
y2: Fixed,
|
| 96 |
+
color1: u32,
|
| 97 |
+
color2: u32,
|
| 98 |
+
),
|
| 99 |
+
/// draw line with camera offset and clip (0x56BD50, RET 0x14)
|
| 100 |
+
#[slot(14)]
|
| 101 |
+
pub draw_line_clipped:
|
| 102 |
+
fn(this: *mut DDDisplay, x1: Fixed, y1: Fixed, x2: Fixed, y2: Fixed, color: u32),
|
| 103 |
+
/// draw repeated pixels along a vector (0x56BE10, RET 0x18)
|
| 104 |
+
///
|
| 105 |
+
/// Draws count+1 pixels starting at (x,y), stepping by (dx,dy) each iteration.
|
| 106 |
+
/// All coordinates are fixed-point.
|
| 107 |
+
#[slot(15)]
|
| 108 |
+
pub draw_pixel_strip:
|
| 109 |
+
fn(this: *mut DDDisplay, x: Fixed, y: Fixed, dx: Fixed, dy: Fixed, count: i32, color: u32),
|
| 110 |
+
/// draw crosshair pattern — 9 pixels in a cross (0x56BE80, RET 0x10)
|
| 111 |
+
///
|
| 112 |
+
/// Pixel-integer coordinates (adds camera directly).
|
| 113 |
+
#[slot(16)]
|
| 114 |
+
pub draw_crosshair: fn(this: *mut DDDisplay, x: i32, y: i32, color_fg: u32, color_bg: u32),
|
| 115 |
+
/// draw outlined pixel — center + 4 cardinal neighbors (0x56BFD0, RET 0x10)
|
| 116 |
+
///
|
| 117 |
+
/// Pixel-integer coordinates.
|
| 118 |
+
#[slot(17)]
|
| 119 |
+
pub draw_outlined_pixel: fn(this: *mut DDDisplay, x: i32, y: i32, color_fg: u32, color_bg: i32),
|
| 120 |
+
/// fill rectangle with camera offset and clipping (0x56B810, RET 0x14)
|
| 121 |
+
///
|
| 122 |
+
/// Pixel-integer coordinates (adds camera directly, then clips).
|
| 123 |
+
#[slot(18)]
|
| 124 |
+
pub fill_rect: fn(this: *mut DDDisplay, x1: i32, y1: i32, x2: i32, y2: i32, color: u32),
|
| 125 |
+
/// blit sprite to display (0x56B080, RET 0x10)
|
| 126 |
+
///
|
| 127 |
+
/// Complex sprite blitting with orientation, palette, and blend flags.
|
| 128 |
+
/// x/y are fixed-point world coordinates (`>> 0x10` internally).
|
| 129 |
+
/// param_4 low 16 bits = sprite ID, high bits = orientation/blend flags.
|
| 130 |
+
/// param_5 = palette/opacity value.
|
| 131 |
+
///
|
| 132 |
+
/// Note: the original code also reads EBX, ESI, EDI set by the caller
|
| 133 |
+
/// (sprite width, sprite height, extra flags) via callee-saved registers.
|
| 134 |
+
/// These are not part of the thiscall ABI and cannot be expressed here.
|
| 135 |
+
#[slot(19)]
|
| 136 |
+
pub blit_sprite: fn(this: *mut DDDisplay, x: Fixed, y: Fixed, sprite_flags: u32, palette: u32),
|
| 137 |
+
/// draw scaled/rotated sprite (0x56B660, RET 0x20)
|
| 138 |
+
///
|
| 139 |
+
/// x/y are fixed-point world coordinates.
|
| 140 |
+
/// Dispatches by flags: bit 21 mirror, bit 26 additive, bit 27/28 blend modes.
|
| 141 |
+
#[slot(20)]
|
| 142 |
+
pub draw_scaled_sprite: fn(
|
| 143 |
+
this: *mut DDDisplay,
|
| 144 |
+
x: Fixed,
|
| 145 |
+
y: Fixed,
|
| 146 |
+
sprite: u32,
|
| 147 |
+
src_x: i32,
|
| 148 |
+
src_y: i32,
|
| 149 |
+
src_w: i32,
|
| 150 |
+
src_h: i32,
|
| 151 |
+
flags: u32,
|
| 152 |
+
),
|
| 153 |
+
/// draw via object callback (0x56B7C0, RET 0x14)
|
| 154 |
+
///
|
| 155 |
+
/// Calls vtable[2] on the object pointer with camera-adjusted coordinates.
|
| 156 |
+
/// x/y are fixed-point.
|
| 157 |
+
#[slot(21)]
|
| 158 |
+
pub draw_via_callback:
|
| 159 |
+
fn(this: *mut DDDisplay, x: Fixed, y: Fixed, obj: *mut u8, p5: u32, p6: u32),
|
| 160 |
+
/// stream/animation data to display (0x56C5A0, RET 0x10)
|
| 161 |
+
#[slot(22)]
|
| 162 |
+
pub stream_data: fn(this: *mut DDDisplay, p2: i32, p3: i32, count: i32, flags: u32),
|
| 163 |
+
/// set layer visibility (0x56A5D0, RET 0x8)
|
| 164 |
#[slot(23)]
|
| 165 |
+
pub set_layer_visibility: fn(this: *mut DDDisplay, layer: i32, visible: i32),
|
| 166 |
+
/// update palette from palette data (0x56A610, RET 0x8)
|
| 167 |
+
#[slot(24)]
|
| 168 |
+
pub update_palette: fn(this: *mut DDDisplay, palette_data: *mut i16, p3: i32),
|
| 169 |
+
// Slot 25: stub (CGameTask__vt19)
|
| 170 |
+
/// flush pending render state (0x56A580, plain RET)
|
| 171 |
+
///
|
| 172 |
+
/// No stack params. Releases renderer lock and
|
| 173 |
+
/// calls through DDDisplayWrapper vtable to finalize.
|
| 174 |
+
#[slot(26)]
|
| 175 |
+
pub flush_render: fn(this: *mut DDDisplay),
|
| 176 |
+
/// set camera offset (0x56CC40, RET 0x8)
|
| 177 |
///
|
| 178 |
+
/// Fixed-point input; internally `>> 16` to pixel integers stored at +0x3560/+0x3564.
|
| 179 |
+
#[slot(27)]
|
| 180 |
+
pub set_camera_offset: fn(this: *mut DDDisplay, x: Fixed, y: Fixed),
|
| 181 |
+
/// set clip rectangle (0x56CC60, RET 0x10)
|
| 182 |
+
///
|
| 183 |
+
/// Fixed-point input; internally `>> 16` to pixel integers, clamped to display dimensions.
|
| 184 |
+
#[slot(28)]
|
| 185 |
+
pub set_clip_rect: fn(this: *mut DDDisplay, x1: Fixed, y1: Fixed, x2: Fixed, y2: Fixed),
|
| 186 |
+
// Slot 29: stub (CGameTask__vt18)
|
| 187 |
+
/// load sprite with extended params (0x523310, RET 0x18)
|
| 188 |
+
///
|
| 189 |
+
/// Allocates 0x17C sprite object, calls DisplayGfx constructor.
|
| 190 |
+
#[slot(30)]
|
| 191 |
+
pub load_sprite_ex:
|
| 192 |
+
fn(this: *mut DDDisplay, mode: i32, id: i32, p4: u32, count: i32, p6: u32, p7: u32) -> i32,
|
| 193 |
+
/// load sprite into layer (0x523400, RET 0x14)
|
| 194 |
+
#[slot(31)]
|
| 195 |
+
pub load_sprite: fn(
|
| 196 |
+
this: *mut DDDisplay,
|
| 197 |
+
layer: u32,
|
| 198 |
+
id: u32,
|
| 199 |
+
flag: u32,
|
| 200 |
+
gfx: *mut u8,
|
| 201 |
+
name: *const core::ffi::c_char,
|
| 202 |
+
) -> i32,
|
| 203 |
+
/// check if a sprite ID is loaded (0x56A480, RET 0x4)
|
| 204 |
+
///
|
| 205 |
+
/// Returns 1 if the sprite exists in any of the three sprite arrays.
|
| 206 |
+
#[slot(32)]
|
| 207 |
+
pub is_sprite_loaded: fn(this: *mut DDDisplay, id: i32) -> u32,
|
| 208 |
+
/// load sprite with complex params (0x5237C0, RET 0x24)
|
| 209 |
+
///
|
| 210 |
+
/// 9 stack params. Dual-path loading depending on layer type.
|
| 211 |
+
#[slot(33)]
|
| 212 |
+
pub load_sprite_complex: fn(
|
| 213 |
+
this: *mut DDDisplay,
|
| 214 |
+
layer: i32,
|
| 215 |
+
p3: u32,
|
| 216 |
+
p4: u32,
|
| 217 |
+
p5: u32,
|
| 218 |
+
p6: u32,
|
| 219 |
+
p7: u32,
|
| 220 |
+
p8: u32,
|
| 221 |
+
p9: u32,
|
| 222 |
+
p10: u32,
|
| 223 |
+
) -> u32,
|
| 224 |
+
/// load .fnt bitmap font into a font slot (0x523560, RET 0x10)
|
| 225 |
#[slot(34)]
|
| 226 |
pub load_font: fn(
|
| 227 |
this: *mut DDDisplay,
|
|
|
|
| 230 |
gfx: *mut u8,
|
| 231 |
filename: *const core::ffi::c_char,
|
| 232 |
) -> u32,
|
| 233 |
+
/// load .fex font extension for a font slot (0x523620, RET 0x14)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
#[slot(35)]
|
| 235 |
pub load_font_extension: fn(
|
| 236 |
this: *mut DDDisplay,
|
|
|
|
| 240 |
palette_value: u32,
|
| 241 |
flag: i32,
|
| 242 |
) -> u32,
|
| 243 |
+
/// set font palette for all loaded fonts (0x523690, RET 0x8)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
#[slot(36)]
|
| 245 |
pub set_font_palette: fn(this: *mut DDDisplay, font_count: u32, palette_value: u32),
|
| 246 |
+
/// load sprite by layer with fallback allocation (0x56A4C0, RET 0x10)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
#[slot(37)]
|
| 248 |
pub load_sprite_by_layer: fn(
|
| 249 |
this: *mut DDDisplay,
|
|
@@ -356,12 +356,53 @@ macro_rules! usercall_trampoline {
|
|
| 356 |
|
| 357 |
pub(crate) use usercall_trampoline;
|
| 358 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
/// Install a panic trap on a fully-converted WA function.
|
| 360 |
///
|
| 361 |
/// Use this when ALL callers of a WA function have been replaced with Rust code.
|
| 362 |
/// The trap verifies our caller analysis by panicking if WA.exe unexpectedly
|
| 363 |
/// calls the function. Each invocation generates a unique trap function with
|
| 364 |
-
/// the function name baked into the panic message.
|
| 365 |
///
|
| 366 |
/// ```ignore
|
| 367 |
/// install_trap!("DDGameWrapper__Constructor", va::CONSTRUCT_DD_GAME_WRAPPER);
|
|
@@ -369,6 +410,9 @@ pub(crate) use usercall_trampoline;
|
|
| 369 |
macro_rules! install_trap {
|
| 370 |
($name:literal, $addr:expr) => {{
|
| 371 |
unsafe extern "C" fn trap() {
|
|
|
|
|
|
|
|
|
|
| 372 |
panic!(concat!(
|
| 373 |
"TRAP: ",
|
| 374 |
$name,
|
|
|
|
| 356 |
|
| 357 |
pub(crate) use usercall_trampoline;
|
| 358 |
|
| 359 |
+
/// Walk the EBP chain and log a symbolicated stack trace.
|
| 360 |
+
///
|
| 361 |
+
/// `ebp` is the current frame pointer; the function walks up to 12 frames,
|
| 362 |
+
/// resolving return addresses via the address registry.
|
| 363 |
+
pub unsafe fn log_stack_trace(name: &str, ebp: u32) {
|
| 364 |
+
use openwa_core::address::va;
|
| 365 |
+
use openwa_core::rebase::rb;
|
| 366 |
+
let delta = rb(va::IMAGE_BASE).wrapping_sub(va::IMAGE_BASE);
|
| 367 |
+
let wa_base = rb(va::IMAGE_BASE);
|
| 368 |
+
|
| 369 |
+
let mut trace = String::new();
|
| 370 |
+
let mut ebp = ebp;
|
| 371 |
+
for depth in 0..12 {
|
| 372 |
+
if !(0x10000..=0x7FFE0000).contains(&ebp) || (ebp & 3) != 0 {
|
| 373 |
+
break;
|
| 374 |
+
}
|
| 375 |
+
if !openwa_core::mem::can_read(ebp, 8) {
|
| 376 |
+
break;
|
| 377 |
+
}
|
| 378 |
+
let ret_addr = *((ebp + 4) as *const u32);
|
| 379 |
+
let next_ebp = *(ebp as *const u32);
|
| 380 |
+
let ghidra_ret = ret_addr.wrapping_sub(delta);
|
| 381 |
+
if depth > 0 {
|
| 382 |
+
trace.push_str("<-");
|
| 383 |
+
}
|
| 384 |
+
let in_wa = ret_addr >= wa_base && ret_addr < wa_base + 0x300000;
|
| 385 |
+
if in_wa {
|
| 386 |
+
trace.push_str(&openwa_core::registry::format_va(ghidra_ret));
|
| 387 |
+
} else {
|
| 388 |
+
use core::fmt::Write;
|
| 389 |
+
let _ = write!(trace, "r:{:08X}", ret_addr);
|
| 390 |
+
}
|
| 391 |
+
if next_ebp <= ebp {
|
| 392 |
+
break;
|
| 393 |
+
}
|
| 394 |
+
ebp = next_ebp;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
let _ = crate::log_line(&format!("[TRAP] {} stack=[{}]", name, trace));
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
/// Install a panic trap on a fully-converted WA function.
|
| 401 |
///
|
| 402 |
/// Use this when ALL callers of a WA function have been replaced with Rust code.
|
| 403 |
/// The trap verifies our caller analysis by panicking if WA.exe unexpectedly
|
| 404 |
/// calls the function. Each invocation generates a unique trap function with
|
| 405 |
+
/// the function name baked into the panic message, plus a stack trace.
|
| 406 |
///
|
| 407 |
/// ```ignore
|
| 408 |
/// install_trap!("DDGameWrapper__Constructor", va::CONSTRUCT_DD_GAME_WRAPPER);
|
|
|
|
| 410 |
macro_rules! install_trap {
|
| 411 |
($name:literal, $addr:expr) => {{
|
| 412 |
unsafe extern "C" fn trap() {
|
| 413 |
+
let ebp: u32;
|
| 414 |
+
core::arch::asm!("mov {}, ebp", out(reg) ebp);
|
| 415 |
+
hook::log_stack_trace($name, ebp);
|
| 416 |
panic!(concat!(
|
| 417 |
"TRAP: ",
|
| 418 |
$name,
|