refactor: split render.rs, move game logic to core, type command structs
Browse filesMajor restructuring of the render subsystem:
Module split — DLL render.rs (1463 lines) split into:
- render/render_queue.rs: enqueue hook trampolines + install
- render/display_vtable.rs: vtable patch stubs + install
- render/mod.rs: thin re-export
Game logic moved to openwa-core (~1140 lines):
- bitgrid/blit.rs: blit routing (8bpp, 1-bit, generic fallback)
- render/display/blit_sprite.rs: BlitSprite + DrawScaledSprite
- render/bungee_trail.rs: bungee trail rendering
- render/crosshair_line.rs: crosshair line rendering
- render/display/destructor.rs: headless_destructor
- trig.rs: interpolated trig table lookup (sin_lookup, cos_lookup)
Render command struct type improvements:
- Position fields: u32 → Fixed (repr-transparent, layout-compatible)
- Sprite fields: u32 → SpriteOp in DrawSpriteOffsetCmd
- Pointer fields: u32 → *mut DisplayBitGrid / *const TiledBitmapSource
- Named all param_N/_reserved fields from dispatcher semantics
(ref_z, src_x/y/w/h, color, mode, flags, etc.)
Also deduplicates fixed_mul in weapon_fire.rs via Fixed::mul_raw.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/bitgrid/blit.rs +385 -0
- crates/openwa-core/src/{bitgrid.rs → bitgrid/mod.rs} +2 -0
- crates/openwa-core/src/game/weapon_fire.rs +1 -1
- crates/openwa-core/src/lib.rs +1 -0
- crates/openwa-core/src/render/bungee_trail.rs +129 -0
- crates/openwa-core/src/render/crosshair_line.rs +120 -0
- crates/openwa-core/src/render/display/blit_sprite.rs +455 -0
- crates/openwa-core/src/render/display/destructor.rs +36 -0
- crates/openwa-core/src/render/display/mod.rs +1 -0
- crates/openwa-core/src/render/mod.rs +2 -0
- crates/openwa-core/src/render/queue.rs +50 -46
- crates/openwa-core/src/trig.rs +51 -0
- crates/openwa-dll/src/replacements/bitgrid.rs +11 -364
- crates/openwa-dll/src/replacements/render.rs +0 -1463
- crates/openwa-dll/src/replacements/render/display_vtable.rs +335 -0
- crates/openwa-dll/src/replacements/render/mod.rs +14 -0
- crates/openwa-dll/src/replacements/render/render_queue.rs +478 -0
|
@@ -0,0 +1,385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! BitGrid blit operations — sprite blitting on raw BitGrid pointers.
|
| 2 |
+
//!
|
| 3 |
+
//! These functions bridge raw `*mut DisplayBitGrid` pointers to the safe
|
| 4 |
+
//! `PixelGrid`/`BlitSource` abstractions in `render::display::sprite_blit`.
|
| 5 |
+
|
| 6 |
+
use crate::bitgrid::DisplayBitGrid;
|
| 7 |
+
use crate::render::display::line_draw::PixelGridMut;
|
| 8 |
+
use crate::render::display::sprite_blit::{
|
| 9 |
+
blit_sprite_rect, blit_stippled, blit_tiled, BlitBlend, BlitOrientation, BlitSource,
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
/// Rust implementation of the core sprite blit (BitGrid__BlitSpriteRect, 0x4F6910).
|
| 13 |
+
///
|
| 14 |
+
/// Handles all blend modes for both 8bpp and 1-bit surfaces:
|
| 15 |
+
/// - 8bpp modes 0-3: via `blit_sprite_rect` (orientation-aware, PixelGrid wrapper)
|
| 16 |
+
/// - 1-bit byte-aligned fast path: memcpy/OR/nop for modes 0-3 with Normal orientation
|
| 17 |
+
/// - Generic per-pixel fallback: handles all remaining cases (unaligned 1-bit, modes 4-5,
|
| 18 |
+
/// mixed cpp, etc.) via `blit_generic_perpixel`
|
| 19 |
+
pub unsafe fn blit_impl(
|
| 20 |
+
dst: *mut DisplayBitGrid,
|
| 21 |
+
dst_x: i32,
|
| 22 |
+
dst_y: i32,
|
| 23 |
+
width: i32,
|
| 24 |
+
height: i32,
|
| 25 |
+
src: *mut DisplayBitGrid,
|
| 26 |
+
src_x: i32,
|
| 27 |
+
src_y: i32,
|
| 28 |
+
color_table: *const u8,
|
| 29 |
+
flags: u32,
|
| 30 |
+
) -> u32 {
|
| 31 |
+
if width == 0 || height == 0 {
|
| 32 |
+
return 0;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
let blend_mode = flags & 0xFFFF;
|
| 36 |
+
let src_cpp = (*src).cells_per_unit;
|
| 37 |
+
let dst_cpp = (*dst).cells_per_unit;
|
| 38 |
+
|
| 39 |
+
// Fast path: 8bpp surfaces with blend modes 0-3
|
| 40 |
+
if dst_cpp == 8 && src_cpp == 8 && blend_mode <= 3 {
|
| 41 |
+
return blit_8bpp(
|
| 42 |
+
dst,
|
| 43 |
+
dst_x,
|
| 44 |
+
dst_y,
|
| 45 |
+
width,
|
| 46 |
+
height,
|
| 47 |
+
src,
|
| 48 |
+
src_x,
|
| 49 |
+
src_y,
|
| 50 |
+
color_table,
|
| 51 |
+
flags,
|
| 52 |
+
);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// --- Clipping (shared by 1-bit fast path and generic fallback) ---
|
| 56 |
+
// This mirrors the clipping logic at the top of BitGrid__BlitSpriteRect.
|
| 57 |
+
let dst_right = dst_x + width;
|
| 58 |
+
let dst_bottom = dst_y + height;
|
| 59 |
+
|
| 60 |
+
let clip_left = (*dst).clip_left as i32;
|
| 61 |
+
let clip_top = (*dst).clip_top as i32;
|
| 62 |
+
let clip_right = (*dst).clip_right as i32;
|
| 63 |
+
let clip_bottom = (*dst).clip_bottom as i32;
|
| 64 |
+
|
| 65 |
+
// Early-out: completely outside clip rect
|
| 66 |
+
if dst_x >= clip_right
|
| 67 |
+
|| dst_right <= clip_left
|
| 68 |
+
|| dst_y >= clip_bottom
|
| 69 |
+
|| dst_bottom <= clip_top
|
| 70 |
+
{
|
| 71 |
+
return 0;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
// Clamp visible region to clip rect
|
| 75 |
+
let vis_left = dst_x.max(clip_left);
|
| 76 |
+
let vis_right = dst_right.min(clip_right);
|
| 77 |
+
let vis_top = dst_y.max(clip_top);
|
| 78 |
+
let vis_bottom = dst_bottom.min(clip_bottom);
|
| 79 |
+
|
| 80 |
+
if vis_left >= vis_right || vis_top >= vis_bottom {
|
| 81 |
+
return 0;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// Adjust source coordinates for clipping and orientation.
|
| 85 |
+
// The orientation switch in the original adjusts src_x/src_y before the cpp check.
|
| 86 |
+
let orientation_code = (flags >> 16) & 0xFFFF;
|
| 87 |
+
let (adj_src_x, adj_src_y) = adjust_src_for_orientation(
|
| 88 |
+
src_x,
|
| 89 |
+
src_y,
|
| 90 |
+
dst_x,
|
| 91 |
+
dst_y,
|
| 92 |
+
width,
|
| 93 |
+
height,
|
| 94 |
+
vis_left,
|
| 95 |
+
vis_top,
|
| 96 |
+
vis_right,
|
| 97 |
+
vis_bottom,
|
| 98 |
+
orientation_code,
|
| 99 |
+
);
|
| 100 |
+
|
| 101 |
+
// Build slices from raw BitGrid data pointers
|
| 102 |
+
let dst_data =
|
| 103 |
+
core::slice::from_raw_parts_mut((*dst).data, ((*dst).row_stride * (*dst).height) as usize);
|
| 104 |
+
let src_data =
|
| 105 |
+
core::slice::from_raw_parts((*src).data, ((*src).row_stride * (*src).height) as usize);
|
| 106 |
+
|
| 107 |
+
// 1-bit byte-aligned fast path
|
| 108 |
+
if dst_cpp == 1 && src_cpp == 1 {
|
| 109 |
+
let left_aligned = (vis_left & 7) == 0;
|
| 110 |
+
let right_aligned = (vis_right & 7) == 0;
|
| 111 |
+
let src_aligned = (adj_src_x & 7) == 0;
|
| 112 |
+
|
| 113 |
+
if left_aligned && right_aligned && src_aligned && color_table.is_null() && flags < 4 {
|
| 114 |
+
crate::render::display::sprite_blit::blit_1bit_aligned(
|
| 115 |
+
dst_data,
|
| 116 |
+
(*dst).row_stride,
|
| 117 |
+
src_data,
|
| 118 |
+
(*src).row_stride,
|
| 119 |
+
vis_left,
|
| 120 |
+
vis_top,
|
| 121 |
+
vis_right,
|
| 122 |
+
vis_bottom,
|
| 123 |
+
adj_src_x,
|
| 124 |
+
adj_src_y,
|
| 125 |
+
blend_mode,
|
| 126 |
+
);
|
| 127 |
+
return 1;
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// Convert color_table pointer to typed reference for the generic path
|
| 132 |
+
let color_table_ref: Option<&[u8; 256]> = if !color_table.is_null() {
|
| 133 |
+
Some(&*(color_table as *const [u8; 256]))
|
| 134 |
+
} else {
|
| 135 |
+
None
|
| 136 |
+
};
|
| 137 |
+
|
| 138 |
+
// Generic per-pixel fallback — handles all remaining cases
|
| 139 |
+
crate::render::display::sprite_blit::blit_generic_perpixel(
|
| 140 |
+
dst_data,
|
| 141 |
+
(*dst).row_stride,
|
| 142 |
+
dst_cpp,
|
| 143 |
+
src_data,
|
| 144 |
+
(*src).row_stride,
|
| 145 |
+
src_cpp,
|
| 146 |
+
vis_left,
|
| 147 |
+
vis_top,
|
| 148 |
+
vis_right,
|
| 149 |
+
vis_bottom,
|
| 150 |
+
adj_src_x,
|
| 151 |
+
adj_src_y,
|
| 152 |
+
color_table_ref,
|
| 153 |
+
blend_mode,
|
| 154 |
+
)
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
/// Adjust source coordinates for clipping and orientation.
|
| 158 |
+
///
|
| 159 |
+
/// Port of the orientation switch in BitGrid__BlitSpriteRect (0x4F69D3..0x4F6A9D).
|
| 160 |
+
/// Maps the 16 orientation codes (0-15) to 8 unique source adjustments.
|
| 161 |
+
pub fn adjust_src_for_orientation(
|
| 162 |
+
src_x: i32,
|
| 163 |
+
src_y: i32,
|
| 164 |
+
dst_x: i32,
|
| 165 |
+
dst_y: i32,
|
| 166 |
+
width: i32,
|
| 167 |
+
height: i32,
|
| 168 |
+
vis_left: i32,
|
| 169 |
+
vis_top: i32,
|
| 170 |
+
vis_right: i32,
|
| 171 |
+
vis_bottom: i32,
|
| 172 |
+
orientation: u32,
|
| 173 |
+
) -> (i32, i32) {
|
| 174 |
+
match orientation {
|
| 175 |
+
1 | 10 => {
|
| 176 |
+
// MirrorX: src_x from right side, src_y from top
|
| 177 |
+
let sx = src_x + (dst_x - vis_right) + width;
|
| 178 |
+
let sy = src_y + (vis_top - dst_y);
|
| 179 |
+
(sx, sy)
|
| 180 |
+
}
|
| 181 |
+
2 | 9 => {
|
| 182 |
+
// MirrorY: src_x from left, src_y from bottom
|
| 183 |
+
let sx = src_x + (vis_left - dst_x);
|
| 184 |
+
let sy = src_y + (dst_y - vis_bottom) + height;
|
| 185 |
+
(sx, sy)
|
| 186 |
+
}
|
| 187 |
+
3 | 8 => {
|
| 188 |
+
// MirrorXY: src_x from right, src_y from bottom
|
| 189 |
+
let sx = src_x + (dst_x - vis_right) + width;
|
| 190 |
+
let sy = src_y + (dst_y - vis_bottom) + height;
|
| 191 |
+
(sx, sy)
|
| 192 |
+
}
|
| 193 |
+
4 | 15 => {
|
| 194 |
+
// Rotate90: src_x tracks dst_y from bottom, src_y tracks dst_x from left
|
| 195 |
+
let sx = src_x + (dst_y - vis_bottom) + height;
|
| 196 |
+
let sy = src_y + (vis_left - dst_x);
|
| 197 |
+
(sx, sy)
|
| 198 |
+
}
|
| 199 |
+
5 | 14 => {
|
| 200 |
+
// Rotate90+MirrorX
|
| 201 |
+
let sx = src_x + (dst_y - vis_bottom) + height;
|
| 202 |
+
let sy = src_y + (dst_x - vis_right) + width;
|
| 203 |
+
(sx, sy)
|
| 204 |
+
}
|
| 205 |
+
6 | 13 => {
|
| 206 |
+
// Rotate90+MirrorY
|
| 207 |
+
let sx = src_x + (vis_top - dst_y);
|
| 208 |
+
let sy = src_y + (vis_left - dst_x);
|
| 209 |
+
(sx, sy)
|
| 210 |
+
}
|
| 211 |
+
7 | 12 => {
|
| 212 |
+
// Rotate90+MirrorXY
|
| 213 |
+
let sx = src_x + (vis_top - dst_y);
|
| 214 |
+
let sy = src_y + (dst_x - vis_right) + width;
|
| 215 |
+
(sx, sy)
|
| 216 |
+
}
|
| 217 |
+
_ => {
|
| 218 |
+
// Normal (0, 11, or any default)
|
| 219 |
+
let sx = src_x + (vis_left - dst_x);
|
| 220 |
+
let sy = src_y + (vis_top - dst_y);
|
| 221 |
+
(sx, sy)
|
| 222 |
+
}
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
/// 8bpp blit path — extracted from the original blit_impl for clarity.
|
| 227 |
+
///
|
| 228 |
+
/// Handles blend modes 0-3 on 8bpp surfaces via PixelGrid wrapper.
|
| 229 |
+
pub unsafe fn blit_8bpp(
|
| 230 |
+
dst: *mut DisplayBitGrid,
|
| 231 |
+
dst_x: i32,
|
| 232 |
+
dst_y: i32,
|
| 233 |
+
width: i32,
|
| 234 |
+
height: i32,
|
| 235 |
+
src: *mut DisplayBitGrid,
|
| 236 |
+
src_x: i32,
|
| 237 |
+
src_y: i32,
|
| 238 |
+
color_table: *const u8,
|
| 239 |
+
flags: u32,
|
| 240 |
+
) -> u32 {
|
| 241 |
+
let color_table_ref: Option<&[u8; 256]> = if !color_table.is_null() {
|
| 242 |
+
Some(&*(color_table as *const [u8; 256]))
|
| 243 |
+
} else {
|
| 244 |
+
None
|
| 245 |
+
};
|
| 246 |
+
|
| 247 |
+
let orientation = BlitOrientation::from_flags(flags);
|
| 248 |
+
let blend = BlitBlend::from_flags(flags);
|
| 249 |
+
|
| 250 |
+
let src_data =
|
| 251 |
+
core::slice::from_raw_parts((*src).data, ((*src).row_stride * (*src).height) as usize);
|
| 252 |
+
let blit_src = BlitSource {
|
| 253 |
+
data: src_data,
|
| 254 |
+
width: (*src).width,
|
| 255 |
+
height: (*src).height,
|
| 256 |
+
row_stride: (*src).row_stride,
|
| 257 |
+
};
|
| 258 |
+
|
| 259 |
+
let dst_data_len = ((*dst).row_stride * (*dst).height) as usize;
|
| 260 |
+
let dst_grid = PixelGridMut {
|
| 261 |
+
data: core::slice::from_raw_parts_mut((*dst).data, dst_data_len),
|
| 262 |
+
width: (*dst).width,
|
| 263 |
+
height: (*dst).height,
|
| 264 |
+
row_stride: (*dst).row_stride,
|
| 265 |
+
clip_left: (*dst).clip_left,
|
| 266 |
+
clip_top: (*dst).clip_top,
|
| 267 |
+
clip_right: (*dst).clip_right,
|
| 268 |
+
clip_bottom: (*dst).clip_bottom,
|
| 269 |
+
};
|
| 270 |
+
|
| 271 |
+
blit_sprite_rect(
|
| 272 |
+
dst_grid,
|
| 273 |
+
&blit_src,
|
| 274 |
+
dst_x,
|
| 275 |
+
dst_y,
|
| 276 |
+
width,
|
| 277 |
+
height,
|
| 278 |
+
src_x,
|
| 279 |
+
src_y,
|
| 280 |
+
color_table_ref,
|
| 281 |
+
orientation,
|
| 282 |
+
blend,
|
| 283 |
+
) as u32
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
/// Stippled blit on raw BitGrid pointers.
|
| 287 |
+
///
|
| 288 |
+
/// Wraps the raw pointers into PixelGrid/BlitSource and calls
|
| 289 |
+
/// `sprite_blit::blit_stippled`.
|
| 290 |
+
pub unsafe fn blit_stippled_raw(
|
| 291 |
+
dst: *mut DisplayBitGrid,
|
| 292 |
+
src: *mut DisplayBitGrid,
|
| 293 |
+
dst_x: i32,
|
| 294 |
+
dst_y: i32,
|
| 295 |
+
width: i32,
|
| 296 |
+
height: i32,
|
| 297 |
+
src_x: i32,
|
| 298 |
+
src_y: i32,
|
| 299 |
+
stipple_mode: u32,
|
| 300 |
+
parity: u32,
|
| 301 |
+
) {
|
| 302 |
+
let src_data =
|
| 303 |
+
core::slice::from_raw_parts((*src).data, ((*src).row_stride * (*src).height) as usize);
|
| 304 |
+
let blit_src = BlitSource {
|
| 305 |
+
data: src_data,
|
| 306 |
+
width: (*src).width,
|
| 307 |
+
height: (*src).height,
|
| 308 |
+
row_stride: (*src).row_stride,
|
| 309 |
+
};
|
| 310 |
+
|
| 311 |
+
let dst_data_len = ((*dst).row_stride * (*dst).height) as usize;
|
| 312 |
+
let mut dst_grid = PixelGridMut {
|
| 313 |
+
data: core::slice::from_raw_parts_mut((*dst).data, dst_data_len),
|
| 314 |
+
width: (*dst).width,
|
| 315 |
+
height: (*dst).height,
|
| 316 |
+
row_stride: (*dst).row_stride,
|
| 317 |
+
clip_left: (*dst).clip_left,
|
| 318 |
+
clip_top: (*dst).clip_top,
|
| 319 |
+
clip_right: (*dst).clip_right,
|
| 320 |
+
clip_bottom: (*dst).clip_bottom,
|
| 321 |
+
};
|
| 322 |
+
|
| 323 |
+
blit_stippled(
|
| 324 |
+
&mut dst_grid,
|
| 325 |
+
&blit_src,
|
| 326 |
+
dst_x,
|
| 327 |
+
dst_y,
|
| 328 |
+
width,
|
| 329 |
+
height,
|
| 330 |
+
src_x,
|
| 331 |
+
src_y,
|
| 332 |
+
stipple_mode,
|
| 333 |
+
parity,
|
| 334 |
+
);
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
/// Tiled blit on raw BitGrid pointers.
|
| 338 |
+
///
|
| 339 |
+
/// Wraps the raw pointers into PixelGrid/BlitSource and calls
|
| 340 |
+
/// `sprite_blit::blit_tiled`.
|
| 341 |
+
pub unsafe fn blit_tiled_raw(
|
| 342 |
+
dst: *mut DisplayBitGrid,
|
| 343 |
+
src: *mut DisplayBitGrid,
|
| 344 |
+
initial_x: i32,
|
| 345 |
+
dst_y: i32,
|
| 346 |
+
tile_width: i32,
|
| 347 |
+
tile_height: i32,
|
| 348 |
+
clip_left: i32,
|
| 349 |
+
clip_right: i32,
|
| 350 |
+
flags: u32,
|
| 351 |
+
) {
|
| 352 |
+
let src_data =
|
| 353 |
+
core::slice::from_raw_parts((*src).data, ((*src).row_stride * (*src).height) as usize);
|
| 354 |
+
let blit_src = BlitSource {
|
| 355 |
+
data: src_data,
|
| 356 |
+
width: (*src).width,
|
| 357 |
+
height: (*src).height,
|
| 358 |
+
row_stride: (*src).row_stride,
|
| 359 |
+
};
|
| 360 |
+
|
| 361 |
+
let dst_data_len = ((*dst).row_stride * (*dst).height) as usize;
|
| 362 |
+
let mut dst_grid = PixelGridMut {
|
| 363 |
+
data: core::slice::from_raw_parts_mut((*dst).data, dst_data_len),
|
| 364 |
+
width: (*dst).width,
|
| 365 |
+
height: (*dst).height,
|
| 366 |
+
row_stride: (*dst).row_stride,
|
| 367 |
+
clip_left: (*dst).clip_left,
|
| 368 |
+
clip_top: (*dst).clip_top,
|
| 369 |
+
clip_right: (*dst).clip_right,
|
| 370 |
+
clip_bottom: (*dst).clip_bottom,
|
| 371 |
+
};
|
| 372 |
+
|
| 373 |
+
blit_tiled(
|
| 374 |
+
&mut dst_grid,
|
| 375 |
+
&blit_src,
|
| 376 |
+
initial_x,
|
| 377 |
+
dst_y,
|
| 378 |
+
tile_width,
|
| 379 |
+
tile_height,
|
| 380 |
+
clip_left,
|
| 381 |
+
clip_right,
|
| 382 |
+
None, // color_table — tiled mode doesn't use one
|
| 383 |
+
flags,
|
| 384 |
+
);
|
| 385 |
+
}
|
|
@@ -35,6 +35,8 @@
|
|
| 35 |
//! (*display_grid).vtable = rb_ptr(BIT_GRID_DISPLAY_VTABLE);
|
| 36 |
//! ```
|
| 37 |
|
|
|
|
|
|
|
| 38 |
use crate::rebase::rb;
|
| 39 |
use crate::task::base::Vtable;
|
| 40 |
use crate::wa_alloc::{wa_malloc, wa_malloc_struct_zeroed};
|
|
|
|
| 35 |
//! (*display_grid).vtable = rb_ptr(BIT_GRID_DISPLAY_VTABLE);
|
| 36 |
//! ```
|
| 37 |
|
| 38 |
+
pub mod blit;
|
| 39 |
+
|
| 40 |
use crate::rebase::rb;
|
| 41 |
use crate::task::base::Vtable;
|
| 42 |
use crate::wa_alloc::{wa_malloc, wa_malloc_struct_zeroed};
|
|
@@ -1363,7 +1363,7 @@ pub unsafe fn projectile_fire(
|
|
| 1363 |
/// Fixed-point 16.16 multiply: (a * b) >> 16, using full 64-bit intermediate.
|
| 1364 |
#[inline(always)]
|
| 1365 |
fn fixed_mul(a: i32, b: i32) -> i32 {
|
| 1366 |
-
(
|
| 1367 |
}
|
| 1368 |
|
| 1369 |
/// Rust implementation of CreateArrow (0x51ED90).
|
|
|
|
| 1363 |
/// Fixed-point 16.16 multiply: (a * b) >> 16, using full 64-bit intermediate.
|
| 1364 |
#[inline(always)]
|
| 1365 |
fn fixed_mul(a: i32, b: i32) -> i32 {
|
| 1366 |
+
crate::fixed::Fixed(a).mul_raw(crate::fixed::Fixed(b)).0
|
| 1367 |
}
|
| 1368 |
|
| 1369 |
/// Rust implementation of CreateArrow (0x51ED90).
|
|
@@ -46,6 +46,7 @@ pub mod registry;
|
|
| 46 |
pub mod rng;
|
| 47 |
pub mod snapshot;
|
| 48 |
pub mod task;
|
|
|
|
| 49 |
pub mod vtable;
|
| 50 |
pub mod wa;
|
| 51 |
pub mod wa_alloc;
|
|
|
|
| 46 |
pub mod rng;
|
| 47 |
pub mod snapshot;
|
| 48 |
pub mod task;
|
| 49 |
+
pub mod trig;
|
| 50 |
pub mod vtable;
|
| 51 |
pub mod wa;
|
| 52 |
pub mod wa_alloc;
|
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Bungee trail rendering — draws the bungee drop trajectory path.
|
| 2 |
+
//!
|
| 3 |
+
//! Ported from WA.exe `DrawBungeeTrail` (0x500720).
|
| 4 |
+
//! Draws:
|
| 5 |
+
//! 1. Sprite at trail start position
|
| 6 |
+
//! 2. Series of vertices computed by accumulating angle + trig interpolation
|
| 7 |
+
//! 3. Final vertex at task position (0x84/0x88)
|
| 8 |
+
//! 4. DrawPolygon (if fill != 0) or DrawLineStrip
|
| 9 |
+
|
| 10 |
+
use crate::address::va;
|
| 11 |
+
use crate::fixed::Fixed;
|
| 12 |
+
use crate::rebase::rb;
|
| 13 |
+
use crate::render::queue::*;
|
| 14 |
+
use crate::render::SpriteOp;
|
| 15 |
+
use crate::task::BungeeTrailTask;
|
| 16 |
+
use crate::trig::trig_lookup;
|
| 17 |
+
|
| 18 |
+
/// Draw bungee trail for the given task.
|
| 19 |
+
///
|
| 20 |
+
/// # Safety
|
| 21 |
+
///
|
| 22 |
+
/// `task_ptr` must point to a valid `BungeeTrailTask`. ASLR rebase must be
|
| 23 |
+
/// initialized.
|
| 24 |
+
pub unsafe fn draw_bungee_trail(task_ptr: u32, style: u32, fill: u32) {
|
| 25 |
+
let task = &*(task_ptr as *const BungeeTrailTask);
|
| 26 |
+
|
| 27 |
+
if task.trail_visible == 0 {
|
| 28 |
+
return;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
let ddgame = &*task.base.ddgame;
|
| 32 |
+
let rq = &mut *ddgame.render_queue;
|
| 33 |
+
|
| 34 |
+
let seg_data = task.segment_data;
|
| 35 |
+
if seg_data.is_null() {
|
| 36 |
+
return;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
let segment_count = task.segment_count;
|
| 40 |
+
if segment_count <= 0 {
|
| 41 |
+
return;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
let mut x = task.trail_start_x;
|
| 45 |
+
let mut y = task.trail_start_y;
|
| 46 |
+
|
| 47 |
+
let first_angle = *(seg_data.add(4) as *const i32);
|
| 48 |
+
|
| 49 |
+
// Enqueue start sprite (command type 5 = local)
|
| 50 |
+
if let Some(entry) = rq.alloc::<DrawSpriteCmd>() {
|
| 51 |
+
*entry = DrawSpriteCmd {
|
| 52 |
+
command_type: command_type::DRAW_SPRITE_LOCAL,
|
| 53 |
+
layer: 0xDFFFF,
|
| 54 |
+
x: Fixed(x).floor(),
|
| 55 |
+
y: Fixed(y).floor(),
|
| 56 |
+
sprite: SpriteOp(0x45),
|
| 57 |
+
palette: (first_angle + 0x8100) as u32,
|
| 58 |
+
};
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// Build vertex array from trail segments
|
| 62 |
+
const MAX_VERTICES: usize = 256;
|
| 63 |
+
let mut verts = [[0i32; 3]; MAX_VERTICES];
|
| 64 |
+
let mut vert_count: usize = 0;
|
| 65 |
+
let mut accumulated_angle: u32 = 0;
|
| 66 |
+
|
| 67 |
+
let sin_table = rb(va::G_SIN_TABLE) as *const i32;
|
| 68 |
+
let cos_table = rb(va::G_COS_TABLE) as *const i32;
|
| 69 |
+
|
| 70 |
+
for i in 0..segment_count {
|
| 71 |
+
let seg_angle = *(seg_data.add(4 + i as usize * 8) as *const i32);
|
| 72 |
+
|
| 73 |
+
// Include vertex if: first segment, or segment has nonzero angle, or fill mode
|
| 74 |
+
if (i == 0 || seg_angle != 0 || fill != 0) && vert_count < MAX_VERTICES {
|
| 75 |
+
verts[vert_count] = [x, y, 0];
|
| 76 |
+
vert_count += 1;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
accumulated_angle = accumulated_angle.wrapping_add(seg_angle as u32);
|
| 80 |
+
|
| 81 |
+
let sin_interp = trig_lookup(sin_table, accumulated_angle);
|
| 82 |
+
let cos_interp = trig_lookup(cos_table, accumulated_angle);
|
| 83 |
+
|
| 84 |
+
x = x.wrapping_add(sin_interp.0.wrapping_mul(8));
|
| 85 |
+
y = y.wrapping_sub(cos_interp.0.wrapping_mul(8));
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// Final vertex = task position (target)
|
| 89 |
+
if vert_count < MAX_VERTICES {
|
| 90 |
+
verts[vert_count] = [task.pos_x.0, task.pos_y.0, 0];
|
| 91 |
+
vert_count += 1;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
// Enqueue as polygon or line strip
|
| 95 |
+
if fill != 0 {
|
| 96 |
+
let total_size = vert_count * 0xC + 0x20;
|
| 97 |
+
if let Some(ptr) = rq.alloc_raw(total_size) {
|
| 98 |
+
let header = &mut *(ptr as *mut DrawPolygonHeader);
|
| 99 |
+
*header = DrawPolygonHeader {
|
| 100 |
+
command_type: command_type::DRAW_POLYGON,
|
| 101 |
+
layer: 0xE_0000,
|
| 102 |
+
count: vert_count as u32,
|
| 103 |
+
color1: style,
|
| 104 |
+
color2: fill,
|
| 105 |
+
};
|
| 106 |
+
core::ptr::copy_nonoverlapping(
|
| 107 |
+
verts.as_ptr() as *const u8,
|
| 108 |
+
ptr.add(core::mem::size_of::<DrawPolygonHeader>()),
|
| 109 |
+
vert_count * 0xC,
|
| 110 |
+
);
|
| 111 |
+
}
|
| 112 |
+
} else {
|
| 113 |
+
let total_size = vert_count * 0xC + 0x1C;
|
| 114 |
+
if let Some(ptr) = rq.alloc_raw(total_size) {
|
| 115 |
+
let header = &mut *(ptr as *mut DrawLineStripHeader);
|
| 116 |
+
*header = DrawLineStripHeader {
|
| 117 |
+
command_type: command_type::DRAW_LINE_STRIP,
|
| 118 |
+
layer: 0xE_0000,
|
| 119 |
+
count: vert_count as u32,
|
| 120 |
+
color: style,
|
| 121 |
+
};
|
| 122 |
+
core::ptr::copy_nonoverlapping(
|
| 123 |
+
verts.as_ptr() as *const u8,
|
| 124 |
+
ptr.add(core::mem::size_of::<DrawLineStripHeader>()),
|
| 125 |
+
vert_count * 0xC,
|
| 126 |
+
);
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Crosshair line rendering — draws the weapon aiming crosshair line.
|
| 2 |
+
//!
|
| 3 |
+
//! Ported from WA.exe `DrawCrosshairLine` (0x5197D0).
|
| 4 |
+
//! Draws:
|
| 5 |
+
//! 1. Compute direction from angle at task+0x264
|
| 6 |
+
//! 2. Compute line length from DDGame scale + task offset
|
| 7 |
+
//! 3. Endpoint = start + direction * length (with overflow clamping)
|
| 8 |
+
//! 4. DrawPolygon (2 vertices) for the line
|
| 9 |
+
//! 5. Conditionally DrawSpriteLocal at endpoint (crosshair sprite)
|
| 10 |
+
|
| 11 |
+
use crate::address::va;
|
| 12 |
+
use crate::fixed::Fixed;
|
| 13 |
+
use crate::rebase::rb;
|
| 14 |
+
use crate::render::queue::*;
|
| 15 |
+
use crate::render::SpriteOp;
|
| 16 |
+
use crate::task::WeaponAimTask;
|
| 17 |
+
use crate::trig::trig_lookup;
|
| 18 |
+
|
| 19 |
+
/// Draw the weapon aiming crosshair line for the given task.
|
| 20 |
+
///
|
| 21 |
+
/// # Safety
|
| 22 |
+
///
|
| 23 |
+
/// `task_ptr` must point to a valid `WeaponAimTask`. ASLR rebase must be
|
| 24 |
+
/// initialized.
|
| 25 |
+
pub unsafe fn draw_crosshair_line(task_ptr: u32) {
|
| 26 |
+
let task = &*(task_ptr as *const WeaponAimTask);
|
| 27 |
+
let gt = &task.game_task;
|
| 28 |
+
|
| 29 |
+
if task.aim_active == 0 {
|
| 30 |
+
return;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
let ddgame = &*gt.base.ddgame;
|
| 34 |
+
let rq = &mut *ddgame.render_queue;
|
| 35 |
+
|
| 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;
|
| 43 |
+
let cos_table = rb(va::G_COS_TABLE) as *const i32;
|
| 44 |
+
let sin_interp = trig_lookup(sin_table, angle);
|
| 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)
|
| 52 |
+
.mul_raw(Fixed(scale))
|
| 53 |
+
.0
|
| 54 |
+
.wrapping_add(start_x);
|
| 55 |
+
let mut endpoint_y = Fixed(cos_interp.0)
|
| 56 |
+
.mul_raw(Fixed(scale))
|
| 57 |
+
.0
|
| 58 |
+
.wrapping_add(start_y);
|
| 59 |
+
|
| 60 |
+
// Overflow clamping — when endpoint overflows i32 due to large scale
|
| 61 |
+
let mut overflowed = false;
|
| 62 |
+
let mut clamp_factor = 0i32;
|
| 63 |
+
|
| 64 |
+
let threshold = (*ddgame.game_info).game_version;
|
| 65 |
+
|
| 66 |
+
if threshold > 0x11E {
|
| 67 |
+
// Check X overflow: sin > 0 but endpoint wrapped below start
|
| 68 |
+
if sin_interp.0 > 0 && endpoint_x < start_x {
|
| 69 |
+
overflowed = true;
|
| 70 |
+
clamp_factor = (0x7FFFFFFFi32 - start_x) / sin_interp.0;
|
| 71 |
+
}
|
| 72 |
+
// Check Y overflow: cos > 0 but endpoint wrapped below start
|
| 73 |
+
if cos_interp.0 > 0 && endpoint_y < start_y {
|
| 74 |
+
let y_clamp = (0x7FFFFFFFi32 - start_y) / cos_interp.0;
|
| 75 |
+
if !overflowed || y_clamp < clamp_factor {
|
| 76 |
+
clamp_factor = y_clamp;
|
| 77 |
+
}
|
| 78 |
+
overflowed = true;
|
| 79 |
+
}
|
| 80 |
+
if overflowed {
|
| 81 |
+
endpoint_x = start_x + clamp_factor * sin_interp.0;
|
| 82 |
+
endpoint_y = start_y + clamp_factor * cos_interp.0;
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
// Enqueue polygon line (2 vertices)
|
| 87 |
+
let poly_param_1 = ddgame.gfx_color_table[8]; // crosshair line style
|
| 88 |
+
let poly_param_2 = ddgame.gfx_color_table[6]; // crosshair line color
|
| 89 |
+
let verts: [[i32; 3]; 2] = [[start_x, start_y, 0], [endpoint_x, endpoint_y, 0]];
|
| 90 |
+
let total_size = 2 * 0xC + 0x20;
|
| 91 |
+
if let Some(ptr) = rq.alloc_raw(total_size) {
|
| 92 |
+
let header = &mut *(ptr as *mut DrawPolygonHeader);
|
| 93 |
+
*header = DrawPolygonHeader {
|
| 94 |
+
command_type: command_type::DRAW_POLYGON,
|
| 95 |
+
layer: 0xE_0000,
|
| 96 |
+
count: 2,
|
| 97 |
+
color1: poly_param_1,
|
| 98 |
+
color2: poly_param_2,
|
| 99 |
+
};
|
| 100 |
+
core::ptr::copy_nonoverlapping(
|
| 101 |
+
verts.as_ptr() as *const u8,
|
| 102 |
+
ptr.add(core::mem::size_of::<DrawPolygonHeader>()),
|
| 103 |
+
2 * 0xC,
|
| 104 |
+
);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
// Draw crosshair sprite at endpoint (only if no overflow clamping)
|
| 108 |
+
if !overflowed {
|
| 109 |
+
if let Some(entry) = rq.alloc::<DrawSpriteCmd>() {
|
| 110 |
+
*entry = DrawSpriteCmd {
|
| 111 |
+
command_type: command_type::DRAW_SPRITE_LOCAL,
|
| 112 |
+
layer: 0x4_0000,
|
| 113 |
+
x: Fixed(endpoint_x).floor(),
|
| 114 |
+
y: Fixed(endpoint_y).floor(),
|
| 115 |
+
sprite: SpriteOp(0x44),
|
| 116 |
+
palette: (0x8000u32).wrapping_sub(angle),
|
| 117 |
+
};
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
}
|
|
@@ -0,0 +1,455 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Rust port of DisplayGfx::BlitSprite (slot 19) and DrawScaledSprite (slot 20).
|
| 2 |
+
//!
|
| 3 |
+
//! These are the high-level sprite blit entry points that resolve sprite IDs,
|
| 4 |
+
//! apply palette transforms, compute destination coordinates with orientation,
|
| 5 |
+
//! and dispatch to the low-level BitGrid blit routines.
|
| 6 |
+
|
| 7 |
+
use crate::address::va;
|
| 8 |
+
use crate::bitgrid::blit::{blit_impl, blit_stippled_raw, blit_tiled_raw};
|
| 9 |
+
use crate::bitgrid::DisplayBitGrid;
|
| 10 |
+
use crate::fixed::Fixed;
|
| 11 |
+
use crate::rebase::rb;
|
| 12 |
+
use crate::render::display::gfx::DisplayGfx;
|
| 13 |
+
use crate::render::display::vtable as display_vtable;
|
| 14 |
+
use crate::render::display::vtable::DrawScaledSpriteResult;
|
| 15 |
+
use crate::render::{SpriteFlags, SpriteOp};
|
| 16 |
+
|
| 17 |
+
/// Rust port of DisplayGfx::BlitSprite (slot 19, 0x56B080).
|
| 18 |
+
///
|
| 19 |
+
/// Resolves sprite ID + animation, applies palette transforms, computes
|
| 20 |
+
/// destination coordinates with orientation, and dispatches to the appropriate
|
| 21 |
+
/// blit routine (normal, stippled, or tiled).
|
| 22 |
+
pub unsafe fn blit_sprite(
|
| 23 |
+
this: *mut DisplayGfx,
|
| 24 |
+
x: Fixed,
|
| 25 |
+
y: Fixed,
|
| 26 |
+
sprite_flags: u32,
|
| 27 |
+
palette: u32,
|
| 28 |
+
) {
|
| 29 |
+
let gfx = this;
|
| 30 |
+
let base = this as *const u8;
|
| 31 |
+
|
| 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 |
+
|
| 39 |
+
if sprite_id == 0 {
|
| 40 |
+
return;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// ---------------------------------------------------------------
|
| 44 |
+
// Palette manipulation
|
| 45 |
+
// ---------------------------------------------------------------
|
| 46 |
+
let mut pal: u32 = palette;
|
| 47 |
+
if flags.contains(SpriteFlags::INVERT_PALETTE) {
|
| 48 |
+
pal = 0x10000u32.wrapping_sub(palette);
|
| 49 |
+
if sprite_id.wrapping_sub(0x1D5) < 3 {
|
| 50 |
+
// Special sprite IDs: scale by 8/18
|
| 51 |
+
pal = (0x10000u32.wrapping_sub(palette).wrapping_mul(8)) / 0x12;
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
if flags.contains(SpriteFlags::PALETTE_XFORM) {
|
| 55 |
+
// Bit 25: palette transform (modular arithmetic for color cycling)
|
| 56 |
+
let tmp = ((pal.wrapping_mul(0x1F) as i32)
|
| 57 |
+
.wrapping_add(((pal.wrapping_mul(0x1F) as i32) >> 31) & 0x1F)
|
| 58 |
+
>> 5) as u32;
|
| 59 |
+
let tmp = tmp.wrapping_add(0x400) & 0xFFFF;
|
| 60 |
+
pal = (tmp.wrapping_rem(0xF800)) / 2;
|
| 61 |
+
if (pal & 0x400) != 0 {
|
| 62 |
+
pal = (pal & !0x400) | 0x8000;
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
// ---------------------------------------------------------------
|
| 67 |
+
// Check sprite arrays — bitmap path if not in primary arrays
|
| 68 |
+
// ---------------------------------------------------------------
|
| 69 |
+
let arr1 = *(base.add(sprite_id as usize * 4 + 0x1008) as *const u32);
|
| 70 |
+
let arr2 = *(base.add(sprite_id as usize * 4 + 0x2008) as *const u32);
|
| 71 |
+
|
| 72 |
+
if arr1 == 0 && arr2 == 0 {
|
| 73 |
+
// Bitmap sprite path — sprite is in the bitmap table at 0x3DD4.
|
| 74 |
+
let bitmap_obj = (*gfx).sprite_table[sprite_id as usize];
|
| 75 |
+
if bitmap_obj.is_null() {
|
| 76 |
+
return;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
// Get frame data and dimensions from bitmap sprite object.
|
| 80 |
+
let mut sprite_w: i32 = 0;
|
| 81 |
+
let mut sprite_h: i32 = 0;
|
| 82 |
+
let mut rect_left: i32 = 0;
|
| 83 |
+
let mut rect_top: i32 = 0;
|
| 84 |
+
let mut rect_right: i32 = 0;
|
| 85 |
+
let mut rect_bottom: i32 = 0;
|
| 86 |
+
let frame_data = display_vtable::get_bitmap_sprite_info(
|
| 87 |
+
bitmap_obj,
|
| 88 |
+
pal,
|
| 89 |
+
&mut sprite_w,
|
| 90 |
+
&mut sprite_h,
|
| 91 |
+
&mut rect_left,
|
| 92 |
+
&mut rect_top,
|
| 93 |
+
&mut rect_right,
|
| 94 |
+
&mut rect_bottom,
|
| 95 |
+
);
|
| 96 |
+
if frame_data.is_null() {
|
| 97 |
+
return;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
let camera_x = (*gfx).camera_x;
|
| 101 |
+
let camera_y = (*gfx).camera_y;
|
| 102 |
+
let half_w = sprite_w / 2;
|
| 103 |
+
let half_h = sprite_h / 2;
|
| 104 |
+
let blit_h = rect_bottom - rect_top;
|
| 105 |
+
let dst_x = (x.0 >> 16) + (camera_x - half_w) + rect_left;
|
| 106 |
+
let dst_y = (y.0 >> 16) + (camera_y - half_h) + rect_top;
|
| 107 |
+
|
| 108 |
+
if !flags.contains(SpriteFlags::TILED) {
|
| 109 |
+
// Non-tiled: clipped blit at (dst_x, dst_y).
|
| 110 |
+
display_vtable::blit_bitmap_clipped_native(
|
| 111 |
+
gfx, dst_x, dst_y, sprite_w, blit_h, frame_data, 2,
|
| 112 |
+
);
|
| 113 |
+
} else {
|
| 114 |
+
// Tiled: horizontal tile across the clip rect.
|
| 115 |
+
display_vtable::blit_bitmap_tiled_native(
|
| 116 |
+
gfx, dst_x, sprite_w, dst_y, blit_h, frame_data,
|
| 117 |
+
);
|
| 118 |
+
}
|
| 119 |
+
return;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
// ---------------------------------------------------------------
|
| 123 |
+
// Bit 24: palette ×4 adjust with orientation-dependent high bits
|
| 124 |
+
// ---------------------------------------------------------------
|
| 125 |
+
// The original ASM at 0x56B145 scales pal by 4, adds 0x8000, then
|
| 126 |
+
// uses the overflow quadrant (bits 16-17 of the scaled value) to
|
| 127 |
+
// select an orientation+blend combo. The low 16 bits become the
|
| 128 |
+
// new palette value.
|
| 129 |
+
let mut orient_local: u32 = 0x0000_0001; // blend=1 (ColorTable/transparency), orientation=0 (Normal)
|
| 130 |
+
if flags.contains(SpriteFlags::PALETTE_X4) {
|
| 131 |
+
let scaled = pal.wrapping_mul(4).wrapping_add(0x8000);
|
| 132 |
+
pal = scaled & 0xFFFF;
|
| 133 |
+
let quad = ((scaled as i32) >> 16) & 3;
|
| 134 |
+
orient_local = match quad {
|
| 135 |
+
0 => 0x0008_0001, // MirrorXY + ColorTable
|
| 136 |
+
1 => 0x000C_0001, // Rotate90MirrorXY + ColorTable
|
| 137 |
+
2 => 0x0004_0001, // Rotate90 + ColorTable
|
| 138 |
+
_ => 0x0000_0001, // Normal + ColorTable
|
| 139 |
+
};
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
// ---------------------------------------------------------------
|
| 143 |
+
// Sprite frame lookup via DisplayGfx::GetSpriteFrameForBlit (slot 33).
|
| 144 |
+
// ---------------------------------------------------------------
|
| 145 |
+
// Resolves the sprite ID + animation value into the actual decompressed
|
| 146 |
+
// frame surface plus its bounding box and full sprite-cell dimensions
|
| 147 |
+
// (used below for centering and clipped blits).
|
| 148 |
+
let mut out_sprite_w: i32 = 0;
|
| 149 |
+
let mut out_sprite_h: i32 = 0;
|
| 150 |
+
let mut out_rect_left: i32 = 0;
|
| 151 |
+
let mut out_rect_top: i32 = 0;
|
| 152 |
+
let mut out_rect_right: i32 = 0;
|
| 153 |
+
let mut out_rect_bottom: i32 = 0;
|
| 154 |
+
let mut out_anim_frac: u32 = 0;
|
| 155 |
+
|
| 156 |
+
let mut sprite_surface = DisplayGfx::get_sprite_frame_for_blit_raw(
|
| 157 |
+
gfx,
|
| 158 |
+
sprite_id,
|
| 159 |
+
pal,
|
| 160 |
+
&mut out_sprite_w,
|
| 161 |
+
&mut out_sprite_h,
|
| 162 |
+
&mut out_rect_left,
|
| 163 |
+
&mut out_rect_top,
|
| 164 |
+
&mut out_rect_right,
|
| 165 |
+
&mut out_rect_bottom,
|
| 166 |
+
&mut out_anim_frac,
|
| 167 |
+
);
|
| 168 |
+
|
| 169 |
+
if sprite_surface.is_null() {
|
| 170 |
+
return;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
let sprite_w = out_sprite_w;
|
| 174 |
+
let sprite_h = out_sprite_h;
|
| 175 |
+
let rect_left = out_rect_left;
|
| 176 |
+
let rect_top = out_rect_top;
|
| 177 |
+
let rect_right = out_rect_right;
|
| 178 |
+
let rect_bottom = out_rect_bottom;
|
| 179 |
+
|
| 180 |
+
// Size checks
|
| 181 |
+
if rect_left >= rect_right || rect_top >= rect_bottom {
|
| 182 |
+
return;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
let mut blit_w = rect_right - rect_left;
|
| 186 |
+
let mut blit_h = rect_bottom - rect_top;
|
| 187 |
+
|
| 188 |
+
// ---------------------------------------------------------------
|
| 189 |
+
// Shadow clear (high_flags bit 22)
|
| 190 |
+
// ---------------------------------------------------------------
|
| 191 |
+
if flags.contains(SpriteFlags::SHADOW_CLEAR) {
|
| 192 |
+
// Blit sprite to layer_2 as shadow base
|
| 193 |
+
let layer2 = (*gfx).layer_2;
|
| 194 |
+
blit_impl(
|
| 195 |
+
layer2,
|
| 196 |
+
0,
|
| 197 |
+
0,
|
| 198 |
+
blit_w,
|
| 199 |
+
blit_h,
|
| 200 |
+
sprite_surface,
|
| 201 |
+
0,
|
| 202 |
+
0,
|
| 203 |
+
core::ptr::null(),
|
| 204 |
+
0, // mode 0 = copy
|
| 205 |
+
);
|
| 206 |
+
// Manipulate color_add_table entry for shadow
|
| 207 |
+
let color_idx = ((*gfx)._unknown_356c as usize) * 0x100;
|
| 208 |
+
let table_byte = &mut (*gfx).color_add_table[color_idx];
|
| 209 |
+
let saved = *table_byte;
|
| 210 |
+
*table_byte = 0;
|
| 211 |
+
|
| 212 |
+
// Call BitGrid__ClearColumn_Maybe (0x4F6590) — clears shadow channel
|
| 213 |
+
let clear_fn: unsafe extern "cdecl" fn(*mut u8) =
|
| 214 |
+
core::mem::transmute(rb(0x004F6590) as usize);
|
| 215 |
+
clear_fn(table_byte as *mut u8);
|
| 216 |
+
|
| 217 |
+
*table_byte = saved;
|
| 218 |
+
|
| 219 |
+
// Replace sprite surface with layer_2 (shadow-processed)
|
| 220 |
+
sprite_surface = layer2;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
// ---------------------------------------------------------------
|
| 224 |
+
// Extra orientation flags from high_flags
|
| 225 |
+
// ---------------------------------------------------------------
|
| 226 |
+
if flags.contains(SpriteFlags::MIRROR_X) {
|
| 227 |
+
orient_local |= 0x0001_0000;
|
| 228 |
+
}
|
| 229 |
+
if flags.contains(SpriteFlags::MIRROR_Y) {
|
| 230 |
+
orient_local |= 0x0002_0000;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
// ---------------------------------------------------------------
|
| 234 |
+
// 16-case orientation switch for camera coordinate mapping
|
| 235 |
+
// ---------------------------------------------------------------
|
| 236 |
+
let camera_x = (*gfx).camera_x;
|
| 237 |
+
let camera_y = (*gfx).camera_y;
|
| 238 |
+
|
| 239 |
+
// Signed divide toward zero (matches MSVC CDQ+SUB+SAR pattern)
|
| 240 |
+
let half_w = if sprite_w < 0 {
|
| 241 |
+
(sprite_w + 1) / 2
|
| 242 |
+
} else {
|
| 243 |
+
sprite_w / 2
|
| 244 |
+
};
|
| 245 |
+
let half_h = if sprite_h < 0 {
|
| 246 |
+
(sprite_h + 1) / 2
|
| 247 |
+
} else {
|
| 248 |
+
sprite_h / 2
|
| 249 |
+
};
|
| 250 |
+
|
| 251 |
+
let x_px = x.0 >> 16;
|
| 252 |
+
let y_px = y.0 >> 16;
|
| 253 |
+
|
| 254 |
+
let (dst_x, dst_y);
|
| 255 |
+
let orientation_key = (orient_local >> 16) as i32;
|
| 256 |
+
|
| 257 |
+
match orientation_key {
|
| 258 |
+
1 | 10 => {
|
| 259 |
+
// MirrorX
|
| 260 |
+
dst_x = camera_x + half_w + x_px - rect_right;
|
| 261 |
+
dst_y = camera_y - half_h + rect_top + y_px;
|
| 262 |
+
}
|
| 263 |
+
2 | 9 => {
|
| 264 |
+
// MirrorY — X same as Normal, Y mirrored
|
| 265 |
+
dst_x = camera_x - half_w + rect_left + x_px;
|
| 266 |
+
dst_y = camera_y + half_h + y_px - rect_bottom;
|
| 267 |
+
}
|
| 268 |
+
3 | 8 => {
|
| 269 |
+
// MirrorXY
|
| 270 |
+
dst_x = camera_x + half_w + x_px - rect_right;
|
| 271 |
+
dst_y = camera_y + half_h + y_px - rect_bottom;
|
| 272 |
+
}
|
| 273 |
+
4 | 15 => {
|
| 274 |
+
// Rotate90 — swap axes
|
| 275 |
+
dst_x = camera_x - half_h + rect_top + x_px;
|
| 276 |
+
dst_y = camera_y + half_w + y_px - rect_right;
|
| 277 |
+
blit_w = rect_bottom - rect_top;
|
| 278 |
+
blit_h = rect_right - rect_left;
|
| 279 |
+
}
|
| 280 |
+
5 | 14 => {
|
| 281 |
+
// Rotate90MirrorX
|
| 282 |
+
dst_x = camera_x + half_h + x_px - rect_bottom;
|
| 283 |
+
dst_y = camera_y + half_w + y_px - rect_right;
|
| 284 |
+
blit_w = rect_bottom - rect_top;
|
| 285 |
+
blit_h = rect_right - rect_left;
|
| 286 |
+
}
|
| 287 |
+
6 | 13 => {
|
| 288 |
+
// Rotate90MirrorY
|
| 289 |
+
dst_x = camera_x - half_h + rect_top + x_px;
|
| 290 |
+
dst_y = camera_y - half_w + rect_left + y_px;
|
| 291 |
+
blit_w = rect_bottom - rect_top;
|
| 292 |
+
blit_h = rect_right - rect_left;
|
| 293 |
+
}
|
| 294 |
+
7 | 12 => {
|
| 295 |
+
// Rotate90MirrorXY
|
| 296 |
+
dst_x = camera_x + half_h + x_px - rect_bottom;
|
| 297 |
+
dst_y = camera_y - half_w + rect_left + y_px;
|
| 298 |
+
blit_w = rect_bottom - rect_top;
|
| 299 |
+
blit_h = rect_right - rect_left;
|
| 300 |
+
}
|
| 301 |
+
_ => {
|
| 302 |
+
// Normal (0, 11, and any other value)
|
| 303 |
+
dst_x = camera_x - half_w + rect_left + x_px;
|
| 304 |
+
dst_y = camera_y - half_h + rect_top + y_px;
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
// ---------------------------------------------------------------
|
| 309 |
+
// Blit dispatch based on high_flags
|
| 310 |
+
// ---------------------------------------------------------------
|
| 311 |
+
|
| 312 |
+
if blit_w <= 0 || blit_h <= 0 {
|
| 313 |
+
return;
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
// Stippled mode (checkerboard per-pixel blit)
|
| 317 |
+
if flags.intersects(SpriteFlags::STIPPLED_0 | SpriteFlags::STIPPLED_1) {
|
| 318 |
+
let stipple_mode: u32 = if flags.contains(SpriteFlags::STIPPLED_1) {
|
| 319 |
+
1
|
| 320 |
+
} else {
|
| 321 |
+
0
|
| 322 |
+
};
|
| 323 |
+
let parity = *(rb(va::G_STIPPLE_PARITY) as *const u32);
|
| 324 |
+
|
| 325 |
+
display_vtable::acquire_render_lock(gfx);
|
| 326 |
+
|
| 327 |
+
blit_stippled_raw(
|
| 328 |
+
(*gfx).layer_0,
|
| 329 |
+
sprite_surface,
|
| 330 |
+
dst_x,
|
| 331 |
+
dst_y,
|
| 332 |
+
blit_w,
|
| 333 |
+
blit_h,
|
| 334 |
+
0,
|
| 335 |
+
0,
|
| 336 |
+
stipple_mode,
|
| 337 |
+
parity,
|
| 338 |
+
);
|
| 339 |
+
return;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
// Tiled mode (horizontal sprite tiling)
|
| 343 |
+
if flags.contains(SpriteFlags::TILED) {
|
| 344 |
+
display_vtable::acquire_render_lock(gfx);
|
| 345 |
+
|
| 346 |
+
blit_tiled_raw(
|
| 347 |
+
(*gfx).layer_0,
|
| 348 |
+
sprite_surface,
|
| 349 |
+
dst_x,
|
| 350 |
+
dst_y,
|
| 351 |
+
blit_w,
|
| 352 |
+
blit_h,
|
| 353 |
+
(*gfx).base.clip_x1,
|
| 354 |
+
(*gfx).base.clip_x2,
|
| 355 |
+
orient_local,
|
| 356 |
+
);
|
| 357 |
+
return;
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
// Determine color table pointer
|
| 361 |
+
let color_table: *const u8 = if flags.contains(SpriteFlags::ADDITIVE) {
|
| 362 |
+
(*gfx).color_add_table.as_ptr()
|
| 363 |
+
} else if flags.contains(SpriteFlags::COLOR_BLEND) {
|
| 364 |
+
(*gfx).color_blend_table.as_ptr()
|
| 365 |
+
} else {
|
| 366 |
+
core::ptr::null()
|
| 367 |
+
};
|
| 368 |
+
|
| 369 |
+
display_vtable::acquire_render_lock(gfx);
|
| 370 |
+
|
| 371 |
+
// src_x=0, src_y=0 always — vtable[33] already set up the sprite surface
|
| 372 |
+
blit_impl(
|
| 373 |
+
(*gfx).layer_0,
|
| 374 |
+
dst_x,
|
| 375 |
+
dst_y,
|
| 376 |
+
blit_w,
|
| 377 |
+
blit_h,
|
| 378 |
+
sprite_surface,
|
| 379 |
+
0,
|
| 380 |
+
0,
|
| 381 |
+
color_table,
|
| 382 |
+
orient_local,
|
| 383 |
+
);
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
/// Rust port of DisplayGfx::DrawScaledSprite (slot 20, 0x56B5F0).
|
| 387 |
+
///
|
| 388 |
+
/// Computes coordinates in the vtable helper, then dispatches the blit
|
| 389 |
+
/// via blit_impl or blit_stippled_raw depending on the result.
|
| 390 |
+
pub unsafe fn draw_scaled_sprite(
|
| 391 |
+
this: *mut DisplayGfx,
|
| 392 |
+
x: Fixed,
|
| 393 |
+
y: Fixed,
|
| 394 |
+
sprite: *mut DisplayBitGrid,
|
| 395 |
+
src_x: i32,
|
| 396 |
+
src_y: i32,
|
| 397 |
+
src_w: i32,
|
| 398 |
+
src_h: i32,
|
| 399 |
+
flags: u32,
|
| 400 |
+
) {
|
| 401 |
+
match display_vtable::draw_scaled_sprite(this, x, y, sprite, src_x, src_y, src_w, src_h, flags)
|
| 402 |
+
{
|
| 403 |
+
DrawScaledSpriteResult::Blit {
|
| 404 |
+
layer,
|
| 405 |
+
dst_x,
|
| 406 |
+
dst_y,
|
| 407 |
+
width,
|
| 408 |
+
height,
|
| 409 |
+
sprite,
|
| 410 |
+
src_x,
|
| 411 |
+
src_y,
|
| 412 |
+
color_table,
|
| 413 |
+
blit_flags,
|
| 414 |
+
} => {
|
| 415 |
+
blit_impl(
|
| 416 |
+
layer,
|
| 417 |
+
dst_x,
|
| 418 |
+
dst_y,
|
| 419 |
+
width,
|
| 420 |
+
height,
|
| 421 |
+
sprite,
|
| 422 |
+
src_x,
|
| 423 |
+
src_y,
|
| 424 |
+
color_table,
|
| 425 |
+
blit_flags,
|
| 426 |
+
);
|
| 427 |
+
}
|
| 428 |
+
DrawScaledSpriteResult::Stippled {
|
| 429 |
+
layer,
|
| 430 |
+
dst_x,
|
| 431 |
+
dst_y,
|
| 432 |
+
width,
|
| 433 |
+
height,
|
| 434 |
+
sprite,
|
| 435 |
+
src_x,
|
| 436 |
+
src_y,
|
| 437 |
+
stipple_mode,
|
| 438 |
+
} => {
|
| 439 |
+
let parity = *(rb(va::G_STIPPLE_PARITY) as *const u32);
|
| 440 |
+
blit_stippled_raw(
|
| 441 |
+
layer,
|
| 442 |
+
sprite,
|
| 443 |
+
dst_x,
|
| 444 |
+
dst_y,
|
| 445 |
+
width,
|
| 446 |
+
height,
|
| 447 |
+
src_x,
|
| 448 |
+
src_y,
|
| 449 |
+
stipple_mode,
|
| 450 |
+
parity,
|
| 451 |
+
);
|
| 452 |
+
}
|
| 453 |
+
DrawScaledSpriteResult::Handled => {}
|
| 454 |
+
}
|
| 455 |
+
}
|
|
@@ -43,6 +43,7 @@ use core::ptr;
|
|
| 43 |
use crate::address::va;
|
| 44 |
use crate::bitgrid::{BitGridBaseVtable, BIT_GRID_BASE_VTABLE};
|
| 45 |
use crate::rebase::rb;
|
|
|
|
| 46 |
use crate::render::display::context::{FastcallResult, RenderContext};
|
| 47 |
use crate::render::display::frame_hook::FramePostProcessHook;
|
| 48 |
use crate::render::display::gfx::{DisplayGfx, TileBitmapSet, DISPLAY_BASE_DESTRUCTOR_IMPL};
|
|
@@ -293,3 +294,38 @@ unsafe fn cbitmap_vtable0(bitmap: *mut CBitmap, flags: u32) {
|
|
| 293 |
core::mem::transmute(*vtable);
|
| 294 |
dtor(bitmap, flags);
|
| 295 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
use crate::address::va;
|
| 44 |
use crate::bitgrid::{BitGridBaseVtable, BIT_GRID_BASE_VTABLE};
|
| 45 |
use crate::rebase::rb;
|
| 46 |
+
use crate::render::display::base::DisplayBase;
|
| 47 |
use crate::render::display::context::{FastcallResult, RenderContext};
|
| 48 |
use crate::render::display::frame_hook::FramePostProcessHook;
|
| 49 |
use crate::render::display::gfx::{DisplayGfx, TileBitmapSet, DISPLAY_BASE_DESTRUCTOR_IMPL};
|
|
|
|
| 294 |
core::mem::transmute(*vtable);
|
| 295 |
dtor(bitmap, flags);
|
| 296 |
}
|
| 297 |
+
|
| 298 |
+
// =============================================================================
|
| 299 |
+
// Headless destructor — used when DisplayBase is a headless stub
|
| 300 |
+
// =============================================================================
|
| 301 |
+
|
| 302 |
+
/// Destructor for the headless `DisplayBase` — frees the sprite cache chain
|
| 303 |
+
/// (SpriteCache → FrameCache → buffer) that was allocated by `new_headless()`.
|
| 304 |
+
///
|
| 305 |
+
/// Installed as vtable slot 0 on the headless `DisplayBase` vtable (0x66A0F8)
|
| 306 |
+
/// by the DLL's display hook installer.
|
| 307 |
+
///
|
| 308 |
+
/// # Safety
|
| 309 |
+
///
|
| 310 |
+
/// `this` must be a valid `DisplayBase` pointer with a headless-allocated
|
| 311 |
+
/// sprite cache chain.
|
| 312 |
+
pub unsafe fn headless_destructor(this: *mut DisplayBase, flags: u8) -> *mut DisplayBase {
|
| 313 |
+
use crate::wa_alloc::wa_free;
|
| 314 |
+
|
| 315 |
+
let sprite_cache = (*this).sprite_cache;
|
| 316 |
+
if !sprite_cache.is_null() {
|
| 317 |
+
let frame_cache = (*sprite_cache).frame_cache;
|
| 318 |
+
if !frame_cache.is_null() {
|
| 319 |
+
let buf = (*frame_cache).buffer;
|
| 320 |
+
if !buf.is_null() {
|
| 321 |
+
wa_free(buf);
|
| 322 |
+
}
|
| 323 |
+
wa_free(frame_cache);
|
| 324 |
+
}
|
| 325 |
+
wa_free(sprite_cache);
|
| 326 |
+
}
|
| 327 |
+
if flags & 1 != 0 {
|
| 328 |
+
wa_free(this);
|
| 329 |
+
}
|
| 330 |
+
this
|
| 331 |
+
}
|
|
@@ -1,4 +1,5 @@
|
|
| 1 |
pub mod base;
|
|
|
|
| 2 |
pub mod context;
|
| 3 |
pub mod destructor;
|
| 4 |
pub mod font;
|
|
|
|
| 1 |
pub mod base;
|
| 2 |
+
pub mod blit_sprite;
|
| 3 |
pub mod context;
|
| 4 |
pub mod destructor;
|
| 5 |
pub mod font;
|
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
pub mod ddraw;
|
| 2 |
pub mod display;
|
| 3 |
pub mod landscape;
|
|
|
|
| 1 |
+
pub mod bungee_trail;
|
| 2 |
+
pub mod crosshair_line;
|
| 3 |
pub mod ddraw;
|
| 4 |
pub mod display;
|
| 5 |
pub mod landscape;
|
|
@@ -1,3 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
use super::message::{RenderMessage, TypedRenderCmd, COMMAND_TYPE_TYPED};
|
| 2 |
use super::sprite::sprite_op::SpriteOp;
|
| 3 |
|
|
@@ -10,10 +14,10 @@ pub struct DrawSpriteCmd {
|
|
| 10 |
/// Command type: 4 = sprite global (world-space), 5 = sprite local (screen-space)
|
| 11 |
pub command_type: u32,
|
| 12 |
pub layer: u32,
|
| 13 |
-
/// X position,
|
| 14 |
-
pub
|
| 15 |
-
/// Y position,
|
| 16 |
-
pub
|
| 17 |
pub sprite: SpriteOp,
|
| 18 |
/// Palette context — passed to `blit_sprite` as the last arg.
|
| 19 |
/// Semantics vary by producer (palette pointer, animation index, etc.).
|
|
@@ -116,14 +120,14 @@ pub struct DrawTiledBitmapCmd {
|
|
| 116 |
/// 0x08: Source X coordinate (Fixed16). Hardcoded 0xFF00_0000 (= -256.0)
|
| 117 |
/// — the tile sheet always renders from off-screen-left, with the
|
| 118 |
/// dispatcher's clipping pass producing the on-screen X.
|
| 119 |
-
pub
|
| 120 |
/// 0x0C: Source Y coordinate (Fixed16). Caller-supplied; goes through
|
| 121 |
/// `RQ_ClipCoordinates` and the high 16 bits become the destination Y.
|
| 122 |
-
pub
|
| 123 |
-
/// 0x10: Pointer to a
|
| 124 |
-
///
|
| 125 |
/// (source row stride), `+0x14`, and `+0x18` (total source height).
|
| 126 |
-
pub
|
| 127 |
/// 0x14: Flag byte. Bit 0 forces the destination X to 0 (ignoring the
|
| 128 |
/// clipped result), used for "always start at left edge" rendering.
|
| 129 |
pub flags: u8,
|
|
@@ -143,9 +147,9 @@ pub struct DrawCrosshairCmd {
|
|
| 143 |
pub layer: u32,
|
| 144 |
pub color_fg: u32,
|
| 145 |
pub color_bg: u32,
|
| 146 |
-
pub
|
| 147 |
-
pub
|
| 148 |
-
pub
|
| 149 |
}
|
| 150 |
|
| 151 |
const _: () = assert!(core::mem::size_of::<DrawCrosshairCmd>() == 0x1C);
|
|
@@ -156,11 +160,11 @@ pub struct DrawRectCmd {
|
|
| 156 |
pub command_type: u32, // 0
|
| 157 |
pub layer: u32,
|
| 158 |
pub color: u32,
|
| 159 |
-
pub x1:
|
| 160 |
-
pub y1:
|
| 161 |
-
pub x2:
|
| 162 |
-
pub y2:
|
| 163 |
-
pub
|
| 164 |
}
|
| 165 |
|
| 166 |
const _: () = assert!(core::mem::size_of::<DrawRectCmd>() == 0x20);
|
|
@@ -170,13 +174,13 @@ const _: () = assert!(core::mem::size_of::<DrawRectCmd>() == 0x20);
|
|
| 170 |
pub struct DrawSpriteOffsetCmd {
|
| 171 |
pub command_type: u32, // 6
|
| 172 |
pub layer: u32,
|
| 173 |
-
pub
|
| 174 |
-
pub
|
| 175 |
-
pub
|
| 176 |
-
pub
|
| 177 |
-
pub
|
| 178 |
-
pub
|
| 179 |
-
pub
|
| 180 |
}
|
| 181 |
|
| 182 |
const _: () = assert!(core::mem::size_of::<DrawSpriteOffsetCmd>() == 0x24);
|
|
@@ -186,14 +190,14 @@ const _: () = assert!(core::mem::size_of::<DrawSpriteOffsetCmd>() == 0x24);
|
|
| 186 |
pub struct DrawBitmapGlobalCmd {
|
| 187 |
pub command_type: u32, // 1
|
| 188 |
pub layer: u32,
|
| 189 |
-
pub
|
| 190 |
-
pub
|
| 191 |
-
pub
|
| 192 |
-
pub
|
| 193 |
-
pub
|
| 194 |
-
pub
|
| 195 |
-
pub
|
| 196 |
-
pub
|
| 197 |
}
|
| 198 |
|
| 199 |
const _: () = assert!(core::mem::size_of::<DrawBitmapGlobalCmd>() == 0x28);
|
|
@@ -203,17 +207,17 @@ const _: () = assert!(core::mem::size_of::<DrawBitmapGlobalCmd>() == 0x28);
|
|
| 203 |
pub struct DrawTextboxLocalCmd {
|
| 204 |
pub command_type: u32, // 2
|
| 205 |
pub layer: u32,
|
| 206 |
-
pub
|
| 207 |
-
pub
|
| 208 |
-
pub
|
| 209 |
-
pub
|
| 210 |
-
pub
|
| 211 |
-
pub
|
| 212 |
-
pub
|
| 213 |
-
pub
|
| 214 |
-
pub
|
| 215 |
-
pub
|
| 216 |
-
pub
|
| 217 |
}
|
| 218 |
|
| 219 |
const _: () = assert!(core::mem::size_of::<DrawTextboxLocalCmd>() == 0x34);
|
|
@@ -226,7 +230,7 @@ pub struct DrawLineStripHeader {
|
|
| 226 |
pub command_type: u32, // 8
|
| 227 |
pub layer: u32, // hardcoded 0xE_0000
|
| 228 |
pub count: u32, // vertex count (from EDI)
|
| 229 |
-
pub
|
| 230 |
}
|
| 231 |
|
| 232 |
const _: () = assert!(core::mem::size_of::<DrawLineStripHeader>() == 0x10);
|
|
@@ -239,8 +243,8 @@ pub struct DrawPolygonHeader {
|
|
| 239 |
pub command_type: u32, // 9
|
| 240 |
pub layer: u32, // hardcoded 0xE_0000
|
| 241 |
pub count: u32, // vertex count (from ESI)
|
| 242 |
-
pub
|
| 243 |
-
pub
|
| 244 |
}
|
| 245 |
|
| 246 |
const _: () = assert!(core::mem::size_of::<DrawPolygonHeader>() == 0x14);
|
|
|
|
| 1 |
+
use crate::bitgrid::DisplayBitGrid;
|
| 2 |
+
use crate::fixed::Fixed;
|
| 3 |
+
use crate::render::display::vtable::TiledBitmapSource;
|
| 4 |
+
|
| 5 |
use super::message::{RenderMessage, TypedRenderCmd, COMMAND_TYPE_TYPED};
|
| 6 |
use super::sprite::sprite_op::SpriteOp;
|
| 7 |
|
|
|
|
| 14 |
/// Command type: 4 = sprite global (world-space), 5 = sprite local (screen-space)
|
| 15 |
pub command_type: u32,
|
| 16 |
pub layer: u32,
|
| 17 |
+
/// X position (Fixed16, fractional bits always zero — producers floor before storing).
|
| 18 |
+
pub x: Fixed,
|
| 19 |
+
/// Y position (Fixed16, fractional bits always zero — producers floor before storing).
|
| 20 |
+
pub y: Fixed,
|
| 21 |
pub sprite: SpriteOp,
|
| 22 |
/// Palette context — passed to `blit_sprite` as the last arg.
|
| 23 |
/// Semantics vary by producer (palette pointer, animation index, etc.).
|
|
|
|
| 120 |
/// 0x08: Source X coordinate (Fixed16). Hardcoded 0xFF00_0000 (= -256.0)
|
| 121 |
/// — the tile sheet always renders from off-screen-left, with the
|
| 122 |
/// dispatcher's clipping pass producing the on-screen X.
|
| 123 |
+
pub x: Fixed,
|
| 124 |
/// 0x0C: Source Y coordinate (Fixed16). Caller-supplied; goes through
|
| 125 |
/// `RQ_ClipCoordinates` and the high 16 bits become the destination Y.
|
| 126 |
+
pub y: Fixed,
|
| 127 |
+
/// 0x10: Pointer to a [`TiledBitmapSource`] descriptor — read by the
|
| 128 |
+
/// slot 11 method with fields at `+0x08` (bpp, 8 or 0x40), `+0x10`
|
| 129 |
/// (source row stride), `+0x14`, and `+0x18` (total source height).
|
| 130 |
+
pub source: *const TiledBitmapSource,
|
| 131 |
/// 0x14: Flag byte. Bit 0 forces the destination X to 0 (ignoring the
|
| 132 |
/// clipped result), used for "always start at left edge" rendering.
|
| 133 |
pub flags: u8,
|
|
|
|
| 147 |
pub layer: u32,
|
| 148 |
pub color_fg: u32,
|
| 149 |
pub color_bg: u32,
|
| 150 |
+
pub x: Fixed,
|
| 151 |
+
pub y: Fixed,
|
| 152 |
+
pub ref_z: i32, // hardcoded 0 (perspective clip reference Z)
|
| 153 |
}
|
| 154 |
|
| 155 |
const _: () = assert!(core::mem::size_of::<DrawCrosshairCmd>() == 0x1C);
|
|
|
|
| 160 |
pub command_type: u32, // 0
|
| 161 |
pub layer: u32,
|
| 162 |
pub color: u32,
|
| 163 |
+
pub x1: Fixed,
|
| 164 |
+
pub y1: Fixed,
|
| 165 |
+
pub x2: Fixed,
|
| 166 |
+
pub y2: Fixed,
|
| 167 |
+
pub ref_z: i32, // perspective clip reference Z (EDX & 0xFFFF0000)
|
| 168 |
}
|
| 169 |
|
| 170 |
const _: () = assert!(core::mem::size_of::<DrawRectCmd>() == 0x20);
|
|
|
|
| 174 |
pub struct DrawSpriteOffsetCmd {
|
| 175 |
pub command_type: u32, // 6
|
| 176 |
pub layer: u32,
|
| 177 |
+
pub flags: u32,
|
| 178 |
+
pub x: Fixed,
|
| 179 |
+
pub y: Fixed,
|
| 180 |
+
pub ref_z: i32, // first Z reference for perspective clip (hardcoded 0)
|
| 181 |
+
pub ref_z_2: i32, // second Z reference (EDX & 0xFFFF0000)
|
| 182 |
+
pub sprite: SpriteOp,
|
| 183 |
+
pub palette: u32,
|
| 184 |
}
|
| 185 |
|
| 186 |
const _: () = assert!(core::mem::size_of::<DrawSpriteOffsetCmd>() == 0x24);
|
|
|
|
| 190 |
pub struct DrawBitmapGlobalCmd {
|
| 191 |
pub command_type: u32, // 1
|
| 192 |
pub layer: u32,
|
| 193 |
+
pub x: Fixed,
|
| 194 |
+
pub y: Fixed,
|
| 195 |
+
pub bitmap: *mut DisplayBitGrid,
|
| 196 |
+
pub src_x: i32, // hardcoded 0
|
| 197 |
+
pub src_y: i32,
|
| 198 |
+
pub src_w: i32,
|
| 199 |
+
pub src_h: i32,
|
| 200 |
+
pub flags: u32,
|
| 201 |
}
|
| 202 |
|
| 203 |
const _: () = assert!(core::mem::size_of::<DrawBitmapGlobalCmd>() == 0x28);
|
|
|
|
| 207 |
pub struct DrawTextboxLocalCmd {
|
| 208 |
pub command_type: u32, // 2
|
| 209 |
pub layer: u32,
|
| 210 |
+
pub mode: u32,
|
| 211 |
+
pub x: Fixed,
|
| 212 |
+
pub y: Fixed,
|
| 213 |
+
pub ref_z: i32, // perspective clip reference Z (hardcoded 0)
|
| 214 |
+
pub ref_z_2: i32, // second Z reference (hardcoded 0)
|
| 215 |
+
pub bitmap: *mut DisplayBitGrid,
|
| 216 |
+
pub src_x: i32, // hardcoded 0
|
| 217 |
+
pub src_y: i32, // hardcoded 0
|
| 218 |
+
pub src_w: i32,
|
| 219 |
+
pub src_h: i32,
|
| 220 |
+
pub flags: u32,
|
| 221 |
}
|
| 222 |
|
| 223 |
const _: () = assert!(core::mem::size_of::<DrawTextboxLocalCmd>() == 0x34);
|
|
|
|
| 230 |
pub command_type: u32, // 8
|
| 231 |
pub layer: u32, // hardcoded 0xE_0000
|
| 232 |
pub count: u32, // vertex count (from EDI)
|
| 233 |
+
pub color: u32, // line color
|
| 234 |
}
|
| 235 |
|
| 236 |
const _: () = assert!(core::mem::size_of::<DrawLineStripHeader>() == 0x10);
|
|
|
|
| 243 |
pub command_type: u32, // 9
|
| 244 |
pub layer: u32, // hardcoded 0xE_0000
|
| 245 |
pub count: u32, // vertex count (from ESI)
|
| 246 |
+
pub color1: u32, // line color 1
|
| 247 |
+
pub color2: u32, // line color 2
|
| 248 |
}
|
| 249 |
|
| 250 |
const _: () = assert!(core::mem::size_of::<DrawPolygonHeader>() == 0x14);
|
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Interpolated fixed-point trig table lookup.
|
| 2 |
+
//!
|
| 3 |
+
//! WA stores sine and cosine values as 1025-entry fixed-point 16.16 tables
|
| 4 |
+
//! at [`va::G_SIN_TABLE`] and [`va::G_COS_TABLE`]. Each table covers one full
|
| 5 |
+
//! period in 1024 steps (2π / 1024 radians per step); the 1025th entry is a
|
| 6 |
+
//! sentinel that allows interpolation at the wraparound.
|
| 7 |
+
//!
|
| 8 |
+
//! The lookup angle is a 16-bit quantity where the full range 0..0xFFFF
|
| 9 |
+
//! maps to one full period. The upper 10 bits select the table entry and
|
| 10 |
+
//! the lower 6 bits are used for linear interpolation between adjacent
|
| 11 |
+
//! entries.
|
| 12 |
+
|
| 13 |
+
use crate::fixed::Fixed;
|
| 14 |
+
use crate::rebase::rb;
|
| 15 |
+
|
| 16 |
+
/// Interpolated lookup from a 1025-entry fixed-point trig table.
|
| 17 |
+
///
|
| 18 |
+
/// `table` must point to at least 1025 `i32` values (the standard WA
|
| 19 |
+
/// sine/cosine layout). `angle` is a 16-bit angle where the full range
|
| 20 |
+
/// 0..0xFFFF maps to one full period.
|
| 21 |
+
///
|
| 22 |
+
/// Returns a Fixed 16.16 value interpolated between the two nearest
|
| 23 |
+
/// table entries.
|
| 24 |
+
#[inline]
|
| 25 |
+
pub unsafe fn trig_lookup(table: *const i32, angle: u32) -> Fixed {
|
| 26 |
+
let index = ((angle as i32) >> 6) as usize & 0x3FF;
|
| 27 |
+
let frac = Fixed::from_raw(((angle & 0x3F) << 10) as i32);
|
| 28 |
+
let base = Fixed::from_raw(*table.add(index));
|
| 29 |
+
let next = Fixed::from_raw(*table.add(index + 1));
|
| 30 |
+
(next - base).mul_raw(frac) + base
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
/// Sine lookup from WA's global sine table.
|
| 34 |
+
///
|
| 35 |
+
/// # Safety
|
| 36 |
+
///
|
| 37 |
+
/// Requires ASLR rebase to have been computed (i.e. the DLL is loaded).
|
| 38 |
+
#[inline]
|
| 39 |
+
pub unsafe fn sin_lookup(angle: u32) -> Fixed {
|
| 40 |
+
trig_lookup(rb(crate::address::va::G_SIN_TABLE) as *const i32, angle)
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/// Cosine lookup from WA's global cosine table.
|
| 44 |
+
///
|
| 45 |
+
/// # Safety
|
| 46 |
+
///
|
| 47 |
+
/// Requires ASLR rebase to have been computed (i.e. the DLL is loaded).
|
| 48 |
+
#[inline]
|
| 49 |
+
pub unsafe fn cos_lookup(angle: u32) -> Fixed {
|
| 50 |
+
trig_lookup(rb(crate::address::va::G_COS_TABLE) as *const i32, angle)
|
| 51 |
+
}
|
|
@@ -827,7 +827,7 @@ unsafe extern "cdecl" fn wa_blit_sprite_rect(
|
|
| 827 |
/// Trampoline to the original WA blit function (for unsupported modes).
|
| 828 |
static ORIG_BLIT: AtomicU32 = AtomicU32::new(0);
|
| 829 |
|
| 830 |
-
/// Naked trampoline: captures ESI (dst BitGrid) and forwards to cdecl
|
| 831 |
///
|
| 832 |
/// Original calling convention: ESI=dst, 9 stdcall params, RET 0x24.
|
| 833 |
#[unsafe(naked)]
|
|
@@ -850,18 +850,15 @@ unsafe extern "C" fn blit_hook_trampoline() {
|
|
| 850 |
"mov esp, ebp",
|
| 851 |
"pop ebp",
|
| 852 |
"ret 0x24",
|
| 853 |
-
impl_fn = sym
|
| 854 |
);
|
| 855 |
}
|
| 856 |
|
| 857 |
-
///
|
| 858 |
///
|
| 859 |
-
///
|
| 860 |
-
///
|
| 861 |
-
|
| 862 |
-
/// - Generic per-pixel fallback: handles all remaining cases (unaligned 1-bit, modes 4-5,
|
| 863 |
-
/// mixed cpp, etc.) via `blit_generic_perpixel`
|
| 864 |
-
pub(crate) unsafe extern "cdecl" fn blit_impl(
|
| 865 |
dst: *mut openwa_core::bitgrid::DisplayBitGrid,
|
| 866 |
dst_x: i32,
|
| 867 |
dst_y: i32,
|
|
@@ -873,368 +870,18 @@ pub(crate) unsafe extern "cdecl" fn blit_impl(
|
|
| 873 |
color_table: *const u8,
|
| 874 |
flags: u32,
|
| 875 |
) -> u32 {
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
}
|
| 879 |
-
|
| 880 |
-
let blend_mode = flags & 0xFFFF;
|
| 881 |
-
let src_cpp = (*src).cells_per_unit;
|
| 882 |
-
let dst_cpp = (*dst).cells_per_unit;
|
| 883 |
-
|
| 884 |
-
// Fast path: 8bpp surfaces with blend modes 0-3
|
| 885 |
-
if dst_cpp == 8 && src_cpp == 8 && blend_mode <= 3 {
|
| 886 |
-
return blit_8bpp(
|
| 887 |
-
dst,
|
| 888 |
-
dst_x,
|
| 889 |
-
dst_y,
|
| 890 |
-
width,
|
| 891 |
-
height,
|
| 892 |
-
src,
|
| 893 |
-
src_x,
|
| 894 |
-
src_y,
|
| 895 |
-
color_table,
|
| 896 |
-
flags,
|
| 897 |
-
);
|
| 898 |
-
}
|
| 899 |
-
|
| 900 |
-
// --- Clipping (shared by 1-bit fast path and generic fallback) ---
|
| 901 |
-
// This mirrors the clipping logic at the top of BitGrid__BlitSpriteRect.
|
| 902 |
-
let dst_right = dst_x + width;
|
| 903 |
-
let dst_bottom = dst_y + height;
|
| 904 |
-
|
| 905 |
-
let clip_left = (*dst).clip_left as i32;
|
| 906 |
-
let clip_top = (*dst).clip_top as i32;
|
| 907 |
-
let clip_right = (*dst).clip_right as i32;
|
| 908 |
-
let clip_bottom = (*dst).clip_bottom as i32;
|
| 909 |
-
|
| 910 |
-
// Early-out: completely outside clip rect
|
| 911 |
-
if dst_x >= clip_right
|
| 912 |
-
|| dst_right <= clip_left
|
| 913 |
-
|| dst_y >= clip_bottom
|
| 914 |
-
|| dst_bottom <= clip_top
|
| 915 |
-
{
|
| 916 |
-
return 0;
|
| 917 |
-
}
|
| 918 |
-
|
| 919 |
-
// Clamp visible region to clip rect
|
| 920 |
-
let vis_left = dst_x.max(clip_left);
|
| 921 |
-
let vis_right = dst_right.min(clip_right);
|
| 922 |
-
let vis_top = dst_y.max(clip_top);
|
| 923 |
-
let vis_bottom = dst_bottom.min(clip_bottom);
|
| 924 |
-
|
| 925 |
-
if vis_left >= vis_right || vis_top >= vis_bottom {
|
| 926 |
-
return 0;
|
| 927 |
-
}
|
| 928 |
-
|
| 929 |
-
// Adjust source coordinates for clipping and orientation.
|
| 930 |
-
// The orientation switch in the original adjusts src_x/src_y before the cpp check.
|
| 931 |
-
let orientation_code = (flags >> 16) & 0xFFFF;
|
| 932 |
-
let (adj_src_x, adj_src_y) = adjust_src_for_orientation(
|
| 933 |
-
src_x,
|
| 934 |
-
src_y,
|
| 935 |
-
dst_x,
|
| 936 |
-
dst_y,
|
| 937 |
-
width,
|
| 938 |
-
height,
|
| 939 |
-
vis_left,
|
| 940 |
-
vis_top,
|
| 941 |
-
vis_right,
|
| 942 |
-
vis_bottom,
|
| 943 |
-
orientation_code,
|
| 944 |
-
);
|
| 945 |
-
|
| 946 |
-
// Build slices from raw BitGrid data pointers
|
| 947 |
-
let dst_data =
|
| 948 |
-
core::slice::from_raw_parts_mut((*dst).data, ((*dst).row_stride * (*dst).height) as usize);
|
| 949 |
-
let src_data =
|
| 950 |
-
core::slice::from_raw_parts((*src).data, ((*src).row_stride * (*src).height) as usize);
|
| 951 |
-
|
| 952 |
-
// 1-bit byte-aligned fast path
|
| 953 |
-
if dst_cpp == 1 && src_cpp == 1 {
|
| 954 |
-
let left_aligned = (vis_left & 7) == 0;
|
| 955 |
-
let right_aligned = (vis_right & 7) == 0;
|
| 956 |
-
let src_aligned = (adj_src_x & 7) == 0;
|
| 957 |
-
|
| 958 |
-
if left_aligned && right_aligned && src_aligned && color_table.is_null() && flags < 4 {
|
| 959 |
-
openwa_core::render::display::sprite_blit::blit_1bit_aligned(
|
| 960 |
-
dst_data,
|
| 961 |
-
(*dst).row_stride,
|
| 962 |
-
src_data,
|
| 963 |
-
(*src).row_stride,
|
| 964 |
-
vis_left,
|
| 965 |
-
vis_top,
|
| 966 |
-
vis_right,
|
| 967 |
-
vis_bottom,
|
| 968 |
-
adj_src_x,
|
| 969 |
-
adj_src_y,
|
| 970 |
-
blend_mode,
|
| 971 |
-
);
|
| 972 |
-
return 1;
|
| 973 |
-
}
|
| 974 |
-
}
|
| 975 |
-
|
| 976 |
-
// Convert color_table pointer to typed reference for the generic path
|
| 977 |
-
let color_table_ref: Option<&[u8; 256]> = if !color_table.is_null() {
|
| 978 |
-
Some(&*(color_table as *const [u8; 256]))
|
| 979 |
-
} else {
|
| 980 |
-
None
|
| 981 |
-
};
|
| 982 |
-
|
| 983 |
-
// Generic per-pixel fallback — handles all remaining cases
|
| 984 |
-
openwa_core::render::display::sprite_blit::blit_generic_perpixel(
|
| 985 |
-
dst_data,
|
| 986 |
-
(*dst).row_stride,
|
| 987 |
-
dst_cpp,
|
| 988 |
-
src_data,
|
| 989 |
-
(*src).row_stride,
|
| 990 |
-
src_cpp,
|
| 991 |
-
vis_left,
|
| 992 |
-
vis_top,
|
| 993 |
-
vis_right,
|
| 994 |
-
vis_bottom,
|
| 995 |
-
adj_src_x,
|
| 996 |
-
adj_src_y,
|
| 997 |
-
color_table_ref,
|
| 998 |
-
blend_mode,
|
| 999 |
-
)
|
| 1000 |
-
}
|
| 1001 |
-
|
| 1002 |
-
/// Adjust source coordinates for clipping and orientation.
|
| 1003 |
-
///
|
| 1004 |
-
/// Port of the orientation switch in BitGrid__BlitSpriteRect (0x4F69D3..0x4F6A9D).
|
| 1005 |
-
/// Maps the 16 orientation codes (0-15) to 8 unique source adjustments.
|
| 1006 |
-
fn adjust_src_for_orientation(
|
| 1007 |
-
src_x: i32,
|
| 1008 |
-
src_y: i32,
|
| 1009 |
-
dst_x: i32,
|
| 1010 |
-
dst_y: i32,
|
| 1011 |
-
width: i32,
|
| 1012 |
-
height: i32,
|
| 1013 |
-
vis_left: i32,
|
| 1014 |
-
vis_top: i32,
|
| 1015 |
-
vis_right: i32,
|
| 1016 |
-
vis_bottom: i32,
|
| 1017 |
-
orientation: u32,
|
| 1018 |
-
) -> (i32, i32) {
|
| 1019 |
-
match orientation {
|
| 1020 |
-
1 | 10 => {
|
| 1021 |
-
// MirrorX: src_x from right side, src_y from top
|
| 1022 |
-
let sx = src_x + (dst_x - vis_right) + width;
|
| 1023 |
-
let sy = src_y + (vis_top - dst_y);
|
| 1024 |
-
(sx, sy)
|
| 1025 |
-
}
|
| 1026 |
-
2 | 9 => {
|
| 1027 |
-
// MirrorY: src_x from left, src_y from bottom
|
| 1028 |
-
let sx = src_x + (vis_left - dst_x);
|
| 1029 |
-
let sy = src_y + (dst_y - vis_bottom) + height;
|
| 1030 |
-
(sx, sy)
|
| 1031 |
-
}
|
| 1032 |
-
3 | 8 => {
|
| 1033 |
-
// MirrorXY: src_x from right, src_y from bottom
|
| 1034 |
-
let sx = src_x + (dst_x - vis_right) + width;
|
| 1035 |
-
let sy = src_y + (dst_y - vis_bottom) + height;
|
| 1036 |
-
(sx, sy)
|
| 1037 |
-
}
|
| 1038 |
-
4 | 15 => {
|
| 1039 |
-
// Rotate90: src_x tracks dst_y from bottom, src_y tracks dst_x from left
|
| 1040 |
-
let sx = src_x + (dst_y - vis_bottom) + height;
|
| 1041 |
-
let sy = src_y + (vis_left - dst_x);
|
| 1042 |
-
(sx, sy)
|
| 1043 |
-
}
|
| 1044 |
-
5 | 14 => {
|
| 1045 |
-
// Rotate90+MirrorX
|
| 1046 |
-
let sx = src_x + (dst_y - vis_bottom) + height;
|
| 1047 |
-
let sy = src_y + (dst_x - vis_right) + width;
|
| 1048 |
-
(sx, sy)
|
| 1049 |
-
}
|
| 1050 |
-
6 | 13 => {
|
| 1051 |
-
// Rotate90+MirrorY
|
| 1052 |
-
let sx = src_x + (vis_top - dst_y);
|
| 1053 |
-
let sy = src_y + (vis_left - dst_x);
|
| 1054 |
-
(sx, sy)
|
| 1055 |
-
}
|
| 1056 |
-
7 | 12 => {
|
| 1057 |
-
// Rotate90+MirrorXY
|
| 1058 |
-
let sx = src_x + (vis_top - dst_y);
|
| 1059 |
-
let sy = src_y + (dst_x - vis_right) + width;
|
| 1060 |
-
(sx, sy)
|
| 1061 |
-
}
|
| 1062 |
-
_ => {
|
| 1063 |
-
// Normal (0, 11, or any default)
|
| 1064 |
-
let sx = src_x + (vis_left - dst_x);
|
| 1065 |
-
let sy = src_y + (vis_top - dst_y);
|
| 1066 |
-
(sx, sy)
|
| 1067 |
-
}
|
| 1068 |
-
}
|
| 1069 |
-
}
|
| 1070 |
-
|
| 1071 |
-
/// 8bpp blit path — extracted from the original blit_impl for clarity.
|
| 1072 |
-
///
|
| 1073 |
-
/// Handles blend modes 0-3 on 8bpp surfaces via PixelGrid wrapper.
|
| 1074 |
-
unsafe fn blit_8bpp(
|
| 1075 |
-
dst: *mut openwa_core::bitgrid::DisplayBitGrid,
|
| 1076 |
-
dst_x: i32,
|
| 1077 |
-
dst_y: i32,
|
| 1078 |
-
width: i32,
|
| 1079 |
-
height: i32,
|
| 1080 |
-
src: *mut openwa_core::bitgrid::DisplayBitGrid,
|
| 1081 |
-
src_x: i32,
|
| 1082 |
-
src_y: i32,
|
| 1083 |
-
color_table: *const u8,
|
| 1084 |
-
flags: u32,
|
| 1085 |
-
) -> u32 {
|
| 1086 |
-
use openwa_core::render::display::sprite_blit::{
|
| 1087 |
-
blit_sprite_rect, BlitBlend, BlitOrientation, BlitSource,
|
| 1088 |
-
};
|
| 1089 |
-
|
| 1090 |
-
let color_table_ref: Option<&[u8; 256]> = if !color_table.is_null() {
|
| 1091 |
-
Some(&*(color_table as *const [u8; 256]))
|
| 1092 |
-
} else {
|
| 1093 |
-
None
|
| 1094 |
-
};
|
| 1095 |
-
|
| 1096 |
-
let orientation = BlitOrientation::from_flags(flags);
|
| 1097 |
-
let blend = BlitBlend::from_flags(flags);
|
| 1098 |
-
|
| 1099 |
-
let src_data =
|
| 1100 |
-
core::slice::from_raw_parts((*src).data, ((*src).row_stride * (*src).height) as usize);
|
| 1101 |
-
let blit_src = BlitSource {
|
| 1102 |
-
data: src_data,
|
| 1103 |
-
width: (*src).width,
|
| 1104 |
-
height: (*src).height,
|
| 1105 |
-
row_stride: (*src).row_stride,
|
| 1106 |
-
};
|
| 1107 |
-
|
| 1108 |
-
let dst_data_len = ((*dst).row_stride * (*dst).height) as usize;
|
| 1109 |
-
let dst_grid = openwa_core::render::display::line_draw::PixelGridMut {
|
| 1110 |
-
data: core::slice::from_raw_parts_mut((*dst).data, dst_data_len),
|
| 1111 |
-
width: (*dst).width,
|
| 1112 |
-
height: (*dst).height,
|
| 1113 |
-
row_stride: (*dst).row_stride,
|
| 1114 |
-
clip_left: (*dst).clip_left,
|
| 1115 |
-
clip_top: (*dst).clip_top,
|
| 1116 |
-
clip_right: (*dst).clip_right,
|
| 1117 |
-
clip_bottom: (*dst).clip_bottom,
|
| 1118 |
-
};
|
| 1119 |
-
|
| 1120 |
-
blit_sprite_rect(
|
| 1121 |
-
dst_grid,
|
| 1122 |
-
&blit_src,
|
| 1123 |
-
dst_x,
|
| 1124 |
-
dst_y,
|
| 1125 |
-
width,
|
| 1126 |
-
height,
|
| 1127 |
-
src_x,
|
| 1128 |
-
src_y,
|
| 1129 |
-
color_table_ref,
|
| 1130 |
-
orientation,
|
| 1131 |
-
blend,
|
| 1132 |
-
) as u32
|
| 1133 |
-
}
|
| 1134 |
-
|
| 1135 |
-
/// Stippled blit on raw BitGrid pointers.
|
| 1136 |
-
///
|
| 1137 |
-
/// Wraps the raw pointers into PixelGrid/BlitSource and calls
|
| 1138 |
-
/// `sprite_blit::blit_stippled`.
|
| 1139 |
-
pub(crate) unsafe fn blit_stippled_raw(
|
| 1140 |
-
dst: *mut openwa_core::bitgrid::DisplayBitGrid,
|
| 1141 |
-
src: *mut openwa_core::bitgrid::DisplayBitGrid,
|
| 1142 |
-
dst_x: i32,
|
| 1143 |
-
dst_y: i32,
|
| 1144 |
-
width: i32,
|
| 1145 |
-
height: i32,
|
| 1146 |
-
src_x: i32,
|
| 1147 |
-
src_y: i32,
|
| 1148 |
-
stipple_mode: u32,
|
| 1149 |
-
parity: u32,
|
| 1150 |
-
) {
|
| 1151 |
-
use openwa_core::render::display::sprite_blit::{blit_stippled, BlitSource};
|
| 1152 |
-
|
| 1153 |
-
let src_data =
|
| 1154 |
-
core::slice::from_raw_parts((*src).data, ((*src).row_stride * (*src).height) as usize);
|
| 1155 |
-
let blit_src = BlitSource {
|
| 1156 |
-
data: src_data,
|
| 1157 |
-
width: (*src).width,
|
| 1158 |
-
height: (*src).height,
|
| 1159 |
-
row_stride: (*src).row_stride,
|
| 1160 |
-
};
|
| 1161 |
-
|
| 1162 |
-
let dst_data_len = ((*dst).row_stride * (*dst).height) as usize;
|
| 1163 |
-
let mut dst_grid = openwa_core::render::display::line_draw::PixelGridMut {
|
| 1164 |
-
data: core::slice::from_raw_parts_mut((*dst).data, dst_data_len),
|
| 1165 |
-
width: (*dst).width,
|
| 1166 |
-
height: (*dst).height,
|
| 1167 |
-
row_stride: (*dst).row_stride,
|
| 1168 |
-
clip_left: (*dst).clip_left,
|
| 1169 |
-
clip_top: (*dst).clip_top,
|
| 1170 |
-
clip_right: (*dst).clip_right,
|
| 1171 |
-
clip_bottom: (*dst).clip_bottom,
|
| 1172 |
-
};
|
| 1173 |
-
|
| 1174 |
-
blit_stippled(
|
| 1175 |
-
&mut dst_grid,
|
| 1176 |
-
&blit_src,
|
| 1177 |
dst_x,
|
| 1178 |
dst_y,
|
| 1179 |
width,
|
| 1180 |
height,
|
|
|
|
| 1181 |
src_x,
|
| 1182 |
src_y,
|
| 1183 |
-
|
| 1184 |
-
parity,
|
| 1185 |
-
);
|
| 1186 |
-
}
|
| 1187 |
-
|
| 1188 |
-
/// Tiled blit on raw BitGrid pointers.
|
| 1189 |
-
///
|
| 1190 |
-
/// Wraps the raw pointers into PixelGrid/BlitSource and calls
|
| 1191 |
-
/// `sprite_blit::blit_tiled`.
|
| 1192 |
-
pub(crate) unsafe fn blit_tiled_raw(
|
| 1193 |
-
dst: *mut openwa_core::bitgrid::DisplayBitGrid,
|
| 1194 |
-
src: *mut openwa_core::bitgrid::DisplayBitGrid,
|
| 1195 |
-
initial_x: i32,
|
| 1196 |
-
dst_y: i32,
|
| 1197 |
-
tile_width: i32,
|
| 1198 |
-
tile_height: i32,
|
| 1199 |
-
clip_left: i32,
|
| 1200 |
-
clip_right: i32,
|
| 1201 |
-
flags: u32,
|
| 1202 |
-
) {
|
| 1203 |
-
use openwa_core::render::display::sprite_blit::{blit_tiled, BlitSource};
|
| 1204 |
-
|
| 1205 |
-
let src_data =
|
| 1206 |
-
core::slice::from_raw_parts((*src).data, ((*src).row_stride * (*src).height) as usize);
|
| 1207 |
-
let blit_src = BlitSource {
|
| 1208 |
-
data: src_data,
|
| 1209 |
-
width: (*src).width,
|
| 1210 |
-
height: (*src).height,
|
| 1211 |
-
row_stride: (*src).row_stride,
|
| 1212 |
-
};
|
| 1213 |
-
|
| 1214 |
-
let dst_data_len = ((*dst).row_stride * (*dst).height) as usize;
|
| 1215 |
-
let mut dst_grid = openwa_core::render::display::line_draw::PixelGridMut {
|
| 1216 |
-
data: core::slice::from_raw_parts_mut((*dst).data, dst_data_len),
|
| 1217 |
-
width: (*dst).width,
|
| 1218 |
-
height: (*dst).height,
|
| 1219 |
-
row_stride: (*dst).row_stride,
|
| 1220 |
-
clip_left: (*dst).clip_left,
|
| 1221 |
-
clip_top: (*dst).clip_top,
|
| 1222 |
-
clip_right: (*dst).clip_right,
|
| 1223 |
-
clip_bottom: (*dst).clip_bottom,
|
| 1224 |
-
};
|
| 1225 |
-
|
| 1226 |
-
blit_tiled(
|
| 1227 |
-
&mut dst_grid,
|
| 1228 |
-
&blit_src,
|
| 1229 |
-
initial_x,
|
| 1230 |
-
dst_y,
|
| 1231 |
-
tile_width,
|
| 1232 |
-
tile_height,
|
| 1233 |
-
clip_left,
|
| 1234 |
-
clip_right,
|
| 1235 |
-
None, // color_table — tiled mode doesn't use one
|
| 1236 |
flags,
|
| 1237 |
-
)
|
| 1238 |
}
|
| 1239 |
|
| 1240 |
/// Call the original WA blit function via trampoline.
|
|
|
|
| 827 |
/// Trampoline to the original WA blit function (for unsupported modes).
|
| 828 |
static ORIG_BLIT: AtomicU32 = AtomicU32::new(0);
|
| 829 |
|
| 830 |
+
/// Naked trampoline: captures ESI (dst BitGrid) and forwards to cdecl wrapper.
|
| 831 |
///
|
| 832 |
/// Original calling convention: ESI=dst, 9 stdcall params, RET 0x24.
|
| 833 |
#[unsafe(naked)]
|
|
|
|
| 850 |
"mov esp, ebp",
|
| 851 |
"pop ebp",
|
| 852 |
"ret 0x24",
|
| 853 |
+
impl_fn = sym blit_impl_cdecl,
|
| 854 |
);
|
| 855 |
}
|
| 856 |
|
| 857 |
+
/// Thin cdecl wrapper around `openwa_core::bitgrid::blit::blit_impl`.
|
| 858 |
///
|
| 859 |
+
/// The naked asm trampoline calls this with cdecl convention; this simply
|
| 860 |
+
/// forwards to the core implementation which is a plain `unsafe fn`.
|
| 861 |
+
unsafe extern "cdecl" fn blit_impl_cdecl(
|
|
|
|
|
|
|
|
|
|
| 862 |
dst: *mut openwa_core::bitgrid::DisplayBitGrid,
|
| 863 |
dst_x: i32,
|
| 864 |
dst_y: i32,
|
|
|
|
| 870 |
color_table: *const u8,
|
| 871 |
flags: u32,
|
| 872 |
) -> u32 {
|
| 873 |
+
openwa_core::bitgrid::blit::blit_impl(
|
| 874 |
+
dst,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 875 |
dst_x,
|
| 876 |
dst_y,
|
| 877 |
width,
|
| 878 |
height,
|
| 879 |
+
src,
|
| 880 |
src_x,
|
| 881 |
src_y,
|
| 882 |
+
color_table,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 883 |
flags,
|
| 884 |
+
)
|
| 885 |
}
|
| 886 |
|
| 887 |
/// Call the original WA blit function via trampoline.
|
|
@@ -1,1463 +0,0 @@
|
|
| 1 |
-
//! Render subsystem hooks — RenderQueue enqueue + DisplayGfx vtable patches.
|
| 2 |
-
//!
|
| 3 |
-
//! Combines two subsystems:
|
| 4 |
-
//! - RenderQueue enqueue hooks (full Rust replacements for command enqueueing)
|
| 5 |
-
//! - DisplayGfx vtable patches (DisplayBase pure-call stubs, headless destructor,
|
| 6 |
-
//! and ported DisplayGfx methods)
|
| 7 |
-
|
| 8 |
-
use core::ffi::c_char;
|
| 9 |
-
|
| 10 |
-
use openwa_core::address::va;
|
| 11 |
-
use openwa_core::rebase::rb;
|
| 12 |
-
use openwa_core::render::queue::*;
|
| 13 |
-
use openwa_core::render::sprite::gfx_dir::GfxDir;
|
| 14 |
-
use openwa_core::render::SpriteOp;
|
| 15 |
-
use openwa_core::task::{BungeeTrailTask, WeaponAimTask};
|
| 16 |
-
|
| 17 |
-
use crate::hook::{self, usercall_trampoline};
|
| 18 |
-
use crate::log_line;
|
| 19 |
-
|
| 20 |
-
// ==========================================================================
|
| 21 |
-
// RenderQueue enqueue hooks
|
| 22 |
-
// ==========================================================================
|
| 23 |
-
//
|
| 24 |
-
// All functions enqueue commands to the RenderQueue's downward-growing buffer.
|
| 25 |
-
// Calling conventions are __usercall variants with register + stack params.
|
| 26 |
-
|
| 27 |
-
// ---------------------------------------------------------------------------
|
| 28 |
-
// EnqueueTiledBitmap (0x541D60) — type 0xD, EAX=this, 3 stack, RET 0xC
|
| 29 |
-
//
|
| 30 |
-
// Mis-labelled `RQ_DrawPixel` in earlier reverse-engineering passes. It does
|
| 31 |
-
// NOT enqueue a single-pixel draw — it enqueues a tile-cached bitmap draw
|
| 32 |
-
// dispatched by `RenderDrawingQueue` case 0xD into vtable slot 11.
|
| 33 |
-
// ---------------------------------------------------------------------------
|
| 34 |
-
|
| 35 |
-
usercall_trampoline!(fn trampoline_enqueue_tiled_bitmap; impl_fn = enqueue_tiled_bitmap_impl;
|
| 36 |
-
reg = eax; stack_params = 3; ret_bytes = "0xC");
|
| 37 |
-
|
| 38 |
-
unsafe extern "cdecl" fn enqueue_tiled_bitmap_impl(
|
| 39 |
-
this: u32,
|
| 40 |
-
y_fixed16: u32,
|
| 41 |
-
source_descriptor: u32,
|
| 42 |
-
flags: u32,
|
| 43 |
-
) {
|
| 44 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 45 |
-
|
| 46 |
-
if let Some(entry) = q.alloc::<DrawTiledBitmapCmd>() {
|
| 47 |
-
*entry = DrawTiledBitmapCmd {
|
| 48 |
-
command_type: command_type::DRAW_TILED_BITMAP,
|
| 49 |
-
layer: 0x1B_0000,
|
| 50 |
-
x_fixed16: 0xFF00_0000,
|
| 51 |
-
y_fixed16,
|
| 52 |
-
source_descriptor,
|
| 53 |
-
flags: flags as u8,
|
| 54 |
-
_pad: [0; 3],
|
| 55 |
-
};
|
| 56 |
-
}
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
// ---------------------------------------------------------------------------
|
| 60 |
-
// DrawLineStrip (0x541DD0) — type 8, EAX=this, EDI=count, 2 stack, RET 0x8
|
| 61 |
-
// Allocation: count * 0xC + 0x1C (variable size)
|
| 62 |
-
// ---------------------------------------------------------------------------
|
| 63 |
-
|
| 64 |
-
usercall_trampoline!(fn trampoline_draw_line_strip; impl_fn = draw_line_strip_impl;
|
| 65 |
-
regs = [eax, edi]; stack_params = 2; ret_bytes = "0x8");
|
| 66 |
-
|
| 67 |
-
unsafe extern "cdecl" fn draw_line_strip_impl(
|
| 68 |
-
this: u32,
|
| 69 |
-
count: u32,
|
| 70 |
-
vertex_ptr: u32,
|
| 71 |
-
param_1: u32,
|
| 72 |
-
) {
|
| 73 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 74 |
-
let total_size = count as usize * 0xC + 0x1C;
|
| 75 |
-
|
| 76 |
-
if let Some(ptr) = q.alloc_raw(total_size) {
|
| 77 |
-
let header = &mut *(ptr as *mut DrawLineStripHeader);
|
| 78 |
-
*header = DrawLineStripHeader {
|
| 79 |
-
command_type: command_type::DRAW_LINE_STRIP,
|
| 80 |
-
layer: 0xE_0000,
|
| 81 |
-
count,
|
| 82 |
-
param_1,
|
| 83 |
-
};
|
| 84 |
-
core::ptr::copy_nonoverlapping(
|
| 85 |
-
vertex_ptr as *const u8,
|
| 86 |
-
ptr.add(core::mem::size_of::<DrawLineStripHeader>()),
|
| 87 |
-
count as usize * 0xC,
|
| 88 |
-
);
|
| 89 |
-
}
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
// ---------------------------------------------------------------------------
|
| 93 |
-
// DrawPolygon (0x541E50) — type 9, ECX=this, ESI=count, 3 stack, RET 0xC
|
| 94 |
-
// Allocation: count * 0xC + 0x20 (variable size)
|
| 95 |
-
// ---------------------------------------------------------------------------
|
| 96 |
-
|
| 97 |
-
usercall_trampoline!(fn trampoline_draw_polygon; impl_fn = draw_polygon_impl;
|
| 98 |
-
regs = [ecx, esi]; stack_params = 3; ret_bytes = "0xC");
|
| 99 |
-
|
| 100 |
-
unsafe extern "cdecl" fn draw_polygon_impl(
|
| 101 |
-
this: u32,
|
| 102 |
-
count: u32,
|
| 103 |
-
vertex_ptr: u32,
|
| 104 |
-
param_1: u32,
|
| 105 |
-
param_2: u32,
|
| 106 |
-
) {
|
| 107 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 108 |
-
let total_size = count as usize * 0xC + 0x20;
|
| 109 |
-
|
| 110 |
-
if let Some(ptr) = q.alloc_raw(total_size) {
|
| 111 |
-
let header = &mut *(ptr as *mut DrawPolygonHeader);
|
| 112 |
-
*header = DrawPolygonHeader {
|
| 113 |
-
command_type: command_type::DRAW_POLYGON,
|
| 114 |
-
layer: 0xE_0000,
|
| 115 |
-
count,
|
| 116 |
-
param_1,
|
| 117 |
-
param_2,
|
| 118 |
-
};
|
| 119 |
-
core::ptr::copy_nonoverlapping(
|
| 120 |
-
vertex_ptr as *const u8,
|
| 121 |
-
ptr.add(core::mem::size_of::<DrawPolygonHeader>()),
|
| 122 |
-
count as usize * 0xC,
|
| 123 |
-
);
|
| 124 |
-
}
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
// ---------------------------------------------------------------------------
|
| 128 |
-
// DrawCrosshair (0x541ED0) — type 0xB, ECX=this, 5 stack, RET 0x14
|
| 129 |
-
// Enqueues a crosshair draw command. Dispatched by RenderDrawingQueue
|
| 130 |
-
// case 0xB → DisplayGfx::draw_crosshair (vtable slot 16).
|
| 131 |
-
// ---------------------------------------------------------------------------
|
| 132 |
-
|
| 133 |
-
usercall_trampoline!(fn trampoline_draw_crosshair; impl_fn = draw_crosshair_impl;
|
| 134 |
-
reg = ecx; stack_params = 5; ret_bytes = "0x14");
|
| 135 |
-
|
| 136 |
-
unsafe extern "cdecl" fn draw_crosshair_impl(
|
| 137 |
-
this: u32,
|
| 138 |
-
layer: u32,
|
| 139 |
-
x_pos: u32,
|
| 140 |
-
y_pos: u32,
|
| 141 |
-
color_fg: u32,
|
| 142 |
-
color_bg: u32,
|
| 143 |
-
) {
|
| 144 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 145 |
-
|
| 146 |
-
if let Some(entry) = q.alloc::<DrawCrosshairCmd>() {
|
| 147 |
-
*entry = DrawCrosshairCmd {
|
| 148 |
-
command_type: command_type::DRAW_CROSSHAIR,
|
| 149 |
-
layer,
|
| 150 |
-
color_fg,
|
| 151 |
-
color_bg,
|
| 152 |
-
x_pos,
|
| 153 |
-
y_pos,
|
| 154 |
-
_reserved: 0,
|
| 155 |
-
};
|
| 156 |
-
}
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
// ---------------------------------------------------------------------------
|
| 160 |
-
// DrawRect (0x541F40) — type 0, ECX=this, EDX=y_clip, 6 stack, RET 0x18
|
| 161 |
-
// ---------------------------------------------------------------------------
|
| 162 |
-
|
| 163 |
-
usercall_trampoline!(fn trampoline_draw_rect; impl_fn = draw_rect_impl;
|
| 164 |
-
regs = [ecx, edx]; stack_params = 6; ret_bytes = "0x18");
|
| 165 |
-
|
| 166 |
-
unsafe extern "cdecl" fn draw_rect_impl(
|
| 167 |
-
this: u32,
|
| 168 |
-
y_clip: u32,
|
| 169 |
-
layer: u32,
|
| 170 |
-
x1: u32,
|
| 171 |
-
y1: u32,
|
| 172 |
-
x2: u32,
|
| 173 |
-
y2: u32,
|
| 174 |
-
color: u32,
|
| 175 |
-
) {
|
| 176 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 177 |
-
|
| 178 |
-
if let Some(entry) = q.alloc::<DrawRectCmd>() {
|
| 179 |
-
*entry = DrawRectCmd {
|
| 180 |
-
command_type: command_type::DRAW_RECT,
|
| 181 |
-
layer,
|
| 182 |
-
color,
|
| 183 |
-
x1: x1 & 0xFFFF0000,
|
| 184 |
-
y1: y1 & 0xFFFF0000,
|
| 185 |
-
x2: x2 & 0xFFFF0000,
|
| 186 |
-
y2: y2 & 0xFFFF0000,
|
| 187 |
-
y_clip: y_clip & 0xFFFF0000,
|
| 188 |
-
};
|
| 189 |
-
}
|
| 190 |
-
}
|
| 191 |
-
|
| 192 |
-
// ---------------------------------------------------------------------------
|
| 193 |
-
// DrawSpriteGlobal (0x541FE0) — type 4, EAX=y, ECX=this, 4 stack, RET 0x10
|
| 194 |
-
// ---------------------------------------------------------------------------
|
| 195 |
-
|
| 196 |
-
usercall_trampoline!(fn trampoline_draw_sprite_global; impl_fn = draw_sprite_global_impl;
|
| 197 |
-
regs = [eax, ecx]; stack_params = 4; ret_bytes = "0x10");
|
| 198 |
-
|
| 199 |
-
unsafe extern "cdecl" fn draw_sprite_global_impl(
|
| 200 |
-
y_pos: u32,
|
| 201 |
-
this: u32,
|
| 202 |
-
layer: u32,
|
| 203 |
-
x_pos: u32,
|
| 204 |
-
sprite_id: u32,
|
| 205 |
-
frame: u32,
|
| 206 |
-
) {
|
| 207 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 208 |
-
|
| 209 |
-
if let Some(entry) = q.alloc::<DrawSpriteCmd>() {
|
| 210 |
-
*entry = DrawSpriteCmd {
|
| 211 |
-
command_type: command_type::DRAW_SPRITE_GLOBAL,
|
| 212 |
-
layer,
|
| 213 |
-
x_pos: x_pos & 0xFFFF0000,
|
| 214 |
-
y_pos: y_pos & 0xFFFF0000,
|
| 215 |
-
sprite: SpriteOp(sprite_id),
|
| 216 |
-
palette: frame,
|
| 217 |
-
};
|
| 218 |
-
}
|
| 219 |
-
}
|
| 220 |
-
|
| 221 |
-
// ---------------------------------------------------------------------------
|
| 222 |
-
// DrawSpriteLocal (0x542060) — type 5, EAX=y, ECX=this, 4 stack, RET 0x10
|
| 223 |
-
// ---------------------------------------------------------------------------
|
| 224 |
-
|
| 225 |
-
usercall_trampoline!(fn trampoline_draw_sprite_local; impl_fn = draw_sprite_local_impl;
|
| 226 |
-
regs = [eax, ecx]; stack_params = 4; ret_bytes = "0x10");
|
| 227 |
-
|
| 228 |
-
unsafe extern "cdecl" fn draw_sprite_local_impl(
|
| 229 |
-
y_pos: u32,
|
| 230 |
-
this: u32,
|
| 231 |
-
layer: u32,
|
| 232 |
-
x_pos: u32,
|
| 233 |
-
sprite_id: u32,
|
| 234 |
-
frame: u32,
|
| 235 |
-
) {
|
| 236 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 237 |
-
|
| 238 |
-
if let Some(entry) = q.alloc::<DrawSpriteCmd>() {
|
| 239 |
-
*entry = DrawSpriteCmd {
|
| 240 |
-
command_type: command_type::DRAW_SPRITE_LOCAL,
|
| 241 |
-
layer,
|
| 242 |
-
x_pos: x_pos & 0xFFFF0000,
|
| 243 |
-
y_pos: y_pos & 0xFFFF0000,
|
| 244 |
-
sprite: SpriteOp(sprite_id),
|
| 245 |
-
palette: frame,
|
| 246 |
-
};
|
| 247 |
-
}
|
| 248 |
-
}
|
| 249 |
-
|
| 250 |
-
// ---------------------------------------------------------------------------
|
| 251 |
-
// DrawSpriteOffset (0x5420E0) — type 6, ECX=this, EDX=y_clip, 6 stack, RET 0x18
|
| 252 |
-
// ---------------------------------------------------------------------------
|
| 253 |
-
|
| 254 |
-
usercall_trampoline!(fn trampoline_draw_sprite_offset; impl_fn = draw_sprite_offset_impl;
|
| 255 |
-
regs = [ecx, edx]; stack_params = 6; ret_bytes = "0x18");
|
| 256 |
-
|
| 257 |
-
unsafe extern "cdecl" fn draw_sprite_offset_impl(
|
| 258 |
-
this: u32,
|
| 259 |
-
y_clip: u32,
|
| 260 |
-
layer: u32,
|
| 261 |
-
x_pos: u32,
|
| 262 |
-
y_pos: u32,
|
| 263 |
-
sprite_id: u32,
|
| 264 |
-
param_7: u32,
|
| 265 |
-
param_8: u32,
|
| 266 |
-
) {
|
| 267 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 268 |
-
|
| 269 |
-
if let Some(entry) = q.alloc::<DrawSpriteOffsetCmd>() {
|
| 270 |
-
*entry = DrawSpriteOffsetCmd {
|
| 271 |
-
command_type: command_type::DRAW_SPRITE_OFFSET,
|
| 272 |
-
layer,
|
| 273 |
-
sprite_id,
|
| 274 |
-
x_pos: x_pos & 0xFFFF0000,
|
| 275 |
-
y_pos: y_pos & 0xFFFF0000,
|
| 276 |
-
_reserved: 0,
|
| 277 |
-
y_clip: y_clip & 0xFFFF0000,
|
| 278 |
-
param_7,
|
| 279 |
-
param_8,
|
| 280 |
-
};
|
| 281 |
-
}
|
| 282 |
-
}
|
| 283 |
-
|
| 284 |
-
// ---------------------------------------------------------------------------
|
| 285 |
-
// DrawBitmapGlobal (0x542170) — type 1, ECX=this, EDX=y, 7 stack, RET 0x1C
|
| 286 |
-
// ---------------------------------------------------------------------------
|
| 287 |
-
|
| 288 |
-
usercall_trampoline!(fn trampoline_draw_bitmap_global; impl_fn = draw_bitmap_global_impl;
|
| 289 |
-
regs = [ecx, edx]; stack_params = 7; ret_bytes = "0x1C");
|
| 290 |
-
|
| 291 |
-
unsafe extern "cdecl" fn draw_bitmap_global_impl(
|
| 292 |
-
this: u32,
|
| 293 |
-
y_pos: u32,
|
| 294 |
-
layer: u32,
|
| 295 |
-
x_pos: u32,
|
| 296 |
-
bitmap_ptr: u32,
|
| 297 |
-
param_6: u32,
|
| 298 |
-
param_7: u32,
|
| 299 |
-
param_8: u32,
|
| 300 |
-
param_9: u32,
|
| 301 |
-
) {
|
| 302 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 303 |
-
|
| 304 |
-
if let Some(entry) = q.alloc::<DrawBitmapGlobalCmd>() {
|
| 305 |
-
*entry = DrawBitmapGlobalCmd {
|
| 306 |
-
command_type: command_type::DRAW_BITMAP_GLOBAL,
|
| 307 |
-
layer,
|
| 308 |
-
x_pos: x_pos & 0xFFFF0000,
|
| 309 |
-
y_pos: y_pos & 0xFFFF0000,
|
| 310 |
-
bitmap_ptr,
|
| 311 |
-
_reserved: 0,
|
| 312 |
-
param_6,
|
| 313 |
-
param_7,
|
| 314 |
-
param_8,
|
| 315 |
-
param_9,
|
| 316 |
-
};
|
| 317 |
-
}
|
| 318 |
-
}
|
| 319 |
-
|
| 320 |
-
// ---------------------------------------------------------------------------
|
| 321 |
-
// DrawTextboxLocal (0x542200) — type 2, ECX=this, EDX=y, 6 stack, RET 0x18
|
| 322 |
-
// ---------------------------------------------------------------------------
|
| 323 |
-
|
| 324 |
-
usercall_trampoline!(fn trampoline_draw_textbox_local; impl_fn = draw_textbox_local_impl;
|
| 325 |
-
regs = [ecx, edx]; stack_params = 6; ret_bytes = "0x18");
|
| 326 |
-
|
| 327 |
-
unsafe extern "cdecl" fn draw_textbox_local_impl(
|
| 328 |
-
this: u32,
|
| 329 |
-
y_pos: u32,
|
| 330 |
-
layer: u32,
|
| 331 |
-
x_pos: u32,
|
| 332 |
-
text_ptr: u32,
|
| 333 |
-
param_6: u32,
|
| 334 |
-
param_7: u32,
|
| 335 |
-
param_8: u32,
|
| 336 |
-
) {
|
| 337 |
-
let q = &mut *(this as *mut RenderQueue);
|
| 338 |
-
|
| 339 |
-
if let Some(entry) = q.alloc::<DrawTextboxLocalCmd>() {
|
| 340 |
-
*entry = DrawTextboxLocalCmd {
|
| 341 |
-
command_type: command_type::DRAW_TEXTBOX_LOCAL,
|
| 342 |
-
layer,
|
| 343 |
-
_reserved_2: 0,
|
| 344 |
-
x_pos: x_pos & 0xFFFF0000,
|
| 345 |
-
y_pos: y_pos & 0xFFFF0000,
|
| 346 |
-
_reserved_5: 0,
|
| 347 |
-
_reserved_6: 0,
|
| 348 |
-
text_ptr,
|
| 349 |
-
_reserved_8: 0,
|
| 350 |
-
_reserved_9: 0,
|
| 351 |
-
param_6,
|
| 352 |
-
param_7,
|
| 353 |
-
param_8,
|
| 354 |
-
};
|
| 355 |
-
}
|
| 356 |
-
}
|
| 357 |
-
|
| 358 |
-
// ---------------------------------------------------------------------------
|
| 359 |
-
// Fixed-point trig helpers
|
| 360 |
-
// ---------------------------------------------------------------------------
|
| 361 |
-
|
| 362 |
-
/// Fixed-point 16.16 multiply: ((a * b) >> 16)
|
| 363 |
-
#[inline]
|
| 364 |
-
fn fixed_mul(a: i32, b: i32) -> i32 {
|
| 365 |
-
((a as i64 * b as i64) >> 16) as i32
|
| 366 |
-
}
|
| 367 |
-
|
| 368 |
-
/// Interpolated lookup from a 1024-entry fixed-point trig table.
|
| 369 |
-
/// Index = (angle >> 6) & 0x3FF, fraction = (angle & 0x3F) << 10.
|
| 370 |
-
#[inline]
|
| 371 |
-
unsafe fn trig_lookup(table: *const i32, angle: u32) -> i32 {
|
| 372 |
-
let index = ((angle as i32) >> 6) as usize & 0x3FF;
|
| 373 |
-
let frac = ((angle & 0x3F) << 10) as i32;
|
| 374 |
-
let base = *table.add(index);
|
| 375 |
-
let next = *table.add(index + 1);
|
| 376 |
-
fixed_mul(next - base, frac) + base
|
| 377 |
-
}
|
| 378 |
-
|
| 379 |
-
// ---------------------------------------------------------------------------
|
| 380 |
-
// DrawBungeeTrail (0x500720) — stdcall(task, style, fill), RET 0xC
|
| 381 |
-
//
|
| 382 |
-
// Draws bungee drop trajectory path:
|
| 383 |
-
// 1. Sprite at trail start position
|
| 384 |
-
// 2. Series of vertices computed by accumulating angle + trig interpolation
|
| 385 |
-
// 3. Final vertex at task position (0x84/0x88)
|
| 386 |
-
// 4. DrawPolygon (if fill != 0) or DrawLineStrip
|
| 387 |
-
// Triggered by Bungee weapon (field_0x30==4, field_0x34==7) check in FUN_00519F60.
|
| 388 |
-
// Gated by task+0xBC flag set by InitWormTrail (0x5008D0).
|
| 389 |
-
// ---------------------------------------------------------------------------
|
| 390 |
-
|
| 391 |
-
unsafe extern "stdcall" fn draw_bungee_trail_impl(task_ptr: u32, style: u32, fill: u32) {
|
| 392 |
-
let task = &*(task_ptr as *const BungeeTrailTask);
|
| 393 |
-
|
| 394 |
-
if task.trail_visible == 0 {
|
| 395 |
-
return;
|
| 396 |
-
}
|
| 397 |
-
|
| 398 |
-
let ddgame = &*task.base.ddgame;
|
| 399 |
-
let rq = &mut *ddgame.render_queue;
|
| 400 |
-
|
| 401 |
-
let seg_data = task.segment_data;
|
| 402 |
-
if seg_data.is_null() {
|
| 403 |
-
return;
|
| 404 |
-
}
|
| 405 |
-
|
| 406 |
-
let segment_count = task.segment_count;
|
| 407 |
-
if segment_count <= 0 {
|
| 408 |
-
return;
|
| 409 |
-
}
|
| 410 |
-
|
| 411 |
-
let mut x = task.trail_start_x;
|
| 412 |
-
let mut y = task.trail_start_y;
|
| 413 |
-
|
| 414 |
-
let first_angle = *(seg_data.add(4) as *const i32);
|
| 415 |
-
|
| 416 |
-
// Enqueue start sprite (command type 5 = local)
|
| 417 |
-
if let Some(entry) = rq.alloc::<DrawSpriteCmd>() {
|
| 418 |
-
*entry = DrawSpriteCmd {
|
| 419 |
-
command_type: command_type::DRAW_SPRITE_LOCAL,
|
| 420 |
-
layer: 0xDFFFF,
|
| 421 |
-
x_pos: x as u32 & 0xFFFF0000,
|
| 422 |
-
y_pos: y as u32 & 0xFFFF0000,
|
| 423 |
-
sprite: SpriteOp(0x45),
|
| 424 |
-
palette: (first_angle + 0x8100) as u32,
|
| 425 |
-
};
|
| 426 |
-
}
|
| 427 |
-
|
| 428 |
-
// Build vertex array from trail segments
|
| 429 |
-
const MAX_VERTICES: usize = 256;
|
| 430 |
-
let mut verts = [[0i32; 3]; MAX_VERTICES];
|
| 431 |
-
let mut vert_count: usize = 0;
|
| 432 |
-
let mut accumulated_angle: u32 = 0;
|
| 433 |
-
|
| 434 |
-
let sin_table = rb(va::G_SIN_TABLE) as *const i32;
|
| 435 |
-
let cos_table = rb(va::G_COS_TABLE) as *const i32;
|
| 436 |
-
|
| 437 |
-
for i in 0..segment_count {
|
| 438 |
-
let seg_angle = *(seg_data.add(4 + i as usize * 8) as *const i32);
|
| 439 |
-
|
| 440 |
-
// Include vertex if: first segment, or segment has nonzero angle, or fill mode
|
| 441 |
-
if (i == 0 || seg_angle != 0 || fill != 0) && vert_count < MAX_VERTICES {
|
| 442 |
-
verts[vert_count] = [x, y, 0];
|
| 443 |
-
vert_count += 1;
|
| 444 |
-
}
|
| 445 |
-
|
| 446 |
-
accumulated_angle = accumulated_angle.wrapping_add(seg_angle as u32);
|
| 447 |
-
|
| 448 |
-
let sin_interp = trig_lookup(sin_table, accumulated_angle);
|
| 449 |
-
let cos_interp = trig_lookup(cos_table, accumulated_angle);
|
| 450 |
-
|
| 451 |
-
x = x.wrapping_add(sin_interp.wrapping_mul(8));
|
| 452 |
-
y = y.wrapping_sub(cos_interp.wrapping_mul(8));
|
| 453 |
-
}
|
| 454 |
-
|
| 455 |
-
// Final vertex = task position (target)
|
| 456 |
-
if vert_count < MAX_VERTICES {
|
| 457 |
-
verts[vert_count] = [task.pos_x.0, task.pos_y.0, 0];
|
| 458 |
-
vert_count += 1;
|
| 459 |
-
}
|
| 460 |
-
|
| 461 |
-
// Enqueue as polygon or line strip
|
| 462 |
-
if fill != 0 {
|
| 463 |
-
let total_size = vert_count * 0xC + 0x20;
|
| 464 |
-
if let Some(ptr) = rq.alloc_raw(total_size) {
|
| 465 |
-
let header = &mut *(ptr as *mut DrawPolygonHeader);
|
| 466 |
-
*header = DrawPolygonHeader {
|
| 467 |
-
command_type: command_type::DRAW_POLYGON,
|
| 468 |
-
layer: 0xE_0000,
|
| 469 |
-
count: vert_count as u32,
|
| 470 |
-
param_1: style,
|
| 471 |
-
param_2: fill,
|
| 472 |
-
};
|
| 473 |
-
core::ptr::copy_nonoverlapping(
|
| 474 |
-
verts.as_ptr() as *const u8,
|
| 475 |
-
ptr.add(core::mem::size_of::<DrawPolygonHeader>()),
|
| 476 |
-
vert_count * 0xC,
|
| 477 |
-
);
|
| 478 |
-
}
|
| 479 |
-
} else {
|
| 480 |
-
let total_size = vert_count * 0xC + 0x1C;
|
| 481 |
-
if let Some(ptr) = rq.alloc_raw(total_size) {
|
| 482 |
-
let header = &mut *(ptr as *mut DrawLineStripHeader);
|
| 483 |
-
*header = DrawLineStripHeader {
|
| 484 |
-
command_type: command_type::DRAW_LINE_STRIP,
|
| 485 |
-
layer: 0xE_0000,
|
| 486 |
-
count: vert_count as u32,
|
| 487 |
-
param_1: style,
|
| 488 |
-
};
|
| 489 |
-
core::ptr::copy_nonoverlapping(
|
| 490 |
-
verts.as_ptr() as *const u8,
|
| 491 |
-
ptr.add(core::mem::size_of::<DrawLineStripHeader>()),
|
| 492 |
-
vert_count * 0xC,
|
| 493 |
-
);
|
| 494 |
-
}
|
| 495 |
-
}
|
| 496 |
-
}
|
| 497 |
-
|
| 498 |
-
// ---------------------------------------------------------------------------
|
| 499 |
-
// DrawCrosshairLine (0x5197D0) — usercall(EDI=task_ptr), plain RET
|
| 500 |
-
//
|
| 501 |
-
// Draws the weapon aiming crosshair line:
|
| 502 |
-
// 1. Compute direction from angle at task+0x264
|
| 503 |
-
// 2. Compute line length from DDGame scale + task offset
|
| 504 |
-
// 3. Endpoint = start + direction * length (with overflow clamping)
|
| 505 |
-
// 4. DrawPolygon (2 vertices) for the line
|
| 506 |
-
// 5. Conditionally DrawSpriteLocal at endpoint (crosshair sprite)
|
| 507 |
-
// ---------------------------------------------------------------------------
|
| 508 |
-
|
| 509 |
-
usercall_trampoline!(fn trampoline_draw_crosshair_line; impl_fn = draw_crosshair_line_impl;
|
| 510 |
-
reg = edi);
|
| 511 |
-
|
| 512 |
-
unsafe extern "cdecl" fn draw_crosshair_line_impl(task_ptr: u32) {
|
| 513 |
-
let task = &*(task_ptr as *const WeaponAimTask);
|
| 514 |
-
let gt = &task.game_task;
|
| 515 |
-
|
| 516 |
-
if task.aim_active == 0 {
|
| 517 |
-
return;
|
| 518 |
-
}
|
| 519 |
-
|
| 520 |
-
let ddgame = &*gt.base.ddgame;
|
| 521 |
-
let rq = &mut *ddgame.render_queue;
|
| 522 |
-
|
| 523 |
-
let start_x = gt.pos_x.0;
|
| 524 |
-
let start_y = gt.pos_y.0;
|
| 525 |
-
|
| 526 |
-
let angle = task.aim_angle;
|
| 527 |
-
|
| 528 |
-
// Trig interpolation
|
| 529 |
-
let sin_table = rb(va::G_SIN_TABLE) as *const i32;
|
| 530 |
-
let cos_table = rb(va::G_COS_TABLE) as *const i32;
|
| 531 |
-
let sin_interp = trig_lookup(sin_table, angle);
|
| 532 |
-
let cos_interp = trig_lookup(cos_table, angle);
|
| 533 |
-
|
| 534 |
-
// Scale = fixed_mul(DDGame.parallax_scale, 0x140000) + task.aim_range_offset
|
| 535 |
-
let scale = fixed_mul(ddgame.parallax_scale, 0x14_0000) + task.aim_range_offset;
|
| 536 |
-
|
| 537 |
-
// Endpoint = start + direction * scale
|
| 538 |
-
let mut endpoint_x = fixed_mul(sin_interp, scale).wrapping_add(start_x);
|
| 539 |
-
let mut endpoint_y = fixed_mul(cos_interp, scale).wrapping_add(start_y);
|
| 540 |
-
|
| 541 |
-
// Overflow clamping — when endpoint overflows i32 due to large scale
|
| 542 |
-
let mut overflowed = false;
|
| 543 |
-
let mut clamp_factor = 0i32;
|
| 544 |
-
|
| 545 |
-
let threshold = (*ddgame.game_info).game_version;
|
| 546 |
-
|
| 547 |
-
if threshold > 0x11E {
|
| 548 |
-
// Check X overflow: sin > 0 but endpoint wrapped below start
|
| 549 |
-
if sin_interp > 0 && endpoint_x < start_x {
|
| 550 |
-
overflowed = true;
|
| 551 |
-
clamp_factor = (0x7FFFFFFFi32 - start_x) / sin_interp;
|
| 552 |
-
}
|
| 553 |
-
// Check Y overflow: cos > 0 but endpoint wrapped below start
|
| 554 |
-
if cos_interp > 0 && endpoint_y < start_y {
|
| 555 |
-
let y_clamp = (0x7FFFFFFFi32 - start_y) / cos_interp;
|
| 556 |
-
if !overflowed || y_clamp < clamp_factor {
|
| 557 |
-
clamp_factor = y_clamp;
|
| 558 |
-
}
|
| 559 |
-
overflowed = true;
|
| 560 |
-
}
|
| 561 |
-
if overflowed {
|
| 562 |
-
endpoint_x = start_x + clamp_factor * sin_interp;
|
| 563 |
-
endpoint_y = start_y + clamp_factor * cos_interp;
|
| 564 |
-
}
|
| 565 |
-
}
|
| 566 |
-
|
| 567 |
-
// Enqueue polygon line (2 vertices)
|
| 568 |
-
let poly_param_1 = ddgame.gfx_color_table[8]; // crosshair line style
|
| 569 |
-
let poly_param_2 = ddgame.gfx_color_table[6]; // crosshair line color
|
| 570 |
-
let verts: [[i32; 3]; 2] = [[start_x, start_y, 0], [endpoint_x, endpoint_y, 0]];
|
| 571 |
-
let total_size = 2 * 0xC + 0x20;
|
| 572 |
-
if let Some(ptr) = rq.alloc_raw(total_size) {
|
| 573 |
-
let header = &mut *(ptr as *mut DrawPolygonHeader);
|
| 574 |
-
*header = DrawPolygonHeader {
|
| 575 |
-
command_type: command_type::DRAW_POLYGON,
|
| 576 |
-
layer: 0xE_0000,
|
| 577 |
-
count: 2,
|
| 578 |
-
param_1: poly_param_1,
|
| 579 |
-
param_2: poly_param_2,
|
| 580 |
-
};
|
| 581 |
-
core::ptr::copy_nonoverlapping(
|
| 582 |
-
verts.as_ptr() as *const u8,
|
| 583 |
-
ptr.add(core::mem::size_of::<DrawPolygonHeader>()),
|
| 584 |
-
2 * 0xC,
|
| 585 |
-
);
|
| 586 |
-
}
|
| 587 |
-
|
| 588 |
-
// Draw crosshair sprite at endpoint (only if no overflow clamping)
|
| 589 |
-
if !overflowed {
|
| 590 |
-
if let Some(entry) = rq.alloc::<DrawSpriteCmd>() {
|
| 591 |
-
*entry = DrawSpriteCmd {
|
| 592 |
-
command_type: command_type::DRAW_SPRITE_LOCAL,
|
| 593 |
-
layer: 0x4_0000,
|
| 594 |
-
x_pos: endpoint_x as u32 & 0xFFFF0000,
|
| 595 |
-
y_pos: endpoint_y as u32 & 0xFFFF0000,
|
| 596 |
-
sprite: SpriteOp(0x44),
|
| 597 |
-
palette: (0x8000u32).wrapping_sub(angle),
|
| 598 |
-
};
|
| 599 |
-
}
|
| 600 |
-
}
|
| 601 |
-
}
|
| 602 |
-
|
| 603 |
-
// ---------------------------------------------------------------------------
|
| 604 |
-
// RenderQueue installation
|
| 605 |
-
// ---------------------------------------------------------------------------
|
| 606 |
-
|
| 607 |
-
fn install_render_queue() -> Result<(), String> {
|
| 608 |
-
unsafe {
|
| 609 |
-
let _ = hook::install(
|
| 610 |
-
"EnqueueTiledBitmap",
|
| 611 |
-
va::RQ_ENQUEUE_TILED_BITMAP,
|
| 612 |
-
trampoline_enqueue_tiled_bitmap as *const (),
|
| 613 |
-
)?;
|
| 614 |
-
|
| 615 |
-
let _ = hook::install(
|
| 616 |
-
"DrawLineStrip",
|
| 617 |
-
va::RQ_DRAW_LINE_STRIP,
|
| 618 |
-
trampoline_draw_line_strip as *const (),
|
| 619 |
-
)?;
|
| 620 |
-
|
| 621 |
-
let _ = hook::install(
|
| 622 |
-
"DrawPolygon",
|
| 623 |
-
va::RQ_DRAW_POLYGON,
|
| 624 |
-
trampoline_draw_polygon as *const (),
|
| 625 |
-
)?;
|
| 626 |
-
|
| 627 |
-
let _ = hook::install(
|
| 628 |
-
"DrawCrosshair",
|
| 629 |
-
va::RQ_DRAW_CROSSHAIR,
|
| 630 |
-
trampoline_draw_crosshair as *const (),
|
| 631 |
-
)?;
|
| 632 |
-
|
| 633 |
-
let _ = hook::install(
|
| 634 |
-
"DrawRect",
|
| 635 |
-
va::RQ_DRAW_RECT,
|
| 636 |
-
trampoline_draw_rect as *const (),
|
| 637 |
-
)?;
|
| 638 |
-
|
| 639 |
-
let _ = hook::install(
|
| 640 |
-
"DrawSpriteGlobal",
|
| 641 |
-
va::RQ_DRAW_SPRITE_GLOBAL,
|
| 642 |
-
trampoline_draw_sprite_global as *const (),
|
| 643 |
-
)?;
|
| 644 |
-
|
| 645 |
-
let _ = hook::install(
|
| 646 |
-
"DrawSpriteLocal",
|
| 647 |
-
va::RQ_DRAW_SPRITE_LOCAL,
|
| 648 |
-
trampoline_draw_sprite_local as *const (),
|
| 649 |
-
)?;
|
| 650 |
-
|
| 651 |
-
let _ = hook::install(
|
| 652 |
-
"DrawSpriteOffset",
|
| 653 |
-
va::RQ_DRAW_SPRITE_OFFSET,
|
| 654 |
-
trampoline_draw_sprite_offset as *const (),
|
| 655 |
-
)?;
|
| 656 |
-
|
| 657 |
-
let _ = hook::install(
|
| 658 |
-
"DrawBitmapGlobal",
|
| 659 |
-
va::RQ_DRAW_BITMAP_GLOBAL,
|
| 660 |
-
trampoline_draw_bitmap_global as *const (),
|
| 661 |
-
)?;
|
| 662 |
-
|
| 663 |
-
let _ = hook::install(
|
| 664 |
-
"DrawTextboxLocal",
|
| 665 |
-
va::RQ_DRAW_TEXTBOX_LOCAL,
|
| 666 |
-
trampoline_draw_textbox_local as *const (),
|
| 667 |
-
)?;
|
| 668 |
-
|
| 669 |
-
let _ = hook::install(
|
| 670 |
-
"DrawBungeeTrail",
|
| 671 |
-
va::DRAW_BUNGEE_TRAIL,
|
| 672 |
-
draw_bungee_trail_impl as *const (),
|
| 673 |
-
)?;
|
| 674 |
-
|
| 675 |
-
let _ = hook::install(
|
| 676 |
-
"DrawCrosshairLine",
|
| 677 |
-
va::DRAW_CROSSHAIR_LINE,
|
| 678 |
-
trampoline_draw_crosshair_line as *const (),
|
| 679 |
-
)?;
|
| 680 |
-
|
| 681 |
-
let _ = hook::install(
|
| 682 |
-
"RenderDrawingQueue",
|
| 683 |
-
va::RQ_RENDER_DRAWING_QUEUE,
|
| 684 |
-
trampoline_render_drawing_queue as *const (),
|
| 685 |
-
)?;
|
| 686 |
-
}
|
| 687 |
-
Ok(())
|
| 688 |
-
}
|
| 689 |
-
|
| 690 |
-
// ---------------------------------------------------------------------------
|
| 691 |
-
// RenderDrawingQueue (0x542350) — usercall(EAX=RenderQueue*) + 2 stack
|
| 692 |
-
// (DisplayGfx*, ClipContext*), RET 0x8.
|
| 693 |
-
//
|
| 694 |
-
// The per-frame render-queue dispatcher. Pure Rust port lives in
|
| 695 |
-
// `openwa_core::render::queue_dispatch::render_drawing_queue`. The
|
| 696 |
-
// trampoline captures EAX and forwards the two stack args.
|
| 697 |
-
// ---------------------------------------------------------------------------
|
| 698 |
-
|
| 699 |
-
use openwa_core::render::queue_dispatch::{render_drawing_queue, ClipContext};
|
| 700 |
-
|
| 701 |
-
usercall_trampoline!(fn trampoline_render_drawing_queue;
|
| 702 |
-
impl_fn = render_drawing_queue_impl;
|
| 703 |
-
reg = eax; stack_params = 2; ret_bytes = "0x8");
|
| 704 |
-
|
| 705 |
-
unsafe extern "cdecl" fn render_drawing_queue_impl(
|
| 706 |
-
rq: *mut RenderQueue,
|
| 707 |
-
display: *mut DisplayGfx,
|
| 708 |
-
clip: *mut ClipContext,
|
| 709 |
-
) {
|
| 710 |
-
render_drawing_queue(rq, display, clip);
|
| 711 |
-
}
|
| 712 |
-
|
| 713 |
-
// ==========================================================================
|
| 714 |
-
// DisplayGfx vtable hooks
|
| 715 |
-
// ==========================================================================
|
| 716 |
-
//
|
| 717 |
-
// Patches DisplayBase vtables in WA.exe's .rdata:
|
| 718 |
-
// - Primary vtable (0x6645F8): replaces _purecall slots with safe no-op stubs
|
| 719 |
-
// - Headless vtable (0x66A0F8): replaces destructor with Rust version that
|
| 720 |
-
// correctly frees our Rust-allocated sprite cache sub-objects
|
| 721 |
-
|
| 722 |
-
use openwa_core::bitgrid::DisplayBitGrid;
|
| 723 |
-
use openwa_core::fixed::Fixed;
|
| 724 |
-
use openwa_core::render::display::destructor as display_destructor;
|
| 725 |
-
use openwa_core::render::display::vtable::{self as display_vtable, DisplayGfxVtable};
|
| 726 |
-
use openwa_core::render::display::DisplayBase;
|
| 727 |
-
use openwa_core::render::display::DisplayGfx;
|
| 728 |
-
use openwa_core::vtable::patch_vtable;
|
| 729 |
-
use openwa_core::vtable_replace;
|
| 730 |
-
use openwa_core::wa_alloc::wa_free;
|
| 731 |
-
|
| 732 |
-
/// The _purecall function address (calls abort).
|
| 733 |
-
const PURECALL: u32 = 0x005D_4E16;
|
| 734 |
-
|
| 735 |
-
/// Number of slots in the DisplayBase vtable.
|
| 736 |
-
const VTABLE_SLOTS: usize = 32;
|
| 737 |
-
|
| 738 |
-
unsafe extern "thiscall" fn noop_thiscall(_this: *mut u8) {}
|
| 739 |
-
|
| 740 |
-
/// Rust destructor for headless DisplayBase. Frees the sprite cache chain
|
| 741 |
-
/// (SpriteCache -> FrameCache -> buffer) that was allocated by new_headless().
|
| 742 |
-
unsafe extern "thiscall" fn headless_destructor(
|
| 743 |
-
this: *mut DisplayBase,
|
| 744 |
-
flags: u8,
|
| 745 |
-
) -> *mut DisplayBase {
|
| 746 |
-
let sprite_cache = (*this).sprite_cache;
|
| 747 |
-
if !sprite_cache.is_null() {
|
| 748 |
-
let frame_cache = (*sprite_cache).frame_cache;
|
| 749 |
-
if !frame_cache.is_null() {
|
| 750 |
-
let buf = (*frame_cache).buffer;
|
| 751 |
-
if !buf.is_null() {
|
| 752 |
-
wa_free(buf);
|
| 753 |
-
}
|
| 754 |
-
wa_free(frame_cache);
|
| 755 |
-
}
|
| 756 |
-
wa_free(sprite_cache);
|
| 757 |
-
}
|
| 758 |
-
if flags & 1 != 0 {
|
| 759 |
-
wa_free(this);
|
| 760 |
-
}
|
| 761 |
-
this
|
| 762 |
-
}
|
| 763 |
-
|
| 764 |
-
// No saved originals needed — all paths are fully ported or use direct bridges.
|
| 765 |
-
|
| 766 |
-
/// Rust port of DisplayGfx::BlitSprite (slot 19, 0x56B080).
|
| 767 |
-
///
|
| 768 |
-
/// Standard thiscall: ECX=this, stack params: x, y, sprite_flags, palette (RET 0x10).
|
| 769 |
-
///
|
| 770 |
-
unsafe extern "thiscall" fn blit_sprite(
|
| 771 |
-
this: *mut DisplayGfx,
|
| 772 |
-
x: Fixed,
|
| 773 |
-
y: Fixed,
|
| 774 |
-
sprite_flags: u32,
|
| 775 |
-
palette: u32,
|
| 776 |
-
) {
|
| 777 |
-
use openwa_core::render::display::gfx::DisplayGfx;
|
| 778 |
-
use openwa_core::render::display::vtable as display_vtable;
|
| 779 |
-
use openwa_core::render::SpriteFlags;
|
| 780 |
-
|
| 781 |
-
let gfx = this as *mut DisplayGfx;
|
| 782 |
-
let base = this as *const u8;
|
| 783 |
-
|
| 784 |
-
// ---------------------------------------------------------------
|
| 785 |
-
// Extract sprite index and flags
|
| 786 |
-
// ---------------------------------------------------------------
|
| 787 |
-
let sprite = SpriteOp(sprite_flags);
|
| 788 |
-
let flags = sprite.flags();
|
| 789 |
-
let sprite_id = sprite.index() as u32;
|
| 790 |
-
|
| 791 |
-
if sprite_id == 0 {
|
| 792 |
-
return;
|
| 793 |
-
}
|
| 794 |
-
|
| 795 |
-
// ---------------------------------------------------------------
|
| 796 |
-
// Palette manipulation
|
| 797 |
-
// ---------------------------------------------------------------
|
| 798 |
-
let mut pal: u32 = palette;
|
| 799 |
-
if flags.contains(SpriteFlags::INVERT_PALETTE) {
|
| 800 |
-
pal = 0x10000u32.wrapping_sub(palette);
|
| 801 |
-
if sprite_id.wrapping_sub(0x1D5) < 3 {
|
| 802 |
-
// Special sprite IDs: scale by 8/18
|
| 803 |
-
pal = (0x10000u32.wrapping_sub(palette).wrapping_mul(8)) / 0x12;
|
| 804 |
-
}
|
| 805 |
-
}
|
| 806 |
-
if flags.contains(SpriteFlags::PALETTE_XFORM) {
|
| 807 |
-
// Bit 25: palette transform (modular arithmetic for color cycling)
|
| 808 |
-
let tmp = ((pal.wrapping_mul(0x1F) as i32)
|
| 809 |
-
.wrapping_add(((pal.wrapping_mul(0x1F) as i32) >> 31) & 0x1F)
|
| 810 |
-
>> 5) as u32;
|
| 811 |
-
let tmp = tmp.wrapping_add(0x400) & 0xFFFF;
|
| 812 |
-
pal = (tmp.wrapping_rem(0xF800)) / 2;
|
| 813 |
-
if (pal & 0x400) != 0 {
|
| 814 |
-
pal = (pal & !0x400) | 0x8000;
|
| 815 |
-
}
|
| 816 |
-
}
|
| 817 |
-
|
| 818 |
-
// ---------------------------------------------------------------
|
| 819 |
-
// Check sprite arrays — bitmap path if not in primary arrays
|
| 820 |
-
// ---------------------------------------------------------------
|
| 821 |
-
let arr1 = *(base.add(sprite_id as usize * 4 + 0x1008) as *const u32);
|
| 822 |
-
let arr2 = *(base.add(sprite_id as usize * 4 + 0x2008) as *const u32);
|
| 823 |
-
|
| 824 |
-
if arr1 == 0 && arr2 == 0 {
|
| 825 |
-
// Bitmap sprite path — sprite is in the bitmap table at 0x3DD4.
|
| 826 |
-
let bitmap_obj = (*gfx).sprite_table[sprite_id as usize];
|
| 827 |
-
if bitmap_obj.is_null() {
|
| 828 |
-
return;
|
| 829 |
-
}
|
| 830 |
-
|
| 831 |
-
// Get frame data and dimensions from bitmap sprite object.
|
| 832 |
-
let mut sprite_w: i32 = 0;
|
| 833 |
-
let mut sprite_h: i32 = 0;
|
| 834 |
-
let mut rect_left: i32 = 0;
|
| 835 |
-
let mut rect_top: i32 = 0;
|
| 836 |
-
let mut rect_right: i32 = 0;
|
| 837 |
-
let mut rect_bottom: i32 = 0;
|
| 838 |
-
let frame_data = display_vtable::get_bitmap_sprite_info(
|
| 839 |
-
bitmap_obj,
|
| 840 |
-
pal,
|
| 841 |
-
&mut sprite_w,
|
| 842 |
-
&mut sprite_h,
|
| 843 |
-
&mut rect_left,
|
| 844 |
-
&mut rect_top,
|
| 845 |
-
&mut rect_right,
|
| 846 |
-
&mut rect_bottom,
|
| 847 |
-
);
|
| 848 |
-
if frame_data.is_null() {
|
| 849 |
-
return;
|
| 850 |
-
}
|
| 851 |
-
|
| 852 |
-
let camera_x = (*gfx).camera_x;
|
| 853 |
-
let camera_y = (*gfx).camera_y;
|
| 854 |
-
let half_w = sprite_w / 2;
|
| 855 |
-
let half_h = sprite_h / 2;
|
| 856 |
-
let blit_h = rect_bottom - rect_top;
|
| 857 |
-
let dst_x = (x.0 >> 16) + (camera_x - half_w) + rect_left;
|
| 858 |
-
let dst_y = (y.0 >> 16) + (camera_y - half_h) + rect_top;
|
| 859 |
-
|
| 860 |
-
if !flags.contains(SpriteFlags::TILED) {
|
| 861 |
-
// Non-tiled: clipped blit at (dst_x, dst_y).
|
| 862 |
-
display_vtable::blit_bitmap_clipped_native(
|
| 863 |
-
gfx, dst_x, dst_y, sprite_w, blit_h, frame_data, 2,
|
| 864 |
-
);
|
| 865 |
-
} else {
|
| 866 |
-
// Tiled: horizontal tile across the clip rect.
|
| 867 |
-
display_vtable::blit_bitmap_tiled_native(
|
| 868 |
-
gfx, dst_x, sprite_w, dst_y, blit_h, frame_data,
|
| 869 |
-
);
|
| 870 |
-
}
|
| 871 |
-
return;
|
| 872 |
-
}
|
| 873 |
-
|
| 874 |
-
// ---------------------------------------------------------------
|
| 875 |
-
// Bit 24: palette ×4 adjust with orientation-dependent high bits
|
| 876 |
-
// ---------------------------------------------------------------
|
| 877 |
-
// The original ASM at 0x56B145 scales pal by 4, adds 0x8000, then
|
| 878 |
-
// uses the overflow quadrant (bits 16-17 of the scaled value) to
|
| 879 |
-
// select an orientation+blend combo. The low 16 bits become the
|
| 880 |
-
// new palette value.
|
| 881 |
-
let mut orient_local: u32 = 0x0000_0001; // blend=1 (ColorTable/transparency), orientation=0 (Normal)
|
| 882 |
-
if flags.contains(SpriteFlags::PALETTE_X4) {
|
| 883 |
-
let scaled = pal.wrapping_mul(4).wrapping_add(0x8000);
|
| 884 |
-
pal = scaled & 0xFFFF;
|
| 885 |
-
let quad = ((scaled as i32) >> 16) & 3;
|
| 886 |
-
orient_local = match quad {
|
| 887 |
-
0 => 0x0008_0001, // MirrorXY + ColorTable
|
| 888 |
-
1 => 0x000C_0001, // Rotate90MirrorXY + ColorTable
|
| 889 |
-
2 => 0x0004_0001, // Rotate90 + ColorTable
|
| 890 |
-
_ => 0x0000_0001, // Normal + ColorTable
|
| 891 |
-
};
|
| 892 |
-
}
|
| 893 |
-
|
| 894 |
-
// ---------------------------------------------------------------
|
| 895 |
-
// Sprite frame lookup via DisplayGfx::GetSpriteFrameForBlit (slot 33).
|
| 896 |
-
// ---------------------------------------------------------------
|
| 897 |
-
// Resolves the sprite ID + animation value into the actual decompressed
|
| 898 |
-
// frame surface plus its bounding box and full sprite-cell dimensions
|
| 899 |
-
// (used below for centering and clipped blits).
|
| 900 |
-
let mut out_sprite_w: i32 = 0;
|
| 901 |
-
let mut out_sprite_h: i32 = 0;
|
| 902 |
-
let mut out_rect_left: i32 = 0;
|
| 903 |
-
let mut out_rect_top: i32 = 0;
|
| 904 |
-
let mut out_rect_right: i32 = 0;
|
| 905 |
-
let mut out_rect_bottom: i32 = 0;
|
| 906 |
-
let mut out_anim_frac: u32 = 0;
|
| 907 |
-
|
| 908 |
-
let mut sprite_surface = DisplayGfx::get_sprite_frame_for_blit_raw(
|
| 909 |
-
gfx,
|
| 910 |
-
sprite_id,
|
| 911 |
-
pal,
|
| 912 |
-
&mut out_sprite_w,
|
| 913 |
-
&mut out_sprite_h,
|
| 914 |
-
&mut out_rect_left,
|
| 915 |
-
&mut out_rect_top,
|
| 916 |
-
&mut out_rect_right,
|
| 917 |
-
&mut out_rect_bottom,
|
| 918 |
-
&mut out_anim_frac,
|
| 919 |
-
);
|
| 920 |
-
|
| 921 |
-
if sprite_surface.is_null() {
|
| 922 |
-
return;
|
| 923 |
-
}
|
| 924 |
-
|
| 925 |
-
let sprite_w = out_sprite_w;
|
| 926 |
-
let sprite_h = out_sprite_h;
|
| 927 |
-
let rect_left = out_rect_left;
|
| 928 |
-
let rect_top = out_rect_top;
|
| 929 |
-
let rect_right = out_rect_right;
|
| 930 |
-
let rect_bottom = out_rect_bottom;
|
| 931 |
-
|
| 932 |
-
// Size checks
|
| 933 |
-
if rect_left >= rect_right || rect_top >= rect_bottom {
|
| 934 |
-
return;
|
| 935 |
-
}
|
| 936 |
-
|
| 937 |
-
let mut blit_w = rect_right - rect_left;
|
| 938 |
-
let mut blit_h = rect_bottom - rect_top;
|
| 939 |
-
|
| 940 |
-
// ---------------------------------------------------------------
|
| 941 |
-
// Shadow clear (high_flags bit 22)
|
| 942 |
-
// ---------------------------------------------------------------
|
| 943 |
-
if flags.contains(SpriteFlags::SHADOW_CLEAR) {
|
| 944 |
-
// Blit sprite to layer_2 as shadow base
|
| 945 |
-
let layer2 = (*gfx).layer_2;
|
| 946 |
-
super::bitgrid::blit_impl(
|
| 947 |
-
layer2,
|
| 948 |
-
0,
|
| 949 |
-
0,
|
| 950 |
-
blit_w,
|
| 951 |
-
blit_h,
|
| 952 |
-
sprite_surface,
|
| 953 |
-
0,
|
| 954 |
-
0,
|
| 955 |
-
core::ptr::null(),
|
| 956 |
-
0, // mode 0 = copy
|
| 957 |
-
);
|
| 958 |
-
// Manipulate color_add_table entry for shadow
|
| 959 |
-
let color_idx = ((*gfx)._unknown_356c as usize) * 0x100;
|
| 960 |
-
let table_byte = &mut (*gfx).color_add_table[color_idx];
|
| 961 |
-
let saved = *table_byte;
|
| 962 |
-
*table_byte = 0;
|
| 963 |
-
|
| 964 |
-
// Call BitGrid__ClearColumn_Maybe (0x4F6590) — clears shadow channel
|
| 965 |
-
let clear_fn: unsafe extern "cdecl" fn(*mut u8) =
|
| 966 |
-
core::mem::transmute(rb(0x004F6590) as usize);
|
| 967 |
-
clear_fn(table_byte as *mut u8);
|
| 968 |
-
|
| 969 |
-
*table_byte = saved;
|
| 970 |
-
|
| 971 |
-
// Replace sprite surface with layer_2 (shadow-processed)
|
| 972 |
-
sprite_surface = layer2;
|
| 973 |
-
}
|
| 974 |
-
|
| 975 |
-
// ---------------------------------------------------------------
|
| 976 |
-
// Extra orientation flags from high_flags
|
| 977 |
-
// ---------------------------------------------------------------
|
| 978 |
-
if flags.contains(SpriteFlags::MIRROR_X) {
|
| 979 |
-
orient_local |= 0x0001_0000;
|
| 980 |
-
}
|
| 981 |
-
if flags.contains(SpriteFlags::MIRROR_Y) {
|
| 982 |
-
orient_local |= 0x0002_0000;
|
| 983 |
-
}
|
| 984 |
-
|
| 985 |
-
// ---------------------------------------------------------------
|
| 986 |
-
// 16-case orientation switch for camera coordinate mapping
|
| 987 |
-
// ---------------------------------------------------------------
|
| 988 |
-
let camera_x = (*gfx).camera_x;
|
| 989 |
-
let camera_y = (*gfx).camera_y;
|
| 990 |
-
|
| 991 |
-
// Signed divide toward zero (matches MSVC CDQ+SUB+SAR pattern)
|
| 992 |
-
let half_w = if sprite_w < 0 {
|
| 993 |
-
(sprite_w + 1) / 2
|
| 994 |
-
} else {
|
| 995 |
-
sprite_w / 2
|
| 996 |
-
};
|
| 997 |
-
let half_h = if sprite_h < 0 {
|
| 998 |
-
(sprite_h + 1) / 2
|
| 999 |
-
} else {
|
| 1000 |
-
sprite_h / 2
|
| 1001 |
-
};
|
| 1002 |
-
|
| 1003 |
-
let x_px = x.0 >> 16;
|
| 1004 |
-
let y_px = y.0 >> 16;
|
| 1005 |
-
|
| 1006 |
-
let (dst_x, dst_y);
|
| 1007 |
-
let orientation_key = (orient_local >> 16) as i32;
|
| 1008 |
-
|
| 1009 |
-
match orientation_key {
|
| 1010 |
-
1 | 10 => {
|
| 1011 |
-
// MirrorX
|
| 1012 |
-
dst_x = camera_x + half_w + x_px - rect_right;
|
| 1013 |
-
dst_y = camera_y - half_h + rect_top + y_px;
|
| 1014 |
-
}
|
| 1015 |
-
2 | 9 => {
|
| 1016 |
-
// MirrorY — X same as Normal, Y mirrored
|
| 1017 |
-
dst_x = camera_x - half_w + rect_left + x_px;
|
| 1018 |
-
dst_y = camera_y + half_h + y_px - rect_bottom;
|
| 1019 |
-
}
|
| 1020 |
-
3 | 8 => {
|
| 1021 |
-
// MirrorXY
|
| 1022 |
-
dst_x = camera_x + half_w + x_px - rect_right;
|
| 1023 |
-
dst_y = camera_y + half_h + y_px - rect_bottom;
|
| 1024 |
-
}
|
| 1025 |
-
4 | 15 => {
|
| 1026 |
-
// Rotate90 — swap axes
|
| 1027 |
-
dst_x = camera_x - half_h + rect_top + x_px;
|
| 1028 |
-
dst_y = camera_y + half_w + y_px - rect_right;
|
| 1029 |
-
blit_w = rect_bottom - rect_top;
|
| 1030 |
-
blit_h = rect_right - rect_left;
|
| 1031 |
-
}
|
| 1032 |
-
5 | 14 => {
|
| 1033 |
-
// Rotate90MirrorX
|
| 1034 |
-
dst_x = camera_x + half_h + x_px - rect_bottom;
|
| 1035 |
-
dst_y = camera_y + half_w + y_px - rect_right;
|
| 1036 |
-
blit_w = rect_bottom - rect_top;
|
| 1037 |
-
blit_h = rect_right - rect_left;
|
| 1038 |
-
}
|
| 1039 |
-
6 | 13 => {
|
| 1040 |
-
// Rotate90MirrorY
|
| 1041 |
-
dst_x = camera_x - half_h + rect_top + x_px;
|
| 1042 |
-
dst_y = camera_y - half_w + rect_left + y_px;
|
| 1043 |
-
blit_w = rect_bottom - rect_top;
|
| 1044 |
-
blit_h = rect_right - rect_left;
|
| 1045 |
-
}
|
| 1046 |
-
7 | 12 => {
|
| 1047 |
-
// Rotate90MirrorXY
|
| 1048 |
-
dst_x = camera_x + half_h + x_px - rect_bottom;
|
| 1049 |
-
dst_y = camera_y - half_w + rect_left + y_px;
|
| 1050 |
-
blit_w = rect_bottom - rect_top;
|
| 1051 |
-
blit_h = rect_right - rect_left;
|
| 1052 |
-
}
|
| 1053 |
-
_ => {
|
| 1054 |
-
// Normal (0, 11, and any other value)
|
| 1055 |
-
dst_x = camera_x - half_w + rect_left + x_px;
|
| 1056 |
-
dst_y = camera_y - half_h + rect_top + y_px;
|
| 1057 |
-
}
|
| 1058 |
-
}
|
| 1059 |
-
|
| 1060 |
-
// ---------------------------------------------------------------
|
| 1061 |
-
// Blit dispatch based on high_flags
|
| 1062 |
-
// ---------------------------------------------------------------
|
| 1063 |
-
|
| 1064 |
-
if blit_w <= 0 || blit_h <= 0 {
|
| 1065 |
-
return;
|
| 1066 |
-
}
|
| 1067 |
-
|
| 1068 |
-
// Stippled mode (checkerboard per-pixel blit)
|
| 1069 |
-
if flags.intersects(SpriteFlags::STIPPLED_0 | SpriteFlags::STIPPLED_1) {
|
| 1070 |
-
let stipple_mode: u32 = if flags.contains(SpriteFlags::STIPPLED_1) {
|
| 1071 |
-
1
|
| 1072 |
-
} else {
|
| 1073 |
-
0
|
| 1074 |
-
};
|
| 1075 |
-
let parity = *(rb(openwa_core::address::va::G_STIPPLE_PARITY) as *const u32);
|
| 1076 |
-
|
| 1077 |
-
display_vtable::acquire_render_lock(gfx);
|
| 1078 |
-
|
| 1079 |
-
super::bitgrid::blit_stippled_raw(
|
| 1080 |
-
(*gfx).layer_0,
|
| 1081 |
-
sprite_surface,
|
| 1082 |
-
dst_x,
|
| 1083 |
-
dst_y,
|
| 1084 |
-
blit_w,
|
| 1085 |
-
blit_h,
|
| 1086 |
-
0,
|
| 1087 |
-
0,
|
| 1088 |
-
stipple_mode,
|
| 1089 |
-
parity,
|
| 1090 |
-
);
|
| 1091 |
-
return;
|
| 1092 |
-
}
|
| 1093 |
-
|
| 1094 |
-
// Tiled mode (horizontal sprite tiling)
|
| 1095 |
-
if flags.contains(SpriteFlags::TILED) {
|
| 1096 |
-
display_vtable::acquire_render_lock(gfx);
|
| 1097 |
-
|
| 1098 |
-
super::bitgrid::blit_tiled_raw(
|
| 1099 |
-
(*gfx).layer_0,
|
| 1100 |
-
sprite_surface,
|
| 1101 |
-
dst_x,
|
| 1102 |
-
dst_y,
|
| 1103 |
-
blit_w,
|
| 1104 |
-
blit_h,
|
| 1105 |
-
(*gfx).base.clip_x1,
|
| 1106 |
-
(*gfx).base.clip_x2,
|
| 1107 |
-
orient_local,
|
| 1108 |
-
);
|
| 1109 |
-
return;
|
| 1110 |
-
}
|
| 1111 |
-
|
| 1112 |
-
// Determine color table pointer
|
| 1113 |
-
let color_table: *const u8 = if flags.contains(SpriteFlags::ADDITIVE) {
|
| 1114 |
-
(*gfx).color_add_table.as_ptr()
|
| 1115 |
-
} else if flags.contains(SpriteFlags::COLOR_BLEND) {
|
| 1116 |
-
(*gfx).color_blend_table.as_ptr()
|
| 1117 |
-
} else {
|
| 1118 |
-
core::ptr::null()
|
| 1119 |
-
};
|
| 1120 |
-
|
| 1121 |
-
display_vtable::acquire_render_lock(gfx);
|
| 1122 |
-
|
| 1123 |
-
// src_x=0, src_y=0 always — vtable[33] already set up the sprite surface
|
| 1124 |
-
super::bitgrid::blit_impl(
|
| 1125 |
-
(*gfx).layer_0,
|
| 1126 |
-
dst_x,
|
| 1127 |
-
dst_y,
|
| 1128 |
-
blit_w,
|
| 1129 |
-
blit_h,
|
| 1130 |
-
sprite_surface,
|
| 1131 |
-
0,
|
| 1132 |
-
0,
|
| 1133 |
-
color_table,
|
| 1134 |
-
orient_local,
|
| 1135 |
-
);
|
| 1136 |
-
}
|
| 1137 |
-
|
| 1138 |
-
/// Thiscall wrapper for DisplayGfx::DrawScaledSprite (slot 20).
|
| 1139 |
-
///
|
| 1140 |
-
/// Computes coordinates in core, then dispatches the blit via blit_impl.
|
| 1141 |
-
unsafe extern "thiscall" fn draw_scaled_sprite(
|
| 1142 |
-
this: *mut DisplayGfx,
|
| 1143 |
-
x: Fixed,
|
| 1144 |
-
y: Fixed,
|
| 1145 |
-
sprite: *mut DisplayBitGrid,
|
| 1146 |
-
src_x: i32,
|
| 1147 |
-
src_y: i32,
|
| 1148 |
-
src_w: i32,
|
| 1149 |
-
src_h: i32,
|
| 1150 |
-
flags: u32,
|
| 1151 |
-
) {
|
| 1152 |
-
use openwa_core::render::display::vtable::{self as display_vtable, DrawScaledSpriteResult};
|
| 1153 |
-
|
| 1154 |
-
match display_vtable::draw_scaled_sprite(this, x, y, sprite, src_x, src_y, src_w, src_h, flags)
|
| 1155 |
-
{
|
| 1156 |
-
DrawScaledSpriteResult::Blit {
|
| 1157 |
-
layer,
|
| 1158 |
-
dst_x,
|
| 1159 |
-
dst_y,
|
| 1160 |
-
width,
|
| 1161 |
-
height,
|
| 1162 |
-
sprite,
|
| 1163 |
-
src_x,
|
| 1164 |
-
src_y,
|
| 1165 |
-
color_table,
|
| 1166 |
-
blit_flags,
|
| 1167 |
-
} => {
|
| 1168 |
-
super::bitgrid::blit_impl(
|
| 1169 |
-
layer,
|
| 1170 |
-
dst_x,
|
| 1171 |
-
dst_y,
|
| 1172 |
-
width,
|
| 1173 |
-
height,
|
| 1174 |
-
sprite,
|
| 1175 |
-
src_x,
|
| 1176 |
-
src_y,
|
| 1177 |
-
color_table,
|
| 1178 |
-
blit_flags,
|
| 1179 |
-
);
|
| 1180 |
-
}
|
| 1181 |
-
DrawScaledSpriteResult::Stippled {
|
| 1182 |
-
layer,
|
| 1183 |
-
dst_x,
|
| 1184 |
-
dst_y,
|
| 1185 |
-
width,
|
| 1186 |
-
height,
|
| 1187 |
-
sprite,
|
| 1188 |
-
src_x,
|
| 1189 |
-
src_y,
|
| 1190 |
-
stipple_mode,
|
| 1191 |
-
} => {
|
| 1192 |
-
let parity = *(rb(openwa_core::address::va::G_STIPPLE_PARITY) as *const u32);
|
| 1193 |
-
super::bitgrid::blit_stippled_raw(
|
| 1194 |
-
layer,
|
| 1195 |
-
sprite,
|
| 1196 |
-
dst_x,
|
| 1197 |
-
dst_y,
|
| 1198 |
-
width,
|
| 1199 |
-
height,
|
| 1200 |
-
src_x,
|
| 1201 |
-
src_y,
|
| 1202 |
-
stipple_mode,
|
| 1203 |
-
parity,
|
| 1204 |
-
);
|
| 1205 |
-
}
|
| 1206 |
-
DrawScaledSpriteResult::Handled => {}
|
| 1207 |
-
}
|
| 1208 |
-
}
|
| 1209 |
-
|
| 1210 |
-
// =========================================================================
|
| 1211 |
-
// Font vtable method wrappers
|
| 1212 |
-
// =========================================================================
|
| 1213 |
-
//
|
| 1214 |
-
// Font slot wrappers (slots 7/8/9/10/34/35/36) all live in openwa-core's
|
| 1215 |
-
// `display::vtable` module. The wrappers in this file (slots 31/34/35/37
|
| 1216 |
-
// for sprite/font loading) are thin forwarders only because they need to
|
| 1217 |
-
// capture a bridge function pointer (`wa_load_sprite_from_vfs` etc.) at
|
| 1218 |
-
// install time.
|
| 1219 |
-
|
| 1220 |
-
// =========================================================================
|
| 1221 |
-
// Sprite loading vtable method wrappers
|
| 1222 |
-
// =========================================================================
|
| 1223 |
-
|
| 1224 |
-
/// Thiscall wrapper for DisplayGfx::LoadSprite (vtable slot 31, 0x523400).
|
| 1225 |
-
unsafe extern "thiscall" fn load_sprite(
|
| 1226 |
-
this: *mut DisplayGfx,
|
| 1227 |
-
layer: u32,
|
| 1228 |
-
id: u32,
|
| 1229 |
-
flag: u32,
|
| 1230 |
-
gfx_dir: *mut GfxDir,
|
| 1231 |
-
name: *const c_char,
|
| 1232 |
-
) -> i32 {
|
| 1233 |
-
display_vtable::load_sprite(
|
| 1234 |
-
this,
|
| 1235 |
-
layer,
|
| 1236 |
-
id,
|
| 1237 |
-
flag,
|
| 1238 |
-
gfx_dir,
|
| 1239 |
-
name,
|
| 1240 |
-
wa_load_sprite_from_vfs,
|
| 1241 |
-
)
|
| 1242 |
-
}
|
| 1243 |
-
|
| 1244 |
-
/// Bridge to LoadSpriteFromVfs (0x4FAAF0).
|
| 1245 |
-
/// Usercall: ECX=gfx, EAX=name, stack(sprite, layer_ctx), RET 0x8.
|
| 1246 |
-
///
|
| 1247 |
-
/// Verified from caller at 0x523489:
|
| 1248 |
-
/// ECX ← layer_contexts[layer] (gfx/VFS context)... wait, re-checked:
|
| 1249 |
-
/// ECX ← gfx param, EAX ← name param.
|
| 1250 |
-
/// Stack: sprite (EDI from ConstructSprite), layer_ctx.
|
| 1251 |
-
#[unsafe(naked)]
|
| 1252 |
-
unsafe extern "cdecl" fn wa_load_sprite_from_vfs(
|
| 1253 |
-
_sprite: *mut openwa_core::render::sprite::Sprite,
|
| 1254 |
-
_gfx_dir: *mut GfxDir,
|
| 1255 |
-
_name: *const c_char,
|
| 1256 |
-
_layer_ctx: *mut openwa_core::render::palette::PaletteContext,
|
| 1257 |
-
) -> i32 {
|
| 1258 |
-
core::arch::naked_asm!(
|
| 1259 |
-
// cdecl: +4=sprite, +8=gfx, +12=name, +16=layer_ctx
|
| 1260 |
-
"mov ecx, [esp+8]", // gfx → ECX
|
| 1261 |
-
"mov eax, [esp+12]", // name → EAX
|
| 1262 |
-
"push dword ptr [esp+16]", // layer_ctx
|
| 1263 |
-
"push dword ptr [esp+8]", // sprite (shifted +4 by push)
|
| 1264 |
-
"call [{ADDR}]", // RET 0x8 cleans 2 stack params
|
| 1265 |
-
"ret",
|
| 1266 |
-
ADDR = sym LOAD_SPRITE_FROM_VFS_ADDR,
|
| 1267 |
-
);
|
| 1268 |
-
}
|
| 1269 |
-
|
| 1270 |
-
static mut LOAD_SPRITE_FROM_VFS_ADDR: u32 = 0;
|
| 1271 |
-
|
| 1272 |
-
// GetSpriteFrameForBlit (slot 33) is NOT ported — see the docstring on
|
| 1273 |
-
// `DisplayVtable::get_sprite_frame_for_blit` in openwa-core for the full
|
| 1274 |
-
// rationale. Our `blit_sprite` (slot 19) above calls it via the bound
|
| 1275 |
-
// vtable wrapper on every sprite render.
|
| 1276 |
-
|
| 1277 |
-
unsafe extern "thiscall" fn load_sprite_by_layer(
|
| 1278 |
-
this: *mut DisplayGfx,
|
| 1279 |
-
layer: u32,
|
| 1280 |
-
id: u32,
|
| 1281 |
-
gfx_dir: *mut GfxDir,
|
| 1282 |
-
name: *const c_char,
|
| 1283 |
-
) -> i32 {
|
| 1284 |
-
display_vtable::load_sprite_by_layer(this, layer, id, gfx_dir, name)
|
| 1285 |
-
}
|
| 1286 |
-
|
| 1287 |
-
unsafe extern "thiscall" fn load_font(
|
| 1288 |
-
this: *mut DisplayGfx,
|
| 1289 |
-
mode: u32,
|
| 1290 |
-
font_id: i32,
|
| 1291 |
-
gfx_dir: *mut GfxDir,
|
| 1292 |
-
filename: *const c_char,
|
| 1293 |
-
) -> u32 {
|
| 1294 |
-
display_vtable::load_font(this, mode, font_id, gfx_dir, filename)
|
| 1295 |
-
}
|
| 1296 |
-
|
| 1297 |
-
unsafe extern "thiscall" fn load_font_extension(
|
| 1298 |
-
this: *mut DisplayGfx,
|
| 1299 |
-
font_id: i32,
|
| 1300 |
-
path: *const c_char,
|
| 1301 |
-
char_map: *const c_char,
|
| 1302 |
-
palette_value: u32,
|
| 1303 |
-
flag: i32,
|
| 1304 |
-
) -> u32 {
|
| 1305 |
-
display_vtable::load_font_extension(this, font_id, path, char_map, palette_value, flag)
|
| 1306 |
-
}
|
| 1307 |
-
|
| 1308 |
-
// ---------------------------------------------------------------------------
|
| 1309 |
-
// Display installation
|
| 1310 |
-
// ---------------------------------------------------------------------------
|
| 1311 |
-
|
| 1312 |
-
fn install_display() -> Result<(), String> {
|
| 1313 |
-
let _ = log_line("[Display] Patching DisplayBase vtables");
|
| 1314 |
-
|
| 1315 |
-
unsafe {
|
| 1316 |
-
let purecall_addr = rb(PURECALL);
|
| 1317 |
-
let noop_addr = noop_thiscall as *const () as u32;
|
| 1318 |
-
|
| 1319 |
-
// Patch primary vtable (0x6645F8): replace _purecall with no-ops.
|
| 1320 |
-
let primary = rb(va::DISPLAY_BASE_VTABLE) as *mut u32;
|
| 1321 |
-
patch_vtable(primary, VTABLE_SLOTS, |vt| {
|
| 1322 |
-
let mut patched = 0u32;
|
| 1323 |
-
for i in 0..VTABLE_SLOTS {
|
| 1324 |
-
let slot = vt.add(i);
|
| 1325 |
-
if *slot == purecall_addr {
|
| 1326 |
-
*slot = noop_addr;
|
| 1327 |
-
patched += 1;
|
| 1328 |
-
}
|
| 1329 |
-
}
|
| 1330 |
-
let _ = log_line(&format!(
|
| 1331 |
-
"[Display] Primary: patched {patched}/{VTABLE_SLOTS} _purecall -> no-op"
|
| 1332 |
-
));
|
| 1333 |
-
})?;
|
| 1334 |
-
|
| 1335 |
-
// Patch headless vtable (0x66A0F8): replace destructor (slot 0)
|
| 1336 |
-
// with our Rust version that frees the Rust-allocated sprite cache.
|
| 1337 |
-
let headless = rb(va::DISPLAY_BASE_HEADLESS_VTABLE) as *mut u32;
|
| 1338 |
-
patch_vtable(headless, VTABLE_SLOTS, |vt| {
|
| 1339 |
-
*vt = headless_destructor as *const () as u32;
|
| 1340 |
-
let _ = log_line("[Display] Headless: patched slot 0 (destructor) -> Rust");
|
| 1341 |
-
})?;
|
| 1342 |
-
|
| 1343 |
-
// Initialize bridge address statics for sprite loading
|
| 1344 |
-
LOAD_SPRITE_FROM_VFS_ADDR = rb(va::LOAD_SPRITE_FROM_VFS);
|
| 1345 |
-
|
| 1346 |
-
// Patch DisplayGfx vtable (0x66A218): replace ported methods with Rust.
|
| 1347 |
-
vtable_replace!(DisplayGfxVtable, va::DISPLAY_GFX_VTABLE, {
|
| 1348 |
-
destructor => display_destructor::display_gfx_destructor,
|
| 1349 |
-
get_dimensions => display_vtable::get_dimensions,
|
| 1350 |
-
set_layer_color => display_vtable::set_layer_color,
|
| 1351 |
-
set_active_layer => display_vtable::set_active_layer,
|
| 1352 |
-
get_sprite_info => display_vtable::get_sprite_info,
|
| 1353 |
-
draw_text_on_bitmap => display_vtable::draw_text_on_bitmap,
|
| 1354 |
-
draw_tiled_bitmap => display_vtable::draw_tiled_bitmap,
|
| 1355 |
-
get_font_info => display_vtable::get_font_info,
|
| 1356 |
-
get_font_metric => display_vtable::get_font_metric,
|
| 1357 |
-
set_font_param => display_vtable::set_font_param,
|
| 1358 |
-
draw_polyline => display_vtable::draw_polyline,
|
| 1359 |
-
draw_line => display_vtable::draw_line,
|
| 1360 |
-
draw_line_clipped => display_vtable::draw_line_clipped,
|
| 1361 |
-
draw_pixel_strip => display_vtable::draw_pixel_strip,
|
| 1362 |
-
draw_crosshair => display_vtable::draw_crosshair,
|
| 1363 |
-
draw_outlined_pixel => display_vtable::draw_outlined_pixel,
|
| 1364 |
-
fill_rect => display_vtable::fill_rect,
|
| 1365 |
-
draw_via_callback => display_vtable::draw_via_callback,
|
| 1366 |
-
draw_tiled_terrain => display_vtable::draw_tiled_terrain,
|
| 1367 |
-
flush_render => display_vtable::flush_render,
|
| 1368 |
-
set_camera_offset => display_vtable::set_camera_offset,
|
| 1369 |
-
set_clip_rect => display_vtable::set_clip_rect,
|
| 1370 |
-
is_sprite_loaded => display_vtable::is_sprite_loaded,
|
| 1371 |
-
load_sprite => load_sprite,
|
| 1372 |
-
draw_scaled_sprite => draw_scaled_sprite,
|
| 1373 |
-
set_layer_visibility => display_vtable::set_layer_visibility,
|
| 1374 |
-
update_palette => display_vtable::update_palette,
|
| 1375 |
-
set_font_palette => display_vtable::set_font_palette,
|
| 1376 |
-
slot 19 => blit_sprite,
|
| 1377 |
-
get_sprite_frame_for_blit => display_vtable::get_sprite_frame_for_blit,
|
| 1378 |
-
load_sprite_by_layer => load_sprite_by_layer,
|
| 1379 |
-
load_font => load_font,
|
| 1380 |
-
load_font_extension => load_font_extension,
|
| 1381 |
-
})?;
|
| 1382 |
-
let _ = log_line("[Display] DisplayGfx: patched 32 methods -> Rust");
|
| 1383 |
-
|
| 1384 |
-
// DisplayGfx::LoadSpriteEx (vtable slot 30) has zero callers in both
|
| 1385 |
-
// WA.exe (no instructions reach vtable[+0x78] on a DisplayGfx) and our
|
| 1386 |
-
// own Rust code (no `bind_DisplayVtable!` invocation, no direct call).
|
| 1387 |
-
// Trap it so we'll be alerted the moment any future caller appears.
|
| 1388 |
-
hook::install_trap!("DisplayGfx__LoadSpriteEx", va::DISPLAY_GFX_LOAD_SPRITE_EX);
|
| 1389 |
-
|
| 1390 |
-
// ── Slot 33 leaf functions: trap the WA-side originals ────────
|
| 1391 |
-
//
|
| 1392 |
-
// After `vtable_replace!` swapped slot 33 to our Rust impl, the
|
| 1393 |
-
// original `DisplayGfx::GetSpriteFrameForBlit` (0x5237C0) is no
|
| 1394 |
-
// longer reached, which means its leaves have no remaining
|
| 1395 |
-
// callers in shipping WA either:
|
| 1396 |
-
//
|
| 1397 |
-
// - `Sprite__GetFrameForBlit` (0x4FAD30): only caller was the
|
| 1398 |
-
// original slot 33 dispatcher.
|
| 1399 |
-
// - `SpriteBank__GetFrameForBlit` (0x4F9710): same. Also bank
|
| 1400 |
-
// objects are themselves unreachable (LoadSpriteEx is trapped).
|
| 1401 |
-
// - `FrameCache__Allocate` (0x4FA950): only callers were the
|
| 1402 |
-
// two GetFrameForBlit helpers above.
|
| 1403 |
-
// - `DisplayBitGrid::SetExternalBuffer` (0x4F6470): only caller
|
| 1404 |
-
// was `SpriteBank__GetFrameForBlit`.
|
| 1405 |
-
//
|
| 1406 |
-
// `Sprite_LZSS_Decode` (0x5B29E0) is NOT trapped — it has
|
| 1407 |
-
// independent callers (`IMG_Decode`, `FUN_0048EE32`) reachable
|
| 1408 |
-
// from `DDGame__Constructor`/`SoundEmitter`/`LoadHudAndWeaponSprites`,
|
| 1409 |
-
// all still bridged. Trapping it would break those.
|
| 1410 |
-
hook::install_trap!("Sprite__GetFrameForBlit", va::SPRITE_GET_FRAME_FOR_BLIT);
|
| 1411 |
-
hook::install_trap!(
|
| 1412 |
-
"SpriteBank__GetFrameForBlit",
|
| 1413 |
-
va::SPRITE_BANK_GET_FRAME_FOR_BLIT
|
| 1414 |
-
);
|
| 1415 |
-
hook::install_trap!("FrameCache__Allocate", va::FRAME_CACHE_ALLOCATE);
|
| 1416 |
-
hook::install_trap!(
|
| 1417 |
-
"DisplayBitGrid__SetExternalBuffer",
|
| 1418 |
-
va::DISPLAY_BIT_GRID_SET_EXTERNAL_BUFFER
|
| 1419 |
-
);
|
| 1420 |
-
|
| 1421 |
-
// ── Slot 0 destructor leaves: trap the WA-side originals ──────
|
| 1422 |
-
//
|
| 1423 |
-
// After `vtable_replace!` swapped slot 0 to our Rust impl, the
|
| 1424 |
-
// original `DisplayGfx::DestructorImpl` (0x56A010) is no longer
|
| 1425 |
-
// reached, so its only-callee helpers lose their last live
|
| 1426 |
-
// caller too.
|
| 1427 |
-
//
|
| 1428 |
-
// `TileBitmapSet::Destructor` (0x569BC0) has a *second* xref to
|
| 1429 |
-
// `FUN_0056C2E0` (a tile-bitmap-set realloc helper called only
|
| 1430 |
-
// from `CBitmap__Constructor_Maybe` at 0x56C060). That second
|
| 1431 |
-
// chain is **structurally dead** in shipping WA: 0x56C060 sits
|
| 1432 |
-
// in the DisplayGfx vtable .rdata block at slot index 38 (offset
|
| 1433 |
-
// 0x98 past the vtable base 0x66A218), but no instruction in
|
| 1434 |
-
// WA.exe loads from offset 0x98 of any pointer — verified by
|
| 1435 |
-
// byte-pattern search for `FF 90 98 00 00 00` (CALL [reg+0x98])
|
| 1436 |
-
// and `8B 4? 98` (MOV reg,[reg+0x98]) returning no DisplayGfx
|
| 1437 |
-
// hits. The compiler emitted slots 38/39 of DisplayGfx into
|
| 1438 |
-
// .rdata but the C++ source has no live caller for them — they
|
| 1439 |
-
// are vestigial vtable entries from a removed feature. So
|
| 1440 |
-
// trapping `TileBitmapSet::Destructor` is safe.
|
| 1441 |
-
hook::install_trap!(
|
| 1442 |
-
"DisplayGfx__DestructorImpl",
|
| 1443 |
-
va::DISPLAY_GFX_DESTRUCTOR_IMPL
|
| 1444 |
-
);
|
| 1445 |
-
hook::install_trap!(
|
| 1446 |
-
"DisplayGfx__FreeLayerSpriteTable",
|
| 1447 |
-
va::DISPLAY_GFX_FREE_LAYER_SPRITE_TABLE
|
| 1448 |
-
);
|
| 1449 |
-
hook::install_trap!("TileBitmapSet__Destructor", va::TILE_BITMAP_SET_DESTRUCTOR);
|
| 1450 |
-
}
|
| 1451 |
-
|
| 1452 |
-
Ok(())
|
| 1453 |
-
}
|
| 1454 |
-
|
| 1455 |
-
// ==========================================================================
|
| 1456 |
-
// Combined installation
|
| 1457 |
-
// ==========================================================================
|
| 1458 |
-
|
| 1459 |
-
pub fn install() -> Result<(), String> {
|
| 1460 |
-
install_render_queue()?;
|
| 1461 |
-
install_display()?;
|
| 1462 |
-
Ok(())
|
| 1463 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! DisplayGfx vtable patches — DisplayBase pure-call stubs, headless destructor,
|
| 2 |
+
//! and ported DisplayGfx methods.
|
| 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 |
+
|
| 12 |
+
use openwa_core::address::va;
|
| 13 |
+
use openwa_core::bitgrid::DisplayBitGrid;
|
| 14 |
+
use openwa_core::fixed::Fixed;
|
| 15 |
+
use openwa_core::rebase::rb;
|
| 16 |
+
use openwa_core::render::display::destructor as display_destructor;
|
| 17 |
+
use openwa_core::render::display::vtable::{self as display_vtable_impl, DisplayGfxVtable};
|
| 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;
|
| 24 |
+
|
| 25 |
+
use crate::hook;
|
| 26 |
+
use crate::log_line;
|
| 27 |
+
|
| 28 |
+
/// The _purecall function address (calls abort).
|
| 29 |
+
const PURECALL: u32 = 0x005D_4E16;
|
| 30 |
+
|
| 31 |
+
/// Number of slots in the DisplayBase vtable.
|
| 32 |
+
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,
|
| 41 |
+
) -> *mut DisplayBase {
|
| 42 |
+
let sprite_cache = (*this).sprite_cache;
|
| 43 |
+
if !sprite_cache.is_null() {
|
| 44 |
+
let frame_cache = (*sprite_cache).frame_cache;
|
| 45 |
+
if !frame_cache.is_null() {
|
| 46 |
+
let buf = (*frame_cache).buffer;
|
| 47 |
+
if !buf.is_null() {
|
| 48 |
+
wa_free(buf);
|
| 49 |
+
}
|
| 50 |
+
wa_free(frame_cache);
|
| 51 |
+
}
|
| 52 |
+
wa_free(sprite_cache);
|
| 53 |
+
}
|
| 54 |
+
if flags & 1 != 0 {
|
| 55 |
+
wa_free(this);
|
| 56 |
+
}
|
| 57 |
+
this
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
// No saved originals needed — all paths are fully ported or use direct bridges.
|
| 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 |
+
sprite_flags: u32,
|
| 69 |
+
palette: u32,
|
| 70 |
+
) {
|
| 71 |
+
openwa_core::render::display::blit_sprite::blit_sprite(this, x, y, sprite_flags, palette);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/// Thiscall stub for DisplayGfx::DrawScaledSprite (slot 20).
|
| 75 |
+
/// Implementation in `openwa_core::render::display::blit_sprite`.
|
| 76 |
+
unsafe extern "thiscall" fn draw_scaled_sprite(
|
| 77 |
+
this: *mut DisplayGfx,
|
| 78 |
+
x: Fixed,
|
| 79 |
+
y: Fixed,
|
| 80 |
+
sprite: *mut DisplayBitGrid,
|
| 81 |
+
src_x: i32,
|
| 82 |
+
src_y: i32,
|
| 83 |
+
src_w: i32,
|
| 84 |
+
src_h: i32,
|
| 85 |
+
flags: u32,
|
| 86 |
+
) {
|
| 87 |
+
openwa_core::render::display::blit_sprite::draw_scaled_sprite(
|
| 88 |
+
this, x, y, sprite, src_x, src_y, src_w, src_h, flags,
|
| 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,
|
| 110 |
+
id: u32,
|
| 111 |
+
flag: u32,
|
| 112 |
+
gfx_dir: *mut GfxDir,
|
| 113 |
+
name: *const c_char,
|
| 114 |
+
) -> i32 {
|
| 115 |
+
display_vtable_impl::load_sprite(
|
| 116 |
+
this,
|
| 117 |
+
layer,
|
| 118 |
+
id,
|
| 119 |
+
flag,
|
| 120 |
+
gfx_dir,
|
| 121 |
+
name,
|
| 122 |
+
wa_load_sprite_from_vfs,
|
| 123 |
+
)
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
/// Bridge to LoadSpriteFromVfs (0x4FAAF0).
|
| 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,
|
| 136 |
+
_gfx_dir: *mut GfxDir,
|
| 137 |
+
_name: *const c_char,
|
| 138 |
+
_layer_ctx: *mut openwa_core::render::palette::PaletteContext,
|
| 139 |
+
) -> i32 {
|
| 140 |
+
core::arch::naked_asm!(
|
| 141 |
+
// cdecl: +4=sprite, +8=gfx, +12=name, +16=layer_ctx
|
| 142 |
+
"mov ecx, [esp+8]", // gfx → ECX
|
| 143 |
+
"mov eax, [esp+12]", // name → EAX
|
| 144 |
+
"push dword ptr [esp+16]", // layer_ctx
|
| 145 |
+
"push dword ptr [esp+8]", // sprite (shifted +4 by push)
|
| 146 |
+
"call [{ADDR}]", // RET 0x8 cleans 2 stack params
|
| 147 |
+
"ret",
|
| 148 |
+
ADDR = sym LOAD_SPRITE_FROM_VFS_ADDR,
|
| 149 |
+
);
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
static mut LOAD_SPRITE_FROM_VFS_ADDR: u32 = 0;
|
| 153 |
+
|
| 154 |
+
// GetSpriteFrameForBlit (slot 33) is NOT ported — see the docstring on
|
| 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,
|
| 162 |
+
id: u32,
|
| 163 |
+
gfx_dir: *mut GfxDir,
|
| 164 |
+
name: *const c_char,
|
| 165 |
+
) -> i32 {
|
| 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,
|
| 172 |
+
font_id: i32,
|
| 173 |
+
gfx_dir: *mut GfxDir,
|
| 174 |
+
filename: *const c_char,
|
| 175 |
+
) -> u32 {
|
| 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,
|
| 182 |
+
path: *const c_char,
|
| 183 |
+
char_map: *const c_char,
|
| 184 |
+
palette_value: u32,
|
| 185 |
+
flag: i32,
|
| 186 |
+
) -> u32 {
|
| 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 |
+
|
| 197 |
+
unsafe {
|
| 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;
|
| 205 |
+
for i in 0..VTABLE_SLOTS {
|
| 206 |
+
let slot = vt.add(i);
|
| 207 |
+
if *slot == purecall_addr {
|
| 208 |
+
*slot = noop_addr;
|
| 209 |
+
patched += 1;
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
let _ = log_line(&format!(
|
| 213 |
+
"[Display] Primary: patched {patched}/{VTABLE_SLOTS} _purecall -> no-op"
|
| 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 => display_destructor::display_gfx_destructor,
|
| 231 |
+
get_dimensions => display_vtable_impl::get_dimensions,
|
| 232 |
+
set_layer_color => display_vtable_impl::set_layer_color,
|
| 233 |
+
set_active_layer => display_vtable_impl::set_active_layer,
|
| 234 |
+
get_sprite_info => display_vtable_impl::get_sprite_info,
|
| 235 |
+
draw_text_on_bitmap => display_vtable_impl::draw_text_on_bitmap,
|
| 236 |
+
draw_tiled_bitmap => display_vtable_impl::draw_tiled_bitmap,
|
| 237 |
+
get_font_info => display_vtable_impl::get_font_info,
|
| 238 |
+
get_font_metric => display_vtable_impl::get_font_metric,
|
| 239 |
+
set_font_param => display_vtable_impl::set_font_param,
|
| 240 |
+
draw_polyline => display_vtable_impl::draw_polyline,
|
| 241 |
+
draw_line => display_vtable_impl::draw_line,
|
| 242 |
+
draw_line_clipped => display_vtable_impl::draw_line_clipped,
|
| 243 |
+
draw_pixel_strip => display_vtable_impl::draw_pixel_strip,
|
| 244 |
+
draw_crosshair => display_vtable_impl::draw_crosshair,
|
| 245 |
+
draw_outlined_pixel => display_vtable_impl::draw_outlined_pixel,
|
| 246 |
+
fill_rect => display_vtable_impl::fill_rect,
|
| 247 |
+
draw_via_callback => display_vtable_impl::draw_via_callback,
|
| 248 |
+
draw_tiled_terrain => display_vtable_impl::draw_tiled_terrain,
|
| 249 |
+
flush_render => display_vtable_impl::flush_render,
|
| 250 |
+
set_camera_offset => display_vtable_impl::set_camera_offset,
|
| 251 |
+
set_clip_rect => display_vtable_impl::set_clip_rect,
|
| 252 |
+
is_sprite_loaded => display_vtable_impl::is_sprite_loaded,
|
| 253 |
+
load_sprite => load_sprite,
|
| 254 |
+
draw_scaled_sprite => draw_scaled_sprite,
|
| 255 |
+
set_layer_visibility => display_vtable_impl::set_layer_visibility,
|
| 256 |
+
update_palette => display_vtable_impl::update_palette,
|
| 257 |
+
set_font_palette => display_vtable_impl::set_font_palette,
|
| 258 |
+
slot 19 => blit_sprite,
|
| 259 |
+
get_sprite_frame_for_blit => display_vtable_impl::get_sprite_frame_for_blit,
|
| 260 |
+
load_sprite_by_layer => load_sprite_by_layer,
|
| 261 |
+
load_font => load_font,
|
| 262 |
+
load_font_extension => load_font_extension,
|
| 263 |
+
})?;
|
| 264 |
+
let _ = log_line("[Display] DisplayGfx: patched 32 methods -> Rust");
|
| 265 |
+
|
| 266 |
+
// DisplayGfx::LoadSpriteEx (vtable slot 30) has zero callers in both
|
| 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 |
+
// ── Slot 33 leaf functions: trap the WA-side originals ────────
|
| 273 |
+
//
|
| 274 |
+
// After `vtable_replace!` swapped slot 33 to our Rust impl, the
|
| 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",
|
| 295 |
+
va::SPRITE_BANK_GET_FRAME_FOR_BLIT
|
| 296 |
+
);
|
| 297 |
+
hook::install_trap!("FrameCache__Allocate", va::FRAME_CACHE_ALLOCATE);
|
| 298 |
+
hook::install_trap!(
|
| 299 |
+
"DisplayBitGrid__SetExternalBuffer",
|
| 300 |
+
va::DISPLAY_BIT_GRID_SET_EXTERNAL_BUFFER
|
| 301 |
+
);
|
| 302 |
+
|
| 303 |
+
// ── Slot 0 destructor leaves: trap the WA-side originals ──────
|
| 304 |
+
//
|
| 305 |
+
// After `vtable_replace!` swapped slot 0 to our Rust impl, the
|
| 306 |
+
// original `DisplayGfx::DestructorImpl` (0x56A010) is no longer
|
| 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
|
| 326 |
+
);
|
| 327 |
+
hook::install_trap!(
|
| 328 |
+
"DisplayGfx__FreeLayerSpriteTable",
|
| 329 |
+
va::DISPLAY_GFX_FREE_LAYER_SPRITE_TABLE
|
| 330 |
+
);
|
| 331 |
+
hook::install_trap!("TileBitmapSet__Destructor", va::TILE_BITMAP_SET_DESTRUCTOR);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
Ok(())
|
| 335 |
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Render subsystem hooks — RenderQueue enqueue + DisplayGfx vtable patches.
|
| 2 |
+
//!
|
| 3 |
+
//! Split into two submodules:
|
| 4 |
+
//! - `render_queue`: RenderQueue enqueue hooks and dispatcher
|
| 5 |
+
//! - `display_vtable`: DisplayGfx vtable patches and stubs
|
| 6 |
+
|
| 7 |
+
mod display_vtable;
|
| 8 |
+
mod render_queue;
|
| 9 |
+
|
| 10 |
+
pub fn install() -> Result<(), String> {
|
| 11 |
+
render_queue::install()?;
|
| 12 |
+
display_vtable::install()?;
|
| 13 |
+
Ok(())
|
| 14 |
+
}
|
|
@@ -0,0 +1,478 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! RenderQueue enqueue hooks — full Rust replacements for command enqueueing,
|
| 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 |
+
this: u32,
|
| 26 |
+
y_fixed16: u32,
|
| 27 |
+
source_descriptor: u32,
|
| 28 |
+
flags: u32,
|
| 29 |
+
) {
|
| 30 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 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: Fixed(y_fixed16 as i32),
|
| 38 |
+
source: source_descriptor as *const _,
|
| 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 |
+
this: u32,
|
| 55 |
+
count: u32,
|
| 56 |
+
vertex_ptr: u32,
|
| 57 |
+
param_1: u32,
|
| 58 |
+
) {
|
| 59 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 60 |
+
let total_size = count as usize * 0xC + 0x1C;
|
| 61 |
+
|
| 62 |
+
if let Some(ptr) = q.alloc_raw(total_size) {
|
| 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: param_1,
|
| 69 |
+
};
|
| 70 |
+
core::ptr::copy_nonoverlapping(
|
| 71 |
+
vertex_ptr as *const u8,
|
| 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 |
+
this: u32,
|
| 88 |
+
count: u32,
|
| 89 |
+
vertex_ptr: u32,
|
| 90 |
+
param_1: u32,
|
| 91 |
+
param_2: u32,
|
| 92 |
+
) {
|
| 93 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 94 |
+
let total_size = count as usize * 0xC + 0x20;
|
| 95 |
+
|
| 96 |
+
if let Some(ptr) = q.alloc_raw(total_size) {
|
| 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: param_1,
|
| 103 |
+
color2: param_2,
|
| 104 |
+
};
|
| 105 |
+
core::ptr::copy_nonoverlapping(
|
| 106 |
+
vertex_ptr as *const u8,
|
| 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 |
+
this: u32,
|
| 124 |
+
layer: u32,
|
| 125 |
+
x_pos: u32,
|
| 126 |
+
y_pos: u32,
|
| 127 |
+
color_fg: u32,
|
| 128 |
+
color_bg: u32,
|
| 129 |
+
) {
|
| 130 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 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: Fixed(x_pos as i32),
|
| 139 |
+
y: Fixed(y_pos as i32),
|
| 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 |
+
this: u32,
|
| 154 |
+
y_clip: u32,
|
| 155 |
+
layer: u32,
|
| 156 |
+
x1: u32,
|
| 157 |
+
y1: u32,
|
| 158 |
+
x2: u32,
|
| 159 |
+
y2: u32,
|
| 160 |
+
color: u32,
|
| 161 |
+
) {
|
| 162 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 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: Fixed(x1 as i32).floor(),
|
| 170 |
+
y1: Fixed(y1 as i32).floor(),
|
| 171 |
+
x2: Fixed(x2 as i32).floor(),
|
| 172 |
+
y2: Fixed(y2 as i32).floor(),
|
| 173 |
+
ref_z: Fixed(y_clip as i32).floor().0,
|
| 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: u32,
|
| 187 |
+
this: u32,
|
| 188 |
+
layer: u32,
|
| 189 |
+
x_pos: u32,
|
| 190 |
+
sprite_id: u32,
|
| 191 |
+
frame: u32,
|
| 192 |
+
) {
|
| 193 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 194 |
+
|
| 195 |
+
if let Some(entry) = q.alloc::<DrawSpriteCmd>() {
|
| 196 |
+
*entry = DrawSpriteCmd {
|
| 197 |
+
command_type: command_type::DRAW_SPRITE_GLOBAL,
|
| 198 |
+
layer,
|
| 199 |
+
x: Fixed(x_pos as i32).floor(),
|
| 200 |
+
y: Fixed(y_pos as i32).floor(),
|
| 201 |
+
sprite: SpriteOp(sprite_id),
|
| 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: u32,
|
| 216 |
+
this: u32,
|
| 217 |
+
layer: u32,
|
| 218 |
+
x_pos: u32,
|
| 219 |
+
sprite_id: u32,
|
| 220 |
+
frame: u32,
|
| 221 |
+
) {
|
| 222 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 223 |
+
|
| 224 |
+
if let Some(entry) = q.alloc::<DrawSpriteCmd>() {
|
| 225 |
+
*entry = DrawSpriteCmd {
|
| 226 |
+
command_type: command_type::DRAW_SPRITE_LOCAL,
|
| 227 |
+
layer,
|
| 228 |
+
x: Fixed(x_pos as i32).floor(),
|
| 229 |
+
y: Fixed(y_pos as i32).floor(),
|
| 230 |
+
sprite: SpriteOp(sprite_id),
|
| 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 |
+
this: u32,
|
| 245 |
+
y_clip: u32,
|
| 246 |
+
layer: u32,
|
| 247 |
+
x_pos: u32,
|
| 248 |
+
y_pos: u32,
|
| 249 |
+
sprite_id: u32,
|
| 250 |
+
param_7: u32,
|
| 251 |
+
param_8: u32,
|
| 252 |
+
) {
|
| 253 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 254 |
+
|
| 255 |
+
if let Some(entry) = q.alloc::<DrawSpriteOffsetCmd>() {
|
| 256 |
+
*entry = DrawSpriteOffsetCmd {
|
| 257 |
+
command_type: command_type::DRAW_SPRITE_OFFSET,
|
| 258 |
+
layer,
|
| 259 |
+
flags: sprite_id,
|
| 260 |
+
x: Fixed(x_pos as i32).floor(),
|
| 261 |
+
y: Fixed(y_pos as i32).floor(),
|
| 262 |
+
ref_z: 0,
|
| 263 |
+
ref_z_2: Fixed(y_clip as i32).floor().0,
|
| 264 |
+
sprite: SpriteOp(param_7),
|
| 265 |
+
palette: param_8,
|
| 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 |
+
this: u32,
|
| 279 |
+
y_pos: u32,
|
| 280 |
+
layer: u32,
|
| 281 |
+
x_pos: u32,
|
| 282 |
+
bitmap_ptr: u32,
|
| 283 |
+
param_6: u32,
|
| 284 |
+
param_7: u32,
|
| 285 |
+
param_8: u32,
|
| 286 |
+
param_9: u32,
|
| 287 |
+
) {
|
| 288 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 289 |
+
|
| 290 |
+
if let Some(entry) = q.alloc::<DrawBitmapGlobalCmd>() {
|
| 291 |
+
*entry = DrawBitmapGlobalCmd {
|
| 292 |
+
command_type: command_type::DRAW_BITMAP_GLOBAL,
|
| 293 |
+
layer,
|
| 294 |
+
x: Fixed(x_pos as i32).floor(),
|
| 295 |
+
y: Fixed(y_pos as i32).floor(),
|
| 296 |
+
bitmap: bitmap_ptr as *mut _,
|
| 297 |
+
src_x: 0,
|
| 298 |
+
src_y: param_6 as i32,
|
| 299 |
+
src_w: param_7 as i32,
|
| 300 |
+
src_h: param_8 as i32,
|
| 301 |
+
flags: param_9,
|
| 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 |
+
this: u32,
|
| 315 |
+
y_pos: u32,
|
| 316 |
+
layer: u32,
|
| 317 |
+
x_pos: u32,
|
| 318 |
+
text_ptr: u32,
|
| 319 |
+
param_6: u32,
|
| 320 |
+
param_7: u32,
|
| 321 |
+
param_8: u32,
|
| 322 |
+
) {
|
| 323 |
+
let q = &mut *(this as *mut RenderQueue);
|
| 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: Fixed(x_pos as i32).floor(),
|
| 331 |
+
y: Fixed(y_pos as i32).floor(),
|
| 332 |
+
ref_z: 0,
|
| 333 |
+
ref_z_2: 0,
|
| 334 |
+
bitmap: text_ptr as *mut _,
|
| 335 |
+
src_x: 0,
|
| 336 |
+
src_y: 0,
|
| 337 |
+
src_w: param_6 as i32,
|
| 338 |
+
src_h: param_7 as i32,
|
| 339 |
+
flags: param_8,
|
| 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(task_ptr: u32, style: u32, fill: u32) {
|
| 351 |
+
openwa_core::render::bungee_trail::draw_bungee_trail(task_ptr, style, fill);
|
| 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(task_ptr: u32) {
|
| 369 |
+
openwa_core::render::crosshair_line::draw_crosshair_line(task_ptr);
|
| 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;
|
| 383 |
+
reg = eax; stack_params = 2; ret_bytes = "0x8");
|
| 384 |
+
|
| 385 |
+
unsafe extern "cdecl" fn render_drawing_queue_impl(
|
| 386 |
+
rq: *mut RenderQueue,
|
| 387 |
+
display: *mut DisplayGfx,
|
| 388 |
+
clip: *mut ClipContext,
|
| 389 |
+
) {
|
| 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(
|
| 400 |
+
"EnqueueTiledBitmap",
|
| 401 |
+
va::RQ_ENQUEUE_TILED_BITMAP,
|
| 402 |
+
trampoline_enqueue_tiled_bitmap as *const (),
|
| 403 |
+
)?;
|
| 404 |
+
|
| 405 |
+
let _ = hook::install(
|
| 406 |
+
"DrawLineStrip",
|
| 407 |
+
va::RQ_DRAW_LINE_STRIP,
|
| 408 |
+
trampoline_draw_line_strip as *const (),
|
| 409 |
+
)?;
|
| 410 |
+
|
| 411 |
+
let _ = hook::install(
|
| 412 |
+
"DrawPolygon",
|
| 413 |
+
va::RQ_DRAW_POLYGON,
|
| 414 |
+
trampoline_draw_polygon as *const (),
|
| 415 |
+
)?;
|
| 416 |
+
|
| 417 |
+
let _ = hook::install(
|
| 418 |
+
"DrawCrosshair",
|
| 419 |
+
va::RQ_DRAW_CROSSHAIR,
|
| 420 |
+
trampoline_draw_crosshair as *const (),
|
| 421 |
+
)?;
|
| 422 |
+
|
| 423 |
+
let _ = hook::install(
|
| 424 |
+
"DrawRect",
|
| 425 |
+
va::RQ_DRAW_RECT,
|
| 426 |
+
trampoline_draw_rect as *const (),
|
| 427 |
+
)?;
|
| 428 |
+
|
| 429 |
+
let _ = hook::install(
|
| 430 |
+
"DrawSpriteGlobal",
|
| 431 |
+
va::RQ_DRAW_SPRITE_GLOBAL,
|
| 432 |
+
trampoline_draw_sprite_global as *const (),
|
| 433 |
+
)?;
|
| 434 |
+
|
| 435 |
+
let _ = hook::install(
|
| 436 |
+
"DrawSpriteLocal",
|
| 437 |
+
va::RQ_DRAW_SPRITE_LOCAL,
|
| 438 |
+
trampoline_draw_sprite_local as *const (),
|
| 439 |
+
)?;
|
| 440 |
+
|
| 441 |
+
let _ = hook::install(
|
| 442 |
+
"DrawSpriteOffset",
|
| 443 |
+
va::RQ_DRAW_SPRITE_OFFSET,
|
| 444 |
+
trampoline_draw_sprite_offset as *const (),
|
| 445 |
+
)?;
|
| 446 |
+
|
| 447 |
+
let _ = hook::install(
|
| 448 |
+
"DrawBitmapGlobal",
|
| 449 |
+
va::RQ_DRAW_BITMAP_GLOBAL,
|
| 450 |
+
trampoline_draw_bitmap_global as *const (),
|
| 451 |
+
)?;
|
| 452 |
+
|
| 453 |
+
let _ = hook::install(
|
| 454 |
+
"DrawTextboxLocal",
|
| 455 |
+
va::RQ_DRAW_TEXTBOX_LOCAL,
|
| 456 |
+
trampoline_draw_textbox_local as *const (),
|
| 457 |
+
)?;
|
| 458 |
+
|
| 459 |
+
let _ = hook::install(
|
| 460 |
+
"DrawBungeeTrail",
|
| 461 |
+
va::DRAW_BUNGEE_TRAIL,
|
| 462 |
+
draw_bungee_trail_impl as *const (),
|
| 463 |
+
)?;
|
| 464 |
+
|
| 465 |
+
let _ = hook::install(
|
| 466 |
+
"DrawCrosshairLine",
|
| 467 |
+
va::DRAW_CROSSHAIR_LINE,
|
| 468 |
+
trampoline_draw_crosshair_line as *const (),
|
| 469 |
+
)?;
|
| 470 |
+
|
| 471 |
+
let _ = hook::install(
|
| 472 |
+
"RenderDrawingQueue",
|
| 473 |
+
va::RQ_RENDER_DRAWING_QUEUE,
|
| 474 |
+
trampoline_render_drawing_queue as *const (),
|
| 475 |
+
)?;
|
| 476 |
+
}
|
| 477 |
+
Ok(())
|
| 478 |
+
}
|