refactor: propagate types and trim comments in render hook shims
Browse filesReplace u32 params with Fixed, SpriteOp, and typed pointers across
render_queue.rs and display_vtable.rs. Propagate to core callees
(blit_sprite, bungee_trail, crosshair_line). Trim comments to just
Ghidra name + address.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/render/bungee_trail.rs +2 -2
- crates/openwa-core/src/render/crosshair_line.rs +5 -6
- crates/openwa-core/src/render/display/blit_sprite.rs +1 -2
- crates/openwa-dll/src/replacements/render/display_vtable.rs +53 -131
- crates/openwa-dll/src/replacements/render/render_queue.rs +123 -187
crates/openwa-core/src/render/bungee_trail.rs
CHANGED
|
@@ -21,8 +21,8 @@ use crate::trig::trig_lookup;
|
|
| 21 |
///
|
| 22 |
/// `task_ptr` must point to a valid `BungeeTrailTask`. ASLR rebase must be
|
| 23 |
/// initialized.
|
| 24 |
-
pub unsafe fn draw_bungee_trail(
|
| 25 |
-
let task = &*
|
| 26 |
|
| 27 |
if task.trail_visible == 0 {
|
| 28 |
return;
|
|
|
|
| 21 |
///
|
| 22 |
/// `task_ptr` must point to a valid `BungeeTrailTask`. ASLR rebase must be
|
| 23 |
/// initialized.
|
| 24 |
+
pub unsafe fn draw_bungee_trail(task: *const BungeeTrailTask, style: u32, fill: u32) {
|
| 25 |
+
let task = &*task;
|
| 26 |
|
| 27 |
if task.trail_visible == 0 {
|
| 28 |
return;
|
crates/openwa-core/src/render/crosshair_line.rs
CHANGED
|
@@ -22,11 +22,10 @@ use crate::trig::trig_lookup;
|
|
| 22 |
///
|
| 23 |
/// `task_ptr` must point to a valid `WeaponAimTask`. ASLR rebase must be
|
| 24 |
/// initialized.
|
| 25 |
-
pub unsafe fn draw_crosshair_line(
|
| 26 |
-
let
|
| 27 |
-
let gt = &task.game_task;
|
| 28 |
|
| 29 |
-
if task.aim_active == 0 {
|
| 30 |
return;
|
| 31 |
}
|
| 32 |
|
|
@@ -36,7 +35,7 @@ pub unsafe fn draw_crosshair_line(task_ptr: u32) {
|
|
| 36 |
let start_x = gt.pos_x.0;
|
| 37 |
let start_y = gt.pos_y.0;
|
| 38 |
|
| 39 |
-
let angle = task.aim_angle;
|
| 40 |
|
| 41 |
// Trig interpolation
|
| 42 |
let sin_table = rb(va::G_SIN_TABLE) as *const i32;
|
|
@@ -45,7 +44,7 @@ pub unsafe fn draw_crosshair_line(task_ptr: u32) {
|
|
| 45 |
let cos_interp = trig_lookup(cos_table, angle);
|
| 46 |
|
| 47 |
// Scale = fixed_mul(DDGame.parallax_scale, 0x140000) + task.aim_range_offset
|
| 48 |
-
let scale = Fixed(ddgame.parallax_scale).mul_raw(Fixed(0x14_0000)).0 + task.aim_range_offset;
|
| 49 |
|
| 50 |
// Endpoint = start + direction * scale
|
| 51 |
let mut endpoint_x = Fixed(sin_interp.0)
|
|
|
|
| 22 |
///
|
| 23 |
/// `task_ptr` must point to a valid `WeaponAimTask`. ASLR rebase must be
|
| 24 |
/// initialized.
|
| 25 |
+
pub unsafe fn draw_crosshair_line(task: *const WeaponAimTask) {
|
| 26 |
+
let gt = &(*task).game_task;
|
|
|
|
| 27 |
|
| 28 |
+
if (*task).aim_active == 0 {
|
| 29 |
return;
|
| 30 |
}
|
| 31 |
|
|
|
|
| 35 |
let start_x = gt.pos_x.0;
|
| 36 |
let start_y = gt.pos_y.0;
|
| 37 |
|
| 38 |
+
let angle = (*task).aim_angle;
|
| 39 |
|
| 40 |
// Trig interpolation
|
| 41 |
let sin_table = rb(va::G_SIN_TABLE) as *const i32;
|
|
|
|
| 44 |
let cos_interp = trig_lookup(cos_table, angle);
|
| 45 |
|
| 46 |
// Scale = fixed_mul(DDGame.parallax_scale, 0x140000) + task.aim_range_offset
|
| 47 |
+
let scale = Fixed(ddgame.parallax_scale).mul_raw(Fixed(0x14_0000)).0 + (*task).aim_range_offset;
|
| 48 |
|
| 49 |
// Endpoint = start + direction * scale
|
| 50 |
let mut endpoint_x = Fixed(sin_interp.0)
|
crates/openwa-core/src/render/display/blit_sprite.rs
CHANGED
|
@@ -23,7 +23,7 @@ pub unsafe fn blit_sprite(
|
|
| 23 |
this: *mut DisplayGfx,
|
| 24 |
x: Fixed,
|
| 25 |
y: Fixed,
|
| 26 |
-
|
| 27 |
palette: u32,
|
| 28 |
) {
|
| 29 |
let gfx = this;
|
|
@@ -32,7 +32,6 @@ pub unsafe fn blit_sprite(
|
|
| 32 |
// ---------------------------------------------------------------
|
| 33 |
// Extract sprite index and flags
|
| 34 |
// ---------------------------------------------------------------
|
| 35 |
-
let sprite = SpriteOp(sprite_flags);
|
| 36 |
let flags = sprite.flags();
|
| 37 |
let sprite_id = sprite.index() as u32;
|
| 38 |
|
|
|
|
| 23 |
this: *mut DisplayGfx,
|
| 24 |
x: Fixed,
|
| 25 |
y: Fixed,
|
| 26 |
+
sprite: SpriteOp,
|
| 27 |
palette: u32,
|
| 28 |
) {
|
| 29 |
let gfx = this;
|
|
|
|
| 32 |
// ---------------------------------------------------------------
|
| 33 |
// Extract sprite index and flags
|
| 34 |
// ---------------------------------------------------------------
|
|
|
|
| 35 |
let flags = sprite.flags();
|
| 36 |
let sprite_id = sprite.index() as u32;
|
| 37 |
|
crates/openwa-dll/src/replacements/render/display_vtable.rs
CHANGED
|
@@ -1,11 +1,5 @@
|
|
| 1 |
-
//! DisplayGfx vtable patches — DisplayBase
|
| 2 |
-
//! and ported DisplayGfx
|
| 3 |
-
//!
|
| 4 |
-
//! Patches DisplayBase vtables in WA.exe's .rdata:
|
| 5 |
-
//! - Primary vtable (0x6645F8): replaces _purecall slots with safe no-op stubs
|
| 6 |
-
//! - Headless vtable (0x66A0F8): replaces destructor with Rust version that
|
| 7 |
-
//! correctly frees our Rust-allocated sprite cache sub-objects
|
| 8 |
-
//! - DisplayGfx vtable (0x66A218): replaces ported methods with Rust
|
| 9 |
|
| 10 |
use core::ffi::c_char;
|
| 11 |
|
|
@@ -18,6 +12,7 @@ use openwa_core::render::display::vtable::{self as display_vtable_impl, DisplayG
|
|
| 18 |
use openwa_core::render::display::DisplayBase;
|
| 19 |
use openwa_core::render::display::DisplayGfx;
|
| 20 |
use openwa_core::render::sprite::gfx_dir::GfxDir;
|
|
|
|
| 21 |
use openwa_core::vtable::patch_vtable;
|
| 22 |
use openwa_core::vtable_replace;
|
| 23 |
use openwa_core::wa_alloc::wa_free;
|
|
@@ -33,8 +28,6 @@ const VTABLE_SLOTS: usize = 32;
|
|
| 33 |
|
| 34 |
unsafe extern "thiscall" fn noop_thiscall(_this: *mut u8) {}
|
| 35 |
|
| 36 |
-
/// Rust destructor for headless DisplayBase. Frees the sprite cache chain
|
| 37 |
-
/// (SpriteCache -> FrameCache -> buffer) that was allocated by new_headless().
|
| 38 |
unsafe extern "thiscall" fn headless_destructor(
|
| 39 |
this: *mut DisplayBase,
|
| 40 |
flags: u8,
|
|
@@ -57,22 +50,18 @@ unsafe extern "thiscall" fn headless_destructor(
|
|
| 57 |
this
|
| 58 |
}
|
| 59 |
|
| 60 |
-
//
|
| 61 |
-
|
| 62 |
-
/// Thiscall stub for DisplayGfx::BlitSprite (slot 19, 0x56B080).
|
| 63 |
-
/// Implementation in `openwa_core::render::display::blit_sprite`.
|
| 64 |
unsafe extern "thiscall" fn blit_sprite(
|
| 65 |
this: *mut DisplayGfx,
|
| 66 |
x: Fixed,
|
| 67 |
y: Fixed,
|
| 68 |
-
|
| 69 |
palette: u32,
|
| 70 |
) {
|
| 71 |
-
openwa_core::render::display::blit_sprite::blit_sprite(this, x, y,
|
| 72 |
}
|
| 73 |
|
| 74 |
-
//
|
| 75 |
-
/// Implementation in `openwa_core::render::display::blit_sprite`.
|
| 76 |
unsafe extern "thiscall" fn draw_scaled_sprite(
|
| 77 |
this: *mut DisplayGfx,
|
| 78 |
x: Fixed,
|
|
@@ -89,21 +78,7 @@ unsafe extern "thiscall" fn draw_scaled_sprite(
|
|
| 89 |
);
|
| 90 |
}
|
| 91 |
|
| 92 |
-
//
|
| 93 |
-
// Font vtable method wrappers
|
| 94 |
-
// =========================================================================
|
| 95 |
-
//
|
| 96 |
-
// Font slot wrappers (slots 7/8/9/10/34/35/36) all live in openwa-core's
|
| 97 |
-
// `display::vtable` module. The wrappers in this file (slots 31/34/35/37
|
| 98 |
-
// for sprite/font loading) are thin forwarders only because they need to
|
| 99 |
-
// capture a bridge function pointer (`wa_load_sprite_from_vfs` etc.) at
|
| 100 |
-
// install time.
|
| 101 |
-
|
| 102 |
-
// =========================================================================
|
| 103 |
-
// Sprite loading vtable method wrappers
|
| 104 |
-
// =========================================================================
|
| 105 |
-
|
| 106 |
-
/// Thiscall wrapper for DisplayGfx::LoadSprite (vtable slot 31, 0x523400).
|
| 107 |
unsafe extern "thiscall" fn load_sprite(
|
| 108 |
this: *mut DisplayGfx,
|
| 109 |
layer: u32,
|
|
@@ -123,13 +98,7 @@ unsafe extern "thiscall" fn load_sprite(
|
|
| 123 |
)
|
| 124 |
}
|
| 125 |
|
| 126 |
-
//
|
| 127 |
-
/// Usercall: ECX=gfx, EAX=name, stack(sprite, layer_ctx), RET 0x8.
|
| 128 |
-
///
|
| 129 |
-
/// Verified from caller at 0x523489:
|
| 130 |
-
/// ECX ← layer_contexts[layer] (gfx/VFS context)... wait, re-checked:
|
| 131 |
-
/// ECX ← gfx param, EAX ← name param.
|
| 132 |
-
/// Stack: sprite (EDI from ConstructSprite), layer_ctx.
|
| 133 |
#[unsafe(naked)]
|
| 134 |
unsafe extern "cdecl" fn wa_load_sprite_from_vfs(
|
| 135 |
_sprite: *mut openwa_core::render::sprite::Sprite,
|
|
@@ -151,11 +120,7 @@ unsafe extern "cdecl" fn wa_load_sprite_from_vfs(
|
|
| 151 |
|
| 152 |
static mut LOAD_SPRITE_FROM_VFS_ADDR: u32 = 0;
|
| 153 |
|
| 154 |
-
//
|
| 155 |
-
// `DisplayVtable::get_sprite_frame_for_blit` in openwa-core for the full
|
| 156 |
-
// rationale. Our `blit_sprite` (slot 19) above calls it via the bound
|
| 157 |
-
// vtable wrapper on every sprite render.
|
| 158 |
-
|
| 159 |
unsafe extern "thiscall" fn load_sprite_by_layer(
|
| 160 |
this: *mut DisplayGfx,
|
| 161 |
layer: u32,
|
|
@@ -166,6 +131,7 @@ unsafe extern "thiscall" fn load_sprite_by_layer(
|
|
| 166 |
display_vtable_impl::load_sprite_by_layer(this, layer, id, gfx_dir, name)
|
| 167 |
}
|
| 168 |
|
|
|
|
| 169 |
unsafe extern "thiscall" fn load_font(
|
| 170 |
this: *mut DisplayGfx,
|
| 171 |
mode: u32,
|
|
@@ -176,6 +142,7 @@ unsafe extern "thiscall" fn load_font(
|
|
| 176 |
display_vtable_impl::load_font(this, mode, font_id, gfx_dir, filename)
|
| 177 |
}
|
| 178 |
|
|
|
|
| 179 |
unsafe extern "thiscall" fn load_font_extension(
|
| 180 |
this: *mut DisplayGfx,
|
| 181 |
font_id: i32,
|
|
@@ -187,10 +154,6 @@ unsafe extern "thiscall" fn load_font_extension(
|
|
| 187 |
display_vtable_impl::load_font_extension(this, font_id, path, char_map, palette_value, flag)
|
| 188 |
}
|
| 189 |
|
| 190 |
-
// ---------------------------------------------------------------------------
|
| 191 |
-
// Installation
|
| 192 |
-
// ---------------------------------------------------------------------------
|
| 193 |
-
|
| 194 |
pub fn install() -> Result<(), String> {
|
| 195 |
let _ = log_line("[Display] Patching DisplayBase vtables");
|
| 196 |
|
|
@@ -198,7 +161,6 @@ pub fn install() -> Result<(), String> {
|
|
| 198 |
let purecall_addr = rb(PURECALL);
|
| 199 |
let noop_addr = noop_thiscall as *const () as u32;
|
| 200 |
|
| 201 |
-
// Patch primary vtable (0x6645F8): replace _purecall with no-ops.
|
| 202 |
let primary = rb(va::DISPLAY_BASE_VTABLE) as *mut u32;
|
| 203 |
patch_vtable(primary, VTABLE_SLOTS, |vt| {
|
| 204 |
let mut patched = 0u32;
|
|
@@ -214,81 +176,57 @@ pub fn install() -> Result<(), String> {
|
|
| 214 |
));
|
| 215 |
})?;
|
| 216 |
|
| 217 |
-
// Patch headless vtable (0x66A0F8): replace destructor (slot 0)
|
| 218 |
-
// with our Rust version that frees the Rust-allocated sprite cache.
|
| 219 |
let headless = rb(va::DISPLAY_BASE_HEADLESS_VTABLE) as *mut u32;
|
| 220 |
patch_vtable(headless, VTABLE_SLOTS, |vt| {
|
| 221 |
*vt = headless_destructor as *const () as u32;
|
| 222 |
let _ = log_line("[Display] Headless: patched slot 0 (destructor) -> Rust");
|
| 223 |
})?;
|
| 224 |
|
| 225 |
-
// Initialize bridge address statics for sprite loading
|
| 226 |
LOAD_SPRITE_FROM_VFS_ADDR = rb(va::LOAD_SPRITE_FROM_VFS);
|
| 227 |
|
| 228 |
-
// Patch DisplayGfx vtable (0x66A218): replace ported methods with Rust.
|
| 229 |
vtable_replace!(DisplayGfxVtable, va::DISPLAY_GFX_VTABLE, {
|
| 230 |
-
destructor
|
| 231 |
-
get_dimensions
|
| 232 |
-
set_layer_color
|
| 233 |
-
set_active_layer
|
| 234 |
-
get_sprite_info
|
| 235 |
-
draw_text_on_bitmap
|
| 236 |
-
draw_tiled_bitmap
|
| 237 |
-
get_font_info
|
| 238 |
-
get_font_metric
|
| 239 |
-
set_font_param
|
| 240 |
-
draw_polyline
|
| 241 |
-
draw_line
|
| 242 |
-
draw_line_clipped
|
| 243 |
-
draw_pixel_strip
|
| 244 |
-
draw_crosshair
|
| 245 |
-
draw_outlined_pixel
|
| 246 |
-
fill_rect
|
| 247 |
-
draw_via_callback
|
| 248 |
-
draw_tiled_terrain
|
| 249 |
-
flush_render
|
| 250 |
-
set_camera_offset
|
| 251 |
-
set_clip_rect
|
| 252 |
-
is_sprite_loaded
|
| 253 |
-
load_sprite
|
| 254 |
-
draw_scaled_sprite
|
| 255 |
-
set_layer_visibility
|
| 256 |
-
update_palette
|
| 257 |
-
set_font_palette
|
| 258 |
-
|
| 259 |
-
get_sprite_frame_for_blit
|
| 260 |
-
load_sprite_by_layer
|
| 261 |
-
load_font
|
| 262 |
-
load_font_extension
|
| 263 |
})?;
|
| 264 |
let _ = log_line("[Display] DisplayGfx: patched 32 methods -> Rust");
|
| 265 |
|
| 266 |
-
//
|
| 267 |
-
// WA.exe (no instructions reach vtable[+0x78] on a DisplayGfx) and our
|
| 268 |
-
// own Rust code (no `bind_DisplayVtable!` invocation, no direct call).
|
| 269 |
-
// Trap it so we'll be alerted the moment any future caller appears.
|
| 270 |
hook::install_trap!("DisplayGfx__LoadSpriteEx", va::DISPLAY_GFX_LOAD_SPRITE_EX);
|
| 271 |
|
| 272 |
-
//
|
| 273 |
-
//
|
| 274 |
-
//
|
| 275 |
-
// original `DisplayGfx::GetSpriteFrameForBlit` (0x5237C0) is no
|
| 276 |
-
// longer reached, which means its leaves have no remaining
|
| 277 |
-
// callers in shipping WA either:
|
| 278 |
-
//
|
| 279 |
-
// - `Sprite__GetFrameForBlit` (0x4FAD30): only caller was the
|
| 280 |
-
// original slot 33 dispatcher.
|
| 281 |
-
// - `SpriteBank__GetFrameForBlit` (0x4F9710): same. Also bank
|
| 282 |
-
// objects are themselves unreachable (LoadSpriteEx is trapped).
|
| 283 |
-
// - `FrameCache__Allocate` (0x4FA950): only callers were the
|
| 284 |
-
// two GetFrameForBlit helpers above.
|
| 285 |
-
// - `DisplayBitGrid::SetExternalBuffer` (0x4F6470): only caller
|
| 286 |
-
// was `SpriteBank__GetFrameForBlit`.
|
| 287 |
-
//
|
| 288 |
-
// `Sprite_LZSS_Decode` (0x5B29E0) is NOT trapped — it has
|
| 289 |
-
// independent callers (`IMG_Decode`, `FUN_0048EE32`) reachable
|
| 290 |
-
// from `DDGame__Constructor`/`SoundEmitter`/`LoadHudAndWeaponSprites`,
|
| 291 |
-
// all still bridged. Trapping it would break those.
|
| 292 |
hook::install_trap!("Sprite__GetFrameForBlit", va::SPRITE_GET_FRAME_FOR_BLIT);
|
| 293 |
hook::install_trap!(
|
| 294 |
"SpriteBank__GetFrameForBlit",
|
|
@@ -300,26 +238,10 @@ pub fn install() -> Result<(), String> {
|
|
| 300 |
va::DISPLAY_BIT_GRID_SET_EXTERNAL_BUFFER
|
| 301 |
);
|
| 302 |
|
| 303 |
-
//
|
| 304 |
-
//
|
| 305 |
-
//
|
| 306 |
-
//
|
| 307 |
-
// reached, so its only-callee helpers lose their last live
|
| 308 |
-
// caller too.
|
| 309 |
-
//
|
| 310 |
-
// `TileBitmapSet::Destructor` (0x569BC0) has a *second* xref to
|
| 311 |
-
// `FUN_0056C2E0` (a tile-bitmap-set realloc helper called only
|
| 312 |
-
// from `CBitmap__Constructor_Maybe` at 0x56C060). That second
|
| 313 |
-
// chain is **structurally dead** in shipping WA: 0x56C060 sits
|
| 314 |
-
// in the DisplayGfx vtable .rdata block at slot index 38 (offset
|
| 315 |
-
// 0x98 past the vtable base 0x66A218), but no instruction in
|
| 316 |
-
// WA.exe loads from offset 0x98 of any pointer — verified by
|
| 317 |
-
// byte-pattern search for `FF 90 98 00 00 00` (CALL [reg+0x98])
|
| 318 |
-
// and `8B 4? 98` (MOV reg,[reg+0x98]) returning no DisplayGfx
|
| 319 |
-
// hits. The compiler emitted slots 38/39 of DisplayGfx into
|
| 320 |
-
// .rdata but the C++ source has no live caller for them — they
|
| 321 |
-
// are vestigial vtable entries from a removed feature. So
|
| 322 |
-
// trapping `TileBitmapSet::Destructor` is safe.
|
| 323 |
hook::install_trap!(
|
| 324 |
"DisplayGfx__DestructorImpl",
|
| 325 |
va::DISPLAY_GFX_DESTRUCTOR_IMPL
|
|
|
|
| 1 |
+
//! DisplayGfx vtable patches — DisplayBase purecall stubs, headless destructor,
|
| 2 |
+
//! and ported DisplayGfx method wiring.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
use core::ffi::c_char;
|
| 5 |
|
|
|
|
| 12 |
use openwa_core::render::display::DisplayBase;
|
| 13 |
use openwa_core::render::display::DisplayGfx;
|
| 14 |
use openwa_core::render::sprite::gfx_dir::GfxDir;
|
| 15 |
+
use openwa_core::render::SpriteOp;
|
| 16 |
use openwa_core::vtable::patch_vtable;
|
| 17 |
use openwa_core::vtable_replace;
|
| 18 |
use openwa_core::wa_alloc::wa_free;
|
|
|
|
| 28 |
|
| 29 |
unsafe extern "thiscall" fn noop_thiscall(_this: *mut u8) {}
|
| 30 |
|
|
|
|
|
|
|
| 31 |
unsafe extern "thiscall" fn headless_destructor(
|
| 32 |
this: *mut DisplayBase,
|
| 33 |
flags: u8,
|
|
|
|
| 50 |
this
|
| 51 |
}
|
| 52 |
|
| 53 |
+
// BlitSprite (slot 19, 0x56B080)
|
|
|
|
|
|
|
|
|
|
| 54 |
unsafe extern "thiscall" fn blit_sprite(
|
| 55 |
this: *mut DisplayGfx,
|
| 56 |
x: Fixed,
|
| 57 |
y: Fixed,
|
| 58 |
+
sprite: SpriteOp,
|
| 59 |
palette: u32,
|
| 60 |
) {
|
| 61 |
+
openwa_core::render::display::blit_sprite::blit_sprite(this, x, y, sprite, palette);
|
| 62 |
}
|
| 63 |
|
| 64 |
+
// DrawScaledSprite (slot 20, 0x56B5F0)
|
|
|
|
| 65 |
unsafe extern "thiscall" fn draw_scaled_sprite(
|
| 66 |
this: *mut DisplayGfx,
|
| 67 |
x: Fixed,
|
|
|
|
| 78 |
);
|
| 79 |
}
|
| 80 |
|
| 81 |
+
// LoadSprite (slot 31, 0x523400)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
unsafe extern "thiscall" fn load_sprite(
|
| 83 |
this: *mut DisplayGfx,
|
| 84 |
layer: u32,
|
|
|
|
| 98 |
)
|
| 99 |
}
|
| 100 |
|
| 101 |
+
// LoadSpriteFromVfs (0x4FAAF0) — naked bridge
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
#[unsafe(naked)]
|
| 103 |
unsafe extern "cdecl" fn wa_load_sprite_from_vfs(
|
| 104 |
_sprite: *mut openwa_core::render::sprite::Sprite,
|
|
|
|
| 120 |
|
| 121 |
static mut LOAD_SPRITE_FROM_VFS_ADDR: u32 = 0;
|
| 122 |
|
| 123 |
+
// LoadSpriteByLayer (slot 34)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
unsafe extern "thiscall" fn load_sprite_by_layer(
|
| 125 |
this: *mut DisplayGfx,
|
| 126 |
layer: u32,
|
|
|
|
| 131 |
display_vtable_impl::load_sprite_by_layer(this, layer, id, gfx_dir, name)
|
| 132 |
}
|
| 133 |
|
| 134 |
+
// LoadFont (slot 35)
|
| 135 |
unsafe extern "thiscall" fn load_font(
|
| 136 |
this: *mut DisplayGfx,
|
| 137 |
mode: u32,
|
|
|
|
| 142 |
display_vtable_impl::load_font(this, mode, font_id, gfx_dir, filename)
|
| 143 |
}
|
| 144 |
|
| 145 |
+
// LoadFontExtension (slot 36)
|
| 146 |
unsafe extern "thiscall" fn load_font_extension(
|
| 147 |
this: *mut DisplayGfx,
|
| 148 |
font_id: i32,
|
|
|
|
| 154 |
display_vtable_impl::load_font_extension(this, font_id, path, char_map, palette_value, flag)
|
| 155 |
}
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
pub fn install() -> Result<(), String> {
|
| 158 |
let _ = log_line("[Display] Patching DisplayBase vtables");
|
| 159 |
|
|
|
|
| 161 |
let purecall_addr = rb(PURECALL);
|
| 162 |
let noop_addr = noop_thiscall as *const () as u32;
|
| 163 |
|
|
|
|
| 164 |
let primary = rb(va::DISPLAY_BASE_VTABLE) as *mut u32;
|
| 165 |
patch_vtable(primary, VTABLE_SLOTS, |vt| {
|
| 166 |
let mut patched = 0u32;
|
|
|
|
| 176 |
));
|
| 177 |
})?;
|
| 178 |
|
|
|
|
|
|
|
| 179 |
let headless = rb(va::DISPLAY_BASE_HEADLESS_VTABLE) as *mut u32;
|
| 180 |
patch_vtable(headless, VTABLE_SLOTS, |vt| {
|
| 181 |
*vt = headless_destructor as *const () as u32;
|
| 182 |
let _ = log_line("[Display] Headless: patched slot 0 (destructor) -> Rust");
|
| 183 |
})?;
|
| 184 |
|
|
|
|
| 185 |
LOAD_SPRITE_FROM_VFS_ADDR = rb(va::LOAD_SPRITE_FROM_VFS);
|
| 186 |
|
|
|
|
| 187 |
vtable_replace!(DisplayGfxVtable, va::DISPLAY_GFX_VTABLE, {
|
| 188 |
+
destructor => display_destructor::display_gfx_destructor,
|
| 189 |
+
get_dimensions => display_vtable_impl::get_dimensions,
|
| 190 |
+
set_layer_color => display_vtable_impl::set_layer_color,
|
| 191 |
+
set_active_layer => display_vtable_impl::set_active_layer,
|
| 192 |
+
get_sprite_info => display_vtable_impl::get_sprite_info,
|
| 193 |
+
draw_text_on_bitmap => display_vtable_impl::draw_text_on_bitmap,
|
| 194 |
+
draw_tiled_bitmap => display_vtable_impl::draw_tiled_bitmap,
|
| 195 |
+
get_font_info => display_vtable_impl::get_font_info,
|
| 196 |
+
get_font_metric => display_vtable_impl::get_font_metric,
|
| 197 |
+
set_font_param => display_vtable_impl::set_font_param,
|
| 198 |
+
draw_polyline => display_vtable_impl::draw_polyline,
|
| 199 |
+
draw_line => display_vtable_impl::draw_line,
|
| 200 |
+
draw_line_clipped => display_vtable_impl::draw_line_clipped,
|
| 201 |
+
draw_pixel_strip => display_vtable_impl::draw_pixel_strip,
|
| 202 |
+
draw_crosshair => display_vtable_impl::draw_crosshair,
|
| 203 |
+
draw_outlined_pixel => display_vtable_impl::draw_outlined_pixel,
|
| 204 |
+
fill_rect => display_vtable_impl::fill_rect,
|
| 205 |
+
draw_via_callback => display_vtable_impl::draw_via_callback,
|
| 206 |
+
draw_tiled_terrain => display_vtable_impl::draw_tiled_terrain,
|
| 207 |
+
flush_render => display_vtable_impl::flush_render,
|
| 208 |
+
set_camera_offset => display_vtable_impl::set_camera_offset,
|
| 209 |
+
set_clip_rect => display_vtable_impl::set_clip_rect,
|
| 210 |
+
is_sprite_loaded => display_vtable_impl::is_sprite_loaded,
|
| 211 |
+
load_sprite => load_sprite,
|
| 212 |
+
draw_scaled_sprite => draw_scaled_sprite,
|
| 213 |
+
set_layer_visibility => display_vtable_impl::set_layer_visibility,
|
| 214 |
+
update_palette => display_vtable_impl::update_palette,
|
| 215 |
+
set_font_palette => display_vtable_impl::set_font_palette,
|
| 216 |
+
blit_sprite => blit_sprite,
|
| 217 |
+
get_sprite_frame_for_blit => display_vtable_impl::get_sprite_frame_for_blit,
|
| 218 |
+
load_sprite_by_layer => load_sprite_by_layer,
|
| 219 |
+
load_font => load_font,
|
| 220 |
+
load_font_extension => load_font_extension,
|
| 221 |
})?;
|
| 222 |
let _ = log_line("[Display] DisplayGfx: patched 32 methods -> Rust");
|
| 223 |
|
| 224 |
+
// Slot 30: zero callers in WA.exe or Rust.
|
|
|
|
|
|
|
|
|
|
| 225 |
hook::install_trap!("DisplayGfx__LoadSpriteEx", va::DISPLAY_GFX_LOAD_SPRITE_EX);
|
| 226 |
|
| 227 |
+
// Slot 33 leaf functions — only callers were the original WA-side
|
| 228 |
+
// GetSpriteFrameForBlit (now replaced). Note: Sprite_LZSS_Decode
|
| 229 |
+
// (0x5B29E0) is NOT trapped — it has independent live callers.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
hook::install_trap!("Sprite__GetFrameForBlit", va::SPRITE_GET_FRAME_FOR_BLIT);
|
| 231 |
hook::install_trap!(
|
| 232 |
"SpriteBank__GetFrameForBlit",
|
|
|
|
| 238 |
va::DISPLAY_BIT_GRID_SET_EXTERNAL_BUFFER
|
| 239 |
);
|
| 240 |
|
| 241 |
+
// Slot 0 destructor leaf functions — only caller was the original
|
| 242 |
+
// WA-side DestructorImpl (now replaced). TileBitmapSet::Destructor's
|
| 243 |
+
// second xref (via slot 38) is structurally dead — no instruction
|
| 244 |
+
// in WA.exe dispatches through vtable offset 0x98.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
hook::install_trap!(
|
| 246 |
"DisplayGfx__DestructorImpl",
|
| 247 |
va::DISPLAY_GFX_DESTRUCTOR_IMPL
|
crates/openwa-dll/src/replacements/render/render_queue.rs
CHANGED
|
@@ -1,382 +1,322 @@
|
|
| 1 |
-
//! RenderQueue enqueue hooks
|
| 2 |
-
//! plus the per-frame RenderDrawingQueue dispatcher bridge.
|
| 3 |
|
| 4 |
use openwa_core::address::va;
|
|
|
|
| 5 |
use openwa_core::fixed::Fixed;
|
|
|
|
| 6 |
use openwa_core::render::display::DisplayGfx;
|
| 7 |
use openwa_core::render::queue::*;
|
| 8 |
use openwa_core::render::queue_dispatch::{render_drawing_queue, ClipContext};
|
| 9 |
use openwa_core::render::SpriteOp;
|
|
|
|
| 10 |
|
| 11 |
use crate::hook::{self, usercall_trampoline};
|
| 12 |
|
| 13 |
-
//
|
| 14 |
-
// EnqueueTiledBitmap (0x541D60) — type 0xD, EAX=this, 3 stack, RET 0xC
|
| 15 |
-
//
|
| 16 |
-
// Mis-labelled `RQ_DrawPixel` in earlier reverse-engineering passes. It does
|
| 17 |
-
// NOT enqueue a single-pixel draw — it enqueues a tile-cached bitmap draw
|
| 18 |
-
// dispatched by `RenderDrawingQueue` case 0xD into vtable slot 11.
|
| 19 |
-
// ---------------------------------------------------------------------------
|
| 20 |
|
| 21 |
usercall_trampoline!(fn trampoline_enqueue_tiled_bitmap; impl_fn = enqueue_tiled_bitmap_impl;
|
| 22 |
reg = eax; stack_params = 3; ret_bytes = "0xC");
|
| 23 |
|
| 24 |
unsafe extern "cdecl" fn enqueue_tiled_bitmap_impl(
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
flags: u32,
|
| 29 |
) {
|
| 30 |
-
let
|
| 31 |
-
|
| 32 |
-
if let Some(entry) = q.alloc::<DrawTiledBitmapCmd>() {
|
| 33 |
*entry = DrawTiledBitmapCmd {
|
| 34 |
command_type: command_type::DRAW_TILED_BITMAP,
|
| 35 |
layer: 0x1B_0000,
|
| 36 |
x: Fixed(0xFF00_0000u32 as i32),
|
| 37 |
-
y
|
| 38 |
-
source
|
| 39 |
flags: flags as u8,
|
| 40 |
_pad: [0; 3],
|
| 41 |
};
|
| 42 |
}
|
| 43 |
}
|
| 44 |
|
| 45 |
-
// -
|
| 46 |
-
// DrawLineStrip (0x541DD0) — type 8, EAX=this, EDI=count, 2 stack, RET 0x8
|
| 47 |
-
// Allocation: count * 0xC + 0x1C (variable size)
|
| 48 |
-
// ---------------------------------------------------------------------------
|
| 49 |
|
| 50 |
usercall_trampoline!(fn trampoline_draw_line_strip; impl_fn = draw_line_strip_impl;
|
| 51 |
regs = [eax, edi]; stack_params = 2; ret_bytes = "0x8");
|
| 52 |
|
| 53 |
unsafe extern "cdecl" fn draw_line_strip_impl(
|
| 54 |
-
|
| 55 |
count: u32,
|
| 56 |
-
|
| 57 |
-
|
| 58 |
) {
|
| 59 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 60 |
let total_size = count as usize * 0xC + 0x1C;
|
| 61 |
|
| 62 |
-
if let Some(ptr) =
|
| 63 |
let header = &mut *(ptr as *mut DrawLineStripHeader);
|
| 64 |
*header = DrawLineStripHeader {
|
| 65 |
command_type: command_type::DRAW_LINE_STRIP,
|
| 66 |
layer: 0xE_0000,
|
| 67 |
count,
|
| 68 |
-
color
|
| 69 |
};
|
| 70 |
core::ptr::copy_nonoverlapping(
|
| 71 |
-
|
| 72 |
ptr.add(core::mem::size_of::<DrawLineStripHeader>()),
|
| 73 |
count as usize * 0xC,
|
| 74 |
);
|
| 75 |
}
|
| 76 |
}
|
| 77 |
|
| 78 |
-
// -
|
| 79 |
-
// DrawPolygon (0x541E50) — type 9, ECX=this, ESI=count, 3 stack, RET 0xC
|
| 80 |
-
// Allocation: count * 0xC + 0x20 (variable size)
|
| 81 |
-
// ---------------------------------------------------------------------------
|
| 82 |
|
| 83 |
usercall_trampoline!(fn trampoline_draw_polygon; impl_fn = draw_polygon_impl;
|
| 84 |
regs = [ecx, esi]; stack_params = 3; ret_bytes = "0xC");
|
| 85 |
|
| 86 |
unsafe extern "cdecl" fn draw_polygon_impl(
|
| 87 |
-
|
| 88 |
count: u32,
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
) {
|
| 93 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 94 |
let total_size = count as usize * 0xC + 0x20;
|
| 95 |
|
| 96 |
-
if let Some(ptr) =
|
| 97 |
let header = &mut *(ptr as *mut DrawPolygonHeader);
|
| 98 |
*header = DrawPolygonHeader {
|
| 99 |
command_type: command_type::DRAW_POLYGON,
|
| 100 |
layer: 0xE_0000,
|
| 101 |
count,
|
| 102 |
-
color1
|
| 103 |
-
color2
|
| 104 |
};
|
| 105 |
core::ptr::copy_nonoverlapping(
|
| 106 |
-
|
| 107 |
ptr.add(core::mem::size_of::<DrawPolygonHeader>()),
|
| 108 |
count as usize * 0xC,
|
| 109 |
);
|
| 110 |
}
|
| 111 |
}
|
| 112 |
|
| 113 |
-
//
|
| 114 |
-
// DrawCrosshair (0x541ED0) — type 0xB, ECX=this, 5 stack, RET 0x14
|
| 115 |
-
// Enqueues a crosshair draw command. Dispatched by RenderDrawingQueue
|
| 116 |
-
// case 0xB → DisplayGfx::draw_crosshair (vtable slot 16).
|
| 117 |
-
// ---------------------------------------------------------------------------
|
| 118 |
|
| 119 |
usercall_trampoline!(fn trampoline_draw_crosshair; impl_fn = draw_crosshair_impl;
|
| 120 |
reg = ecx; stack_params = 5; ret_bytes = "0x14");
|
| 121 |
|
| 122 |
unsafe extern "cdecl" fn draw_crosshair_impl(
|
| 123 |
-
|
| 124 |
layer: u32,
|
| 125 |
-
|
| 126 |
-
|
| 127 |
color_fg: u32,
|
| 128 |
color_bg: u32,
|
| 129 |
) {
|
| 130 |
-
let
|
| 131 |
-
|
| 132 |
-
if let Some(entry) = q.alloc::<DrawCrosshairCmd>() {
|
| 133 |
*entry = DrawCrosshairCmd {
|
| 134 |
command_type: command_type::DRAW_CROSSHAIR,
|
| 135 |
layer,
|
| 136 |
color_fg,
|
| 137 |
color_bg,
|
| 138 |
-
x
|
| 139 |
-
y
|
| 140 |
ref_z: 0,
|
| 141 |
};
|
| 142 |
}
|
| 143 |
}
|
| 144 |
|
| 145 |
-
//
|
| 146 |
-
// DrawRect (0x541F40) — type 0, ECX=this, EDX=y_clip, 6 stack, RET 0x18
|
| 147 |
-
// ---------------------------------------------------------------------------
|
| 148 |
|
| 149 |
usercall_trampoline!(fn trampoline_draw_rect; impl_fn = draw_rect_impl;
|
| 150 |
regs = [ecx, edx]; stack_params = 6; ret_bytes = "0x18");
|
| 151 |
|
| 152 |
unsafe extern "cdecl" fn draw_rect_impl(
|
| 153 |
-
|
| 154 |
-
y_clip:
|
| 155 |
layer: u32,
|
| 156 |
-
x1:
|
| 157 |
-
y1:
|
| 158 |
-
x2:
|
| 159 |
-
y2:
|
| 160 |
color: u32,
|
| 161 |
) {
|
| 162 |
-
let
|
| 163 |
-
|
| 164 |
-
if let Some(entry) = q.alloc::<DrawRectCmd>() {
|
| 165 |
*entry = DrawRectCmd {
|
| 166 |
command_type: command_type::DRAW_RECT,
|
| 167 |
layer,
|
| 168 |
color,
|
| 169 |
-
x1:
|
| 170 |
-
y1:
|
| 171 |
-
x2:
|
| 172 |
-
y2:
|
| 173 |
-
ref_z:
|
| 174 |
};
|
| 175 |
}
|
| 176 |
}
|
| 177 |
|
| 178 |
-
//
|
| 179 |
-
// DrawSpriteGlobal (0x541FE0) — type 4, EAX=y, ECX=this, 4 stack, RET 0x10
|
| 180 |
-
// ---------------------------------------------------------------------------
|
| 181 |
|
| 182 |
usercall_trampoline!(fn trampoline_draw_sprite_global; impl_fn = draw_sprite_global_impl;
|
| 183 |
regs = [eax, ecx]; stack_params = 4; ret_bytes = "0x10");
|
| 184 |
|
| 185 |
unsafe extern "cdecl" fn draw_sprite_global_impl(
|
| 186 |
-
y_pos:
|
| 187 |
-
|
| 188 |
layer: u32,
|
| 189 |
-
x_pos:
|
| 190 |
-
|
| 191 |
frame: u32,
|
| 192 |
) {
|
| 193 |
-
let
|
| 194 |
-
|
| 195 |
-
if let Some(entry) = q.alloc::<DrawSpriteCmd>() {
|
| 196 |
*entry = DrawSpriteCmd {
|
| 197 |
command_type: command_type::DRAW_SPRITE_GLOBAL,
|
| 198 |
layer,
|
| 199 |
-
x:
|
| 200 |
-
y:
|
| 201 |
-
sprite
|
| 202 |
palette: frame,
|
| 203 |
};
|
| 204 |
}
|
| 205 |
}
|
| 206 |
|
| 207 |
-
//
|
| 208 |
-
// DrawSpriteLocal (0x542060) — type 5, EAX=y, ECX=this, 4 stack, RET 0x10
|
| 209 |
-
// ---------------------------------------------------------------------------
|
| 210 |
|
| 211 |
usercall_trampoline!(fn trampoline_draw_sprite_local; impl_fn = draw_sprite_local_impl;
|
| 212 |
regs = [eax, ecx]; stack_params = 4; ret_bytes = "0x10");
|
| 213 |
|
| 214 |
unsafe extern "cdecl" fn draw_sprite_local_impl(
|
| 215 |
-
y_pos:
|
| 216 |
-
|
| 217 |
layer: u32,
|
| 218 |
-
x_pos:
|
| 219 |
-
|
| 220 |
frame: u32,
|
| 221 |
) {
|
| 222 |
-
let
|
| 223 |
-
|
| 224 |
-
if let Some(entry) = q.alloc::<DrawSpriteCmd>() {
|
| 225 |
*entry = DrawSpriteCmd {
|
| 226 |
command_type: command_type::DRAW_SPRITE_LOCAL,
|
| 227 |
layer,
|
| 228 |
-
x:
|
| 229 |
-
y:
|
| 230 |
-
sprite
|
| 231 |
palette: frame,
|
| 232 |
};
|
| 233 |
}
|
| 234 |
}
|
| 235 |
|
| 236 |
-
//
|
| 237 |
-
// DrawSpriteOffset (0x5420E0) — type 6, ECX=this, EDX=y_clip, 6 stack, RET 0x18
|
| 238 |
-
// ---------------------------------------------------------------------------
|
| 239 |
|
| 240 |
usercall_trampoline!(fn trampoline_draw_sprite_offset; impl_fn = draw_sprite_offset_impl;
|
| 241 |
regs = [ecx, edx]; stack_params = 6; ret_bytes = "0x18");
|
| 242 |
|
| 243 |
unsafe extern "cdecl" fn draw_sprite_offset_impl(
|
| 244 |
-
|
| 245 |
-
y_clip:
|
| 246 |
layer: u32,
|
| 247 |
-
x_pos:
|
| 248 |
-
y_pos:
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
) {
|
| 253 |
-
let
|
| 254 |
-
|
| 255 |
-
if let Some(entry) = q.alloc::<DrawSpriteOffsetCmd>() {
|
| 256 |
*entry = DrawSpriteOffsetCmd {
|
| 257 |
command_type: command_type::DRAW_SPRITE_OFFSET,
|
| 258 |
layer,
|
| 259 |
-
flags
|
| 260 |
-
x:
|
| 261 |
-
y:
|
| 262 |
ref_z: 0,
|
| 263 |
-
ref_z_2:
|
| 264 |
-
sprite
|
| 265 |
-
palette
|
| 266 |
};
|
| 267 |
}
|
| 268 |
}
|
| 269 |
|
| 270 |
-
//
|
| 271 |
-
// DrawBitmapGlobal (0x542170) — type 1, ECX=this, EDX=y, 7 stack, RET 0x1C
|
| 272 |
-
// ---------------------------------------------------------------------------
|
| 273 |
|
| 274 |
usercall_trampoline!(fn trampoline_draw_bitmap_global; impl_fn = draw_bitmap_global_impl;
|
| 275 |
regs = [ecx, edx]; stack_params = 7; ret_bytes = "0x1C");
|
| 276 |
|
| 277 |
unsafe extern "cdecl" fn draw_bitmap_global_impl(
|
| 278 |
-
|
| 279 |
-
y_pos:
|
| 280 |
layer: u32,
|
| 281 |
-
x_pos:
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
) {
|
| 288 |
-
let
|
| 289 |
-
|
| 290 |
-
if let Some(entry) = q.alloc::<DrawBitmapGlobalCmd>() {
|
| 291 |
*entry = DrawBitmapGlobalCmd {
|
| 292 |
command_type: command_type::DRAW_BITMAP_GLOBAL,
|
| 293 |
layer,
|
| 294 |
-
x:
|
| 295 |
-
y:
|
| 296 |
-
bitmap
|
| 297 |
src_x: 0,
|
| 298 |
-
src_y
|
| 299 |
-
src_w
|
| 300 |
-
src_h
|
| 301 |
-
flags
|
| 302 |
};
|
| 303 |
}
|
| 304 |
}
|
| 305 |
|
| 306 |
-
//
|
| 307 |
-
// DrawTextboxLocal (0x542200) — type 2, ECX=this, EDX=y, 6 stack, RET 0x18
|
| 308 |
-
// ---------------------------------------------------------------------------
|
| 309 |
|
| 310 |
usercall_trampoline!(fn trampoline_draw_textbox_local; impl_fn = draw_textbox_local_impl;
|
| 311 |
regs = [ecx, edx]; stack_params = 6; ret_bytes = "0x18");
|
| 312 |
|
| 313 |
unsafe extern "cdecl" fn draw_textbox_local_impl(
|
| 314 |
-
|
| 315 |
-
y_pos:
|
| 316 |
layer: u32,
|
| 317 |
-
x_pos:
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
) {
|
| 323 |
-
let
|
| 324 |
-
|
| 325 |
-
if let Some(entry) = q.alloc::<DrawTextboxLocalCmd>() {
|
| 326 |
*entry = DrawTextboxLocalCmd {
|
| 327 |
command_type: command_type::DRAW_TEXTBOX_LOCAL,
|
| 328 |
layer,
|
| 329 |
mode: 0,
|
| 330 |
-
x:
|
| 331 |
-
y:
|
| 332 |
ref_z: 0,
|
| 333 |
ref_z_2: 0,
|
| 334 |
-
bitmap
|
| 335 |
src_x: 0,
|
| 336 |
src_y: 0,
|
| 337 |
-
src_w
|
| 338 |
-
src_h
|
| 339 |
-
flags
|
| 340 |
};
|
| 341 |
}
|
| 342 |
}
|
| 343 |
|
| 344 |
-
//
|
| 345 |
-
// DrawBungeeTrail (0x500720) — stdcall(task, style, fill), RET 0xC
|
| 346 |
-
//
|
| 347 |
-
// Draws bungee drop trajectory path. Logic lives in openwa_core::render::bungee_trail.
|
| 348 |
-
// ---------------------------------------------------------------------------
|
| 349 |
|
| 350 |
-
unsafe extern "stdcall" fn draw_bungee_trail_impl(
|
| 351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
}
|
| 353 |
|
| 354 |
-
//
|
| 355 |
-
// DrawCrosshairLine (0x5197D0) — usercall(EDI=task_ptr), plain RET
|
| 356 |
-
//
|
| 357 |
-
// Draws the weapon aiming crosshair line:
|
| 358 |
-
// 1. Compute direction from angle at task+0x264
|
| 359 |
-
// 2. Compute line length from DDGame scale + task offset
|
| 360 |
-
// 3. Endpoint = start + direction * length (with overflow clamping)
|
| 361 |
-
// 4. DrawPolygon (2 vertices) for the line
|
| 362 |
-
// 5. Conditionally DrawSpriteLocal at endpoint (crosshair sprite)
|
| 363 |
-
// ---------------------------------------------------------------------------
|
| 364 |
|
| 365 |
usercall_trampoline!(fn trampoline_draw_crosshair_line; impl_fn = draw_crosshair_line_impl;
|
| 366 |
reg = edi);
|
| 367 |
|
| 368 |
-
unsafe extern "cdecl" fn draw_crosshair_line_impl(
|
| 369 |
-
openwa_core::render::crosshair_line::draw_crosshair_line(
|
| 370 |
}
|
| 371 |
|
| 372 |
-
//
|
| 373 |
-
// RenderDrawingQueue (0x542350) — usercall(EAX=RenderQueue*) + 2 stack
|
| 374 |
-
// (DisplayGfx*, ClipContext*), RET 0x8.
|
| 375 |
-
//
|
| 376 |
-
// The per-frame render-queue dispatcher. Pure Rust port lives in
|
| 377 |
-
// `openwa_core::render::queue_dispatch::render_drawing_queue`. The
|
| 378 |
-
// trampoline captures EAX and forwards the two stack args.
|
| 379 |
-
// ---------------------------------------------------------------------------
|
| 380 |
|
| 381 |
usercall_trampoline!(fn trampoline_render_drawing_queue;
|
| 382 |
impl_fn = render_drawing_queue_impl;
|
|
@@ -390,10 +330,6 @@ unsafe extern "cdecl" fn render_drawing_queue_impl(
|
|
| 390 |
render_drawing_queue(rq, display, clip);
|
| 391 |
}
|
| 392 |
|
| 393 |
-
// ---------------------------------------------------------------------------
|
| 394 |
-
// Installation
|
| 395 |
-
// ---------------------------------------------------------------------------
|
| 396 |
-
|
| 397 |
pub fn install() -> Result<(), String> {
|
| 398 |
unsafe {
|
| 399 |
let _ = hook::install(
|
|
|
|
| 1 |
+
//! RenderQueue enqueue hooks and the per-frame dispatcher bridge.
|
|
|
|
| 2 |
|
| 3 |
use openwa_core::address::va;
|
| 4 |
+
use openwa_core::bitgrid::DisplayBitGrid;
|
| 5 |
use openwa_core::fixed::Fixed;
|
| 6 |
+
use openwa_core::render::display::vtable::TiledBitmapSource;
|
| 7 |
use openwa_core::render::display::DisplayGfx;
|
| 8 |
use openwa_core::render::queue::*;
|
| 9 |
use openwa_core::render::queue_dispatch::{render_drawing_queue, ClipContext};
|
| 10 |
use openwa_core::render::SpriteOp;
|
| 11 |
+
use openwa_core::task::{BungeeTrailTask, WeaponAimTask};
|
| 12 |
|
| 13 |
use crate::hook::{self, usercall_trampoline};
|
| 14 |
|
| 15 |
+
// EnqueueTiledBitmap (0x541D60)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
usercall_trampoline!(fn trampoline_enqueue_tiled_bitmap; impl_fn = enqueue_tiled_bitmap_impl;
|
| 18 |
reg = eax; stack_params = 3; ret_bytes = "0xC");
|
| 19 |
|
| 20 |
unsafe extern "cdecl" fn enqueue_tiled_bitmap_impl(
|
| 21 |
+
queue: *mut RenderQueue,
|
| 22 |
+
y: Fixed,
|
| 23 |
+
source: *const TiledBitmapSource,
|
| 24 |
flags: u32,
|
| 25 |
) {
|
| 26 |
+
if let Some(entry) = (*queue).alloc::<DrawTiledBitmapCmd>() {
|
|
|
|
|
|
|
| 27 |
*entry = DrawTiledBitmapCmd {
|
| 28 |
command_type: command_type::DRAW_TILED_BITMAP,
|
| 29 |
layer: 0x1B_0000,
|
| 30 |
x: Fixed(0xFF00_0000u32 as i32),
|
| 31 |
+
y,
|
| 32 |
+
source,
|
| 33 |
flags: flags as u8,
|
| 34 |
_pad: [0; 3],
|
| 35 |
};
|
| 36 |
}
|
| 37 |
}
|
| 38 |
|
| 39 |
+
// DrawLineStrip (0x541DD0) — variable-size allocation: count * 0xC + 0x1C
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
usercall_trampoline!(fn trampoline_draw_line_strip; impl_fn = draw_line_strip_impl;
|
| 42 |
regs = [eax, edi]; stack_params = 2; ret_bytes = "0x8");
|
| 43 |
|
| 44 |
unsafe extern "cdecl" fn draw_line_strip_impl(
|
| 45 |
+
queue: *mut RenderQueue,
|
| 46 |
count: u32,
|
| 47 |
+
vertices: *const u8,
|
| 48 |
+
color: u32,
|
| 49 |
) {
|
|
|
|
| 50 |
let total_size = count as usize * 0xC + 0x1C;
|
| 51 |
|
| 52 |
+
if let Some(ptr) = (*queue).alloc_raw(total_size) {
|
| 53 |
let header = &mut *(ptr as *mut DrawLineStripHeader);
|
| 54 |
*header = DrawLineStripHeader {
|
| 55 |
command_type: command_type::DRAW_LINE_STRIP,
|
| 56 |
layer: 0xE_0000,
|
| 57 |
count,
|
| 58 |
+
color,
|
| 59 |
};
|
| 60 |
core::ptr::copy_nonoverlapping(
|
| 61 |
+
vertices,
|
| 62 |
ptr.add(core::mem::size_of::<DrawLineStripHeader>()),
|
| 63 |
count as usize * 0xC,
|
| 64 |
);
|
| 65 |
}
|
| 66 |
}
|
| 67 |
|
| 68 |
+
// DrawPolygon (0x541E50) — variable-size allocation: count * 0xC + 0x20
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
usercall_trampoline!(fn trampoline_draw_polygon; impl_fn = draw_polygon_impl;
|
| 71 |
regs = [ecx, esi]; stack_params = 3; ret_bytes = "0xC");
|
| 72 |
|
| 73 |
unsafe extern "cdecl" fn draw_polygon_impl(
|
| 74 |
+
queue: *mut RenderQueue,
|
| 75 |
count: u32,
|
| 76 |
+
vertices: *const u8,
|
| 77 |
+
color1: u32,
|
| 78 |
+
color2: u32,
|
| 79 |
) {
|
|
|
|
| 80 |
let total_size = count as usize * 0xC + 0x20;
|
| 81 |
|
| 82 |
+
if let Some(ptr) = (*queue).alloc_raw(total_size) {
|
| 83 |
let header = &mut *(ptr as *mut DrawPolygonHeader);
|
| 84 |
*header = DrawPolygonHeader {
|
| 85 |
command_type: command_type::DRAW_POLYGON,
|
| 86 |
layer: 0xE_0000,
|
| 87 |
count,
|
| 88 |
+
color1,
|
| 89 |
+
color2,
|
| 90 |
};
|
| 91 |
core::ptr::copy_nonoverlapping(
|
| 92 |
+
vertices,
|
| 93 |
ptr.add(core::mem::size_of::<DrawPolygonHeader>()),
|
| 94 |
count as usize * 0xC,
|
| 95 |
);
|
| 96 |
}
|
| 97 |
}
|
| 98 |
|
| 99 |
+
// DrawCrosshair (0x541ED0)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
usercall_trampoline!(fn trampoline_draw_crosshair; impl_fn = draw_crosshair_impl;
|
| 102 |
reg = ecx; stack_params = 5; ret_bytes = "0x14");
|
| 103 |
|
| 104 |
unsafe extern "cdecl" fn draw_crosshair_impl(
|
| 105 |
+
queue: *mut RenderQueue,
|
| 106 |
layer: u32,
|
| 107 |
+
x: Fixed,
|
| 108 |
+
y: Fixed,
|
| 109 |
color_fg: u32,
|
| 110 |
color_bg: u32,
|
| 111 |
) {
|
| 112 |
+
if let Some(entry) = (*queue).alloc::<DrawCrosshairCmd>() {
|
|
|
|
|
|
|
| 113 |
*entry = DrawCrosshairCmd {
|
| 114 |
command_type: command_type::DRAW_CROSSHAIR,
|
| 115 |
layer,
|
| 116 |
color_fg,
|
| 117 |
color_bg,
|
| 118 |
+
x,
|
| 119 |
+
y,
|
| 120 |
ref_z: 0,
|
| 121 |
};
|
| 122 |
}
|
| 123 |
}
|
| 124 |
|
| 125 |
+
// DrawRect (0x541F40)
|
|
|
|
|
|
|
| 126 |
|
| 127 |
usercall_trampoline!(fn trampoline_draw_rect; impl_fn = draw_rect_impl;
|
| 128 |
regs = [ecx, edx]; stack_params = 6; ret_bytes = "0x18");
|
| 129 |
|
| 130 |
unsafe extern "cdecl" fn draw_rect_impl(
|
| 131 |
+
queue: *mut RenderQueue,
|
| 132 |
+
y_clip: Fixed,
|
| 133 |
layer: u32,
|
| 134 |
+
x1: Fixed,
|
| 135 |
+
y1: Fixed,
|
| 136 |
+
x2: Fixed,
|
| 137 |
+
y2: Fixed,
|
| 138 |
color: u32,
|
| 139 |
) {
|
| 140 |
+
if let Some(entry) = (*queue).alloc::<DrawRectCmd>() {
|
|
|
|
|
|
|
| 141 |
*entry = DrawRectCmd {
|
| 142 |
command_type: command_type::DRAW_RECT,
|
| 143 |
layer,
|
| 144 |
color,
|
| 145 |
+
x1: x1.floor(),
|
| 146 |
+
y1: y1.floor(),
|
| 147 |
+
x2: x2.floor(),
|
| 148 |
+
y2: y2.floor(),
|
| 149 |
+
ref_z: y_clip.floor().0,
|
| 150 |
};
|
| 151 |
}
|
| 152 |
}
|
| 153 |
|
| 154 |
+
// DrawSpriteGlobal (0x541FE0)
|
|
|
|
|
|
|
| 155 |
|
| 156 |
usercall_trampoline!(fn trampoline_draw_sprite_global; impl_fn = draw_sprite_global_impl;
|
| 157 |
regs = [eax, ecx]; stack_params = 4; ret_bytes = "0x10");
|
| 158 |
|
| 159 |
unsafe extern "cdecl" fn draw_sprite_global_impl(
|
| 160 |
+
y_pos: Fixed,
|
| 161 |
+
queue: *mut RenderQueue,
|
| 162 |
layer: u32,
|
| 163 |
+
x_pos: Fixed,
|
| 164 |
+
sprite: SpriteOp,
|
| 165 |
frame: u32,
|
| 166 |
) {
|
| 167 |
+
if let Some(entry) = (*queue).alloc::<DrawSpriteCmd>() {
|
|
|
|
|
|
|
| 168 |
*entry = DrawSpriteCmd {
|
| 169 |
command_type: command_type::DRAW_SPRITE_GLOBAL,
|
| 170 |
layer,
|
| 171 |
+
x: x_pos.floor(),
|
| 172 |
+
y: y_pos.floor(),
|
| 173 |
+
sprite,
|
| 174 |
palette: frame,
|
| 175 |
};
|
| 176 |
}
|
| 177 |
}
|
| 178 |
|
| 179 |
+
// DrawSpriteLocal (0x542060)
|
|
|
|
|
|
|
| 180 |
|
| 181 |
usercall_trampoline!(fn trampoline_draw_sprite_local; impl_fn = draw_sprite_local_impl;
|
| 182 |
regs = [eax, ecx]; stack_params = 4; ret_bytes = "0x10");
|
| 183 |
|
| 184 |
unsafe extern "cdecl" fn draw_sprite_local_impl(
|
| 185 |
+
y_pos: Fixed,
|
| 186 |
+
queue: *mut RenderQueue,
|
| 187 |
layer: u32,
|
| 188 |
+
x_pos: Fixed,
|
| 189 |
+
sprite: SpriteOp,
|
| 190 |
frame: u32,
|
| 191 |
) {
|
| 192 |
+
if let Some(entry) = (*queue).alloc::<DrawSpriteCmd>() {
|
|
|
|
|
|
|
| 193 |
*entry = DrawSpriteCmd {
|
| 194 |
command_type: command_type::DRAW_SPRITE_LOCAL,
|
| 195 |
layer,
|
| 196 |
+
x: x_pos.floor(),
|
| 197 |
+
y: y_pos.floor(),
|
| 198 |
+
sprite,
|
| 199 |
palette: frame,
|
| 200 |
};
|
| 201 |
}
|
| 202 |
}
|
| 203 |
|
| 204 |
+
// DrawSpriteOffset (0x5420E0)
|
|
|
|
|
|
|
| 205 |
|
| 206 |
usercall_trampoline!(fn trampoline_draw_sprite_offset; impl_fn = draw_sprite_offset_impl;
|
| 207 |
regs = [ecx, edx]; stack_params = 6; ret_bytes = "0x18");
|
| 208 |
|
| 209 |
unsafe extern "cdecl" fn draw_sprite_offset_impl(
|
| 210 |
+
queue: *mut RenderQueue,
|
| 211 |
+
y_clip: Fixed,
|
| 212 |
layer: u32,
|
| 213 |
+
x_pos: Fixed,
|
| 214 |
+
y_pos: Fixed,
|
| 215 |
+
flags: u32,
|
| 216 |
+
sprite: SpriteOp,
|
| 217 |
+
palette: u32,
|
| 218 |
) {
|
| 219 |
+
if let Some(entry) = (*queue).alloc::<DrawSpriteOffsetCmd>() {
|
|
|
|
|
|
|
| 220 |
*entry = DrawSpriteOffsetCmd {
|
| 221 |
command_type: command_type::DRAW_SPRITE_OFFSET,
|
| 222 |
layer,
|
| 223 |
+
flags,
|
| 224 |
+
x: x_pos.floor(),
|
| 225 |
+
y: y_pos.floor(),
|
| 226 |
ref_z: 0,
|
| 227 |
+
ref_z_2: y_clip.floor().0,
|
| 228 |
+
sprite,
|
| 229 |
+
palette,
|
| 230 |
};
|
| 231 |
}
|
| 232 |
}
|
| 233 |
|
| 234 |
+
// DrawBitmapGlobal (0x542170)
|
|
|
|
|
|
|
| 235 |
|
| 236 |
usercall_trampoline!(fn trampoline_draw_bitmap_global; impl_fn = draw_bitmap_global_impl;
|
| 237 |
regs = [ecx, edx]; stack_params = 7; ret_bytes = "0x1C");
|
| 238 |
|
| 239 |
unsafe extern "cdecl" fn draw_bitmap_global_impl(
|
| 240 |
+
queue: *mut RenderQueue,
|
| 241 |
+
y_pos: Fixed,
|
| 242 |
layer: u32,
|
| 243 |
+
x_pos: Fixed,
|
| 244 |
+
bitmap: *mut DisplayBitGrid,
|
| 245 |
+
src_y: i32,
|
| 246 |
+
src_w: i32,
|
| 247 |
+
src_h: i32,
|
| 248 |
+
flags: u32,
|
| 249 |
) {
|
| 250 |
+
if let Some(entry) = (*queue).alloc::<DrawBitmapGlobalCmd>() {
|
|
|
|
|
|
|
| 251 |
*entry = DrawBitmapGlobalCmd {
|
| 252 |
command_type: command_type::DRAW_BITMAP_GLOBAL,
|
| 253 |
layer,
|
| 254 |
+
x: x_pos.floor(),
|
| 255 |
+
y: y_pos.floor(),
|
| 256 |
+
bitmap,
|
| 257 |
src_x: 0,
|
| 258 |
+
src_y,
|
| 259 |
+
src_w,
|
| 260 |
+
src_h,
|
| 261 |
+
flags,
|
| 262 |
};
|
| 263 |
}
|
| 264 |
}
|
| 265 |
|
| 266 |
+
// DrawTextboxLocal (0x542200)
|
|
|
|
|
|
|
| 267 |
|
| 268 |
usercall_trampoline!(fn trampoline_draw_textbox_local; impl_fn = draw_textbox_local_impl;
|
| 269 |
regs = [ecx, edx]; stack_params = 6; ret_bytes = "0x18");
|
| 270 |
|
| 271 |
unsafe extern "cdecl" fn draw_textbox_local_impl(
|
| 272 |
+
q: *mut RenderQueue,
|
| 273 |
+
y_pos: Fixed,
|
| 274 |
layer: u32,
|
| 275 |
+
x_pos: Fixed,
|
| 276 |
+
bitmap: *mut DisplayBitGrid,
|
| 277 |
+
src_w: i32,
|
| 278 |
+
src_h: i32,
|
| 279 |
+
flags: u32,
|
| 280 |
) {
|
| 281 |
+
if let Some(entry) = (*q).alloc::<DrawTextboxLocalCmd>() {
|
|
|
|
|
|
|
| 282 |
*entry = DrawTextboxLocalCmd {
|
| 283 |
command_type: command_type::DRAW_TEXTBOX_LOCAL,
|
| 284 |
layer,
|
| 285 |
mode: 0,
|
| 286 |
+
x: x_pos.floor(),
|
| 287 |
+
y: y_pos.floor(),
|
| 288 |
ref_z: 0,
|
| 289 |
ref_z_2: 0,
|
| 290 |
+
bitmap,
|
| 291 |
src_x: 0,
|
| 292 |
src_y: 0,
|
| 293 |
+
src_w,
|
| 294 |
+
src_h,
|
| 295 |
+
flags,
|
| 296 |
};
|
| 297 |
}
|
| 298 |
}
|
| 299 |
|
| 300 |
+
// DrawBungeeTrail (0x500720)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
+
unsafe extern "stdcall" fn draw_bungee_trail_impl(
|
| 303 |
+
task: *const BungeeTrailTask,
|
| 304 |
+
style: u32,
|
| 305 |
+
fill: u32,
|
| 306 |
+
) {
|
| 307 |
+
openwa_core::render::bungee_trail::draw_bungee_trail(task, style, fill);
|
| 308 |
}
|
| 309 |
|
| 310 |
+
// DrawCrosshairLine (0x5197D0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
usercall_trampoline!(fn trampoline_draw_crosshair_line; impl_fn = draw_crosshair_line_impl;
|
| 313 |
reg = edi);
|
| 314 |
|
| 315 |
+
unsafe extern "cdecl" fn draw_crosshair_line_impl(task: *const WeaponAimTask) {
|
| 316 |
+
openwa_core::render::crosshair_line::draw_crosshair_line(task);
|
| 317 |
}
|
| 318 |
|
| 319 |
+
// RenderDrawingQueue (0x542350)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
|
| 321 |
usercall_trampoline!(fn trampoline_render_drawing_queue;
|
| 322 |
impl_fn = render_drawing_queue_impl;
|
|
|
|
| 330 |
render_drawing_queue(rq, display, clip);
|
| 331 |
}
|
| 332 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
pub fn install() -> Result<(), String> {
|
| 334 |
unsafe {
|
| 335 |
let _ = hook::install(
|