feat: port DisplayGfx::Destructor (slot 0) to pure Rust
Browse filesReplaces vtable[0] of DisplayGfx (0x66A218) with a native Rust impl
that mirrors the original DestructorImpl (0x56A010) one-to-one.
The cleanup body lives in the new `display::destructor` module and
ports both helpers along with it:
- `free_layer_sprite_table` β Rust port of 0x56A280: iterates
`sprite_table[1..0x3FF]`, runs `LayerSpriteFrame::Destructor` on
every entry of each sprite's count-prefixed `frame_array`, then
frees the frame array and the LayerSprite itself.
- `layer_sprite_frame_destructor` β Rust port of 0x5732E0: rebinds
the embedded CBitmap vtable and dispatches the surface's
scalar deleting destructor (`vtable[0](1)`) via raw thiscall.
- `tile_bitmap_set_destructor` β Rust port of 0x569BC0 (originally
a usercall on EDI): iterates `bitmap_ptrs[0..count]`, calls
`vtable[0](1)` on each non-null `CBitmap*`, frees `bitmap_ptrs`.
- `free_cbitmap_vec` β the inline CBitmap vec teardown loop:
releases each non-null `CBitmap.surface` via the new
`SurfaceVtable::release` (slot 6) typed binding, then runs the
bitmap's own `vtable[0](1)`.
The main body executes the 7 cleanup steps (rebind vtable / free
sprite table / free tile bitmap set / free CBitmap vec / destroy
3 layer BitGrids / destroy hook vec entries / release renderer
backend / free hook vec data / rebind+free embedded BitGrid /
free CBitmap vec data / chain to DisplayBase::Destructor) in the
original order. Steps 2a-2f are gated on `display_initialized != 0`,
mirroring the disasm at 0x56A040; steps 3-6 always run.
Adds `SurfaceVtable::release` at slot 6 β pure fastcall, no stack
args, writes a result code into the `*FastcallResult` buffer most
callers ignore. The CBitmap vec teardown is its first typed user.
The slot 0 thunk `display_gfx_destructor` is a thin wrapper:
runs the body, then `wa_free(this)` if `flags & 1` (the C++
`delete this` path), and returns `this` per the MSVC ABI.
The original wraps the body in C++ SEH so that step 6 (parent
destructor) can unwind. Our port intentionally omits the SEH frame:
none of the cleanup can throw, and Rust panics aren't C++
exceptions.
Verified headful via `replay-test.ps1 bomber_parachute` β game
exits cleanly with no crash or visual regression. Headless tests
pass but per the porting plan don't exercise slot 0 (headless mode
constructs DisplayBase, not DisplayGfx).
|
@@ -340,6 +340,17 @@ pub struct SurfaceVtable {
|
|
| 340 |
height: i32,
|
| 341 |
bpp: i32,
|
| 342 |
) -> *mut FastcallResult,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
}
|
| 344 |
|
| 345 |
/// Backend-specific surface object created by
|
|
|
|
| 340 |
height: i32,
|
| 341 |
bpp: i32,
|
| 342 |
) -> *mut FastcallResult,
|
| 343 |
+
/// release surface backing storage (slot 6).
|
| 344 |
+
///
|
| 345 |
+
/// Pure fastcall β no stack args. Writes a result code into
|
| 346 |
+
/// `*result.value` (which most callers ignore). Called from the
|
| 347 |
+
/// `DisplayGfx` destructor's CBitmap-vec teardown loop on every
|
| 348 |
+
/// non-null `CBitmap.surface` before the bitmap itself is freed.
|
| 349 |
+
#[slot(6)]
|
| 350 |
+
pub release: unsafe extern "fastcall" fn(
|
| 351 |
+
this: *mut Surface,
|
| 352 |
+
result: *mut FastcallResult,
|
| 353 |
+
) -> *mut FastcallResult,
|
| 354 |
}
|
| 355 |
|
| 356 |
/// Backend-specific surface object created by
|
|
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! `DisplayGfx::Destructor` (slot 0) β pure-Rust port.
|
| 2 |
+
//!
|
| 3 |
+
//! Mirrors the original `DisplayGfx::DestructorImpl` (0x56A010) one-to-one.
|
| 4 |
+
//! See the disassembly in that function for the canonical step layout; the
|
| 5 |
+
//! commit message and the per-step doc comments below summarize the
|
| 6 |
+
//! reasoning. The slot 0 thunk lives in `vtable.rs`.
|
| 7 |
+
//!
|
| 8 |
+
//! # Steps (matching the disasm in 0x56A010)
|
| 9 |
+
//!
|
| 10 |
+
//! 1. Rebind `(*this).vtable` to the most-derived `DisplayGfxVtable` β
|
| 11 |
+
//! standard MSVC C++ destructor pattern. The parent destructor will
|
| 12 |
+
//! rebind to `DisplayBaseVtable` after we chain to it.
|
| 13 |
+
//! 2. If `display_initialized != 0`:
|
| 14 |
+
//! 1. `FreeLayerSpriteTable(this)` β free all 1024 [`LayerSprite`]
|
| 15 |
+
//! slots in `sprite_table[1..0x3FF]`.
|
| 16 |
+
//! 2. Free `tile_bitmap_sets[1]` (the only used slot) plus its
|
| 17 |
+
//! backing buffer.
|
| 18 |
+
//! 3. For every non-null entry in the CBitmap vec, call
|
| 19 |
+
//! `surface.vtable[6]` (release) on its surface, then
|
| 20 |
+
//! `bitmap.vtable[0](1)` (scalar deleting destructor).
|
| 21 |
+
//! 4. Call the layer destructor (`vtable[3](1)`) on `layer_2`,
|
| 22 |
+
//! `layer_1`, `layer_0` β in that order, mirroring MSVC's
|
| 23 |
+
//! reverse-construction destruction order.
|
| 24 |
+
//! 5. For every non-null entry in the FramePostProcessHook vec,
|
| 25 |
+
//! call its destructor `vtable[0](1)`.
|
| 26 |
+
//! 6. Call `g_RenderContext->vtable[7](1)` (release_frame_buffer)
|
| 27 |
+
//! β the renderer-backend release.
|
| 28 |
+
//! 3. Always-run cleanup (also taken when `display_initialized == 0`):
|
| 29 |
+
//! - Free the FramePostProcessHook vec data buffer + zero its
|
| 30 |
+
//! proxy/first/last/end fields.
|
| 31 |
+
//! - Rebind the embedded `BitGrid` at `+0x3DA8` to the base BitGrid
|
| 32 |
+
//! vtable, then free its data buffer if `external_buffer == 0`.
|
| 33 |
+
//! - Free the CBitmap vec data buffer + zero its three pointers.
|
| 34 |
+
//! - Chain to `DisplayBase::Destructor` (still WA-side; bridged).
|
| 35 |
+
//!
|
| 36 |
+
//! The original wraps the body in a C++ SEH frame because step 6 (the
|
| 37 |
+
//! parent destructor) might unwind. We don't need to mirror that β none
|
| 38 |
+
//! of our cleanup can throw, and Rust panics aren't C++ exceptions.
|
| 39 |
+
|
| 40 |
+
use core::ffi::c_void;
|
| 41 |
+
use core::ptr;
|
| 42 |
+
|
| 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};
|
| 49 |
+
use crate::render::display::vtable::{DisplayGfxVtable, DISPLAY_GFX_VTABLE};
|
| 50 |
+
use crate::render::sprite::sprite::{CBitmap, LayerSprite, LayerSpriteFrame};
|
| 51 |
+
use crate::wa_alloc::wa_free;
|
| 52 |
+
|
| 53 |
+
// =============================================================================
|
| 54 |
+
// Slot 0 thunk β the actual vtable[0] entry point.
|
| 55 |
+
// =============================================================================
|
| 56 |
+
|
| 57 |
+
/// `DisplayGfx::Destructor` (vtable[0] thunk, original at 0x569CE0).
|
| 58 |
+
///
|
| 59 |
+
/// Standard MSVC scalar deleting destructor: runs the cleanup body, then
|
| 60 |
+
/// calls `_free(this)` if `flags & 1` is set (i.e. the C++ caller used
|
| 61 |
+
/// `delete this`). Returns `this` per the MSVC ABI.
|
| 62 |
+
#[unsafe(no_mangle)]
|
| 63 |
+
pub unsafe extern "thiscall" fn display_gfx_destructor(
|
| 64 |
+
this: *mut DisplayGfx,
|
| 65 |
+
flags: u32,
|
| 66 |
+
) -> *mut DisplayGfx {
|
| 67 |
+
display_gfx_destructor_impl(this);
|
| 68 |
+
if flags & 1 != 0 {
|
| 69 |
+
wa_free(this);
|
| 70 |
+
}
|
| 71 |
+
this
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
// =============================================================================
|
| 75 |
+
// Body β the actual cleanup logic.
|
| 76 |
+
// =============================================================================
|
| 77 |
+
|
| 78 |
+
unsafe fn display_gfx_destructor_impl(this: *mut DisplayGfx) {
|
| 79 |
+
// Step 1: rebind the vtable to the most-derived DisplayGfx vtable.
|
| 80 |
+
// Original at 0x56A030: `MOV [ESI], 0x66A218`. The parent destructor
|
| 81 |
+
// (DisplayBase::Destructor) will overwrite it with the DisplayBase
|
| 82 |
+
// vtable when we chain to it at the end.
|
| 83 |
+
(*this).base.vtable = rb(DISPLAY_GFX_VTABLE) as *const DisplayGfxVtable;
|
| 84 |
+
|
| 85 |
+
// Step 2: only run if Init() actually completed.
|
| 86 |
+
if (*this).base.display_initialized != 0 {
|
| 87 |
+
// Step 2a: free the LayerSprite[1024] table at +0x3DD4.
|
| 88 |
+
free_layer_sprite_table(this);
|
| 89 |
+
|
| 90 |
+
// Step 2b: free tile_bitmap_sets[1] and its inner pointer table.
|
| 91 |
+
let tile_set = (*this).tile_bitmap_sets[1] as *mut TileBitmapSet;
|
| 92 |
+
if !tile_set.is_null() {
|
| 93 |
+
tile_bitmap_set_destructor(tile_set);
|
| 94 |
+
wa_free(tile_set);
|
| 95 |
+
(*this).tile_bitmap_sets[1] = ptr::null_mut();
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// Step 2c: iterate the CBitmap vec, release surface + destroy bitmap.
|
| 99 |
+
free_cbitmap_vec(this);
|
| 100 |
+
|
| 101 |
+
// Step 2d: destroy the three render-context layer BitGrids in
|
| 102 |
+
// reverse-construction order. Each call is `vtable[3](this, 1)`.
|
| 103 |
+
for layer in [(*this).layer_2, (*this).layer_1, (*this).layer_0] {
|
| 104 |
+
if !layer.is_null() {
|
| 105 |
+
((*(*layer).vtable).destructor)(layer, 1);
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
// Step 2e: destroy every FramePostProcessHook in the vec.
|
| 110 |
+
let mut p = (*this).hook_vec_first as *mut *mut FramePostProcessHook;
|
| 111 |
+
let end = (*this).hook_vec_last as *mut *mut FramePostProcessHook;
|
| 112 |
+
while p != end && !p.is_null() {
|
| 113 |
+
let hook = *p;
|
| 114 |
+
if !hook.is_null() {
|
| 115 |
+
((*(*hook).vtable).destructor)(hook, 1);
|
| 116 |
+
}
|
| 117 |
+
p = p.add(1);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
// Step 2f: release the renderer backend's framebuffer.
|
| 121 |
+
// Original at 0x56A1D6: `g_RenderContext->vtable[7](1)`. The
|
| 122 |
+
// typed binding is `release_frame_buffer(this, &result, 1)`.
|
| 123 |
+
let render_ctx = *(rb(va::G_RENDER_CONTEXT) as *const *mut RenderContext);
|
| 124 |
+
if !render_ctx.is_null() {
|
| 125 |
+
let mut result = FastcallResult::default();
|
| 126 |
+
((*(*render_ctx).vtable).release_frame_buffer)(render_ctx, &mut result, 1);
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
// Step 3: free the hook vec backing buffer + clear pointers.
|
| 131 |
+
let hook_first = (*this).hook_vec_first;
|
| 132 |
+
if !hook_first.is_null() {
|
| 133 |
+
wa_free(hook_first);
|
| 134 |
+
}
|
| 135 |
+
(*this).hook_vec_first = ptr::null_mut();
|
| 136 |
+
(*this).hook_vec_last = ptr::null_mut();
|
| 137 |
+
(*this).hook_vec_end = ptr::null_mut();
|
| 138 |
+
|
| 139 |
+
// Step 4: rebind the embedded BitGrid at +0x3DA8 to the base BitGrid
|
| 140 |
+
// vtable, then conditionally free its data buffer.
|
| 141 |
+
let bg = &raw mut (*this).embedded_bitgrid;
|
| 142 |
+
(*bg).vtable = rb(BIT_GRID_BASE_VTABLE) as *const BitGridBaseVtable;
|
| 143 |
+
if (*bg).external_buffer == 0 {
|
| 144 |
+
// Note: the original frees `data` unconditionally inside the
|
| 145 |
+
// `external_buffer == 0` branch, even if `data` happens to be
|
| 146 |
+
// null β `_free(NULL)` is a no-op so it's safe.
|
| 147 |
+
wa_free((*bg).data);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
// Step 5: free the CBitmap vec backing buffer + clear pointers.
|
| 151 |
+
let bitmap_ptr = (*this).bitmap_ptr;
|
| 152 |
+
if !bitmap_ptr.is_null() {
|
| 153 |
+
wa_free(bitmap_ptr);
|
| 154 |
+
}
|
| 155 |
+
(*this).bitmap_ptr = ptr::null_mut();
|
| 156 |
+
(*this).bitmap_end = ptr::null_mut();
|
| 157 |
+
(*this).bitmap_capacity = ptr::null_mut();
|
| 158 |
+
|
| 159 |
+
// Step 6: chain to the parent class destructor. Still WA-side until
|
| 160 |
+
// someone ports DisplayBase::Destructor too.
|
| 161 |
+
let parent_dtor: unsafe extern "thiscall" fn(*mut DisplayGfx) =
|
| 162 |
+
core::mem::transmute(rb(DISPLAY_BASE_DESTRUCTOR_IMPL) as usize);
|
| 163 |
+
parent_dtor(this);
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
// =============================================================================
|
| 167 |
+
// Step 2a β `DisplayGfx::FreeLayerSpriteTable` (0x56A280)
|
| 168 |
+
// =============================================================================
|
| 169 |
+
|
| 170 |
+
/// Pure Rust port of `DisplayGfx::FreeLayerSpriteTable` (0x56A280).
|
| 171 |
+
///
|
| 172 |
+
/// Iterates `sprite_table[1..0x3FF]` (note: index 0 is reserved/unused).
|
| 173 |
+
/// For each non-null `LayerSprite`:
|
| 174 |
+
/// 1. If the sprite has a `frame_array`, run `LayerSpriteFrame::Destructor`
|
| 175 |
+
/// on every element via the standard MSVC count-prefixed array layout
|
| 176 |
+
/// (`count` lives at `frame_array[-4]`), then free the count-prefixed
|
| 177 |
+
/// block.
|
| 178 |
+
/// 2. Free the `LayerSprite` itself.
|
| 179 |
+
/// 3. Zero the slot.
|
| 180 |
+
unsafe fn free_layer_sprite_table(this: *mut DisplayGfx) {
|
| 181 |
+
for idx in 1usize..0x400 {
|
| 182 |
+
let slot = &raw mut (*this).sprite_table[idx];
|
| 183 |
+
let layer_sprite: *mut LayerSprite = *slot;
|
| 184 |
+
if layer_sprite.is_null() {
|
| 185 |
+
continue;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
let frame_array = (*layer_sprite).frame_array;
|
| 189 |
+
if !frame_array.is_null() {
|
| 190 |
+
// Count-prefix lives at `frame_array[-4]`. The base of the
|
| 191 |
+
// freeable allocation is `frame_array - 4` bytes.
|
| 192 |
+
let count_ptr = (frame_array as *mut u8).sub(4) as *mut u32;
|
| 193 |
+
let count = *count_ptr as usize;
|
| 194 |
+
for i in 0..count {
|
| 195 |
+
layer_sprite_frame_destructor(frame_array.add(i));
|
| 196 |
+
}
|
| 197 |
+
wa_free(count_ptr);
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
wa_free(layer_sprite);
|
| 201 |
+
*slot = ptr::null_mut();
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
/// Pure Rust port of `LayerSpriteFrame::Destructor` (0x5732E0).
|
| 206 |
+
///
|
| 207 |
+
/// Rebinds the embedded CBitmap vtable to the standard CBitmap vtable
|
| 208 |
+
/// (`0x643F64`) β necessary for any post-destruction code that might
|
| 209 |
+
/// dispatch through it β then releases the surface via its `vtable[0](1)`
|
| 210 |
+
/// scalar deleting destructor if non-null.
|
| 211 |
+
unsafe fn layer_sprite_frame_destructor(frame: *mut LayerSpriteFrame) {
|
| 212 |
+
use crate::render::sprite::sprite::CBITMAP_VTABLE_MAYBE;
|
| 213 |
+
|
| 214 |
+
(*frame).bitmap_vtable = rb(CBITMAP_VTABLE_MAYBE);
|
| 215 |
+
let surface = (*frame).surface as *mut c_void;
|
| 216 |
+
if !surface.is_null() {
|
| 217 |
+
// surface->vtable[0](this, 1) β scalar deleting destructor.
|
| 218 |
+
let vtable = *(surface as *const *const *const c_void);
|
| 219 |
+
let dtor: unsafe extern "thiscall" fn(*mut c_void, u32) -> *mut c_void =
|
| 220 |
+
core::mem::transmute(*vtable);
|
| 221 |
+
dtor(surface, 1);
|
| 222 |
+
}
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
// =============================================================================
|
| 226 |
+
// Step 2b β `TileBitmapSet::Destructor` (0x569BC0)
|
| 227 |
+
// =============================================================================
|
| 228 |
+
|
| 229 |
+
/// Pure Rust port of `TileBitmapSet::Destructor` (0x569BC0).
|
| 230 |
+
///
|
| 231 |
+
/// The original takes `this` in EDI (usercall) β we sidestep the calling
|
| 232 |
+
/// convention entirely by porting the body. Iterates
|
| 233 |
+
/// `bitmap_ptrs[0..count]`, calls `vtable[0](1)` on each non-null
|
| 234 |
+
/// `CBitmap*`, then frees the `bitmap_ptrs` array.
|
| 235 |
+
unsafe fn tile_bitmap_set_destructor(set: *mut TileBitmapSet) {
|
| 236 |
+
let count = (*set).count as i32;
|
| 237 |
+
let bitmap_ptrs = (*set).bitmap_ptrs as *mut *mut CBitmap;
|
| 238 |
+
if bitmap_ptrs.is_null() || count <= 0 {
|
| 239 |
+
// Defensive: original asm always reads bitmap_ptrs unconditionally
|
| 240 |
+
// but we guard the (rare) null case. Still free below if non-null.
|
| 241 |
+
} else {
|
| 242 |
+
for i in 0..count as usize {
|
| 243 |
+
let bitmap = *bitmap_ptrs.add(i);
|
| 244 |
+
if !bitmap.is_null() {
|
| 245 |
+
cbitmap_vtable0(bitmap, 1);
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
if !bitmap_ptrs.is_null() {
|
| 250 |
+
wa_free(bitmap_ptrs);
|
| 251 |
+
}
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
// =============================================================================
|
| 255 |
+
// Step 2c β CBitmap vec teardown
|
| 256 |
+
// =============================================================================
|
| 257 |
+
|
| 258 |
+
/// Iterate the `CBitmap*` vec at `+0x3580` (start) / `+0x3584` (end) and
|
| 259 |
+
/// destroy every entry: release the backend surface (slot 6) + run the
|
| 260 |
+
/// scalar deleting destructor (slot 0) on the `CBitmap` itself.
|
| 261 |
+
unsafe fn free_cbitmap_vec(this: *mut DisplayGfx) {
|
| 262 |
+
let start = (*this).bitmap_ptr;
|
| 263 |
+
let end = (*this).bitmap_end;
|
| 264 |
+
if start.is_null() || end <= start {
|
| 265 |
+
return;
|
| 266 |
+
}
|
| 267 |
+
let count = end.offset_from(start) as usize;
|
| 268 |
+
for i in 0..count {
|
| 269 |
+
let bitmap = *start.add(i);
|
| 270 |
+
if bitmap.is_null() {
|
| 271 |
+
continue;
|
| 272 |
+
}
|
| 273 |
+
let surface = (*bitmap).surface;
|
| 274 |
+
if !surface.is_null() {
|
| 275 |
+
// surface->vtable[6](this, &result) β release the backend
|
| 276 |
+
// surface storage. Result code is unused.
|
| 277 |
+
let mut result = FastcallResult::default();
|
| 278 |
+
((*(*surface).vtable).release)(surface, &mut result);
|
| 279 |
+
}
|
| 280 |
+
cbitmap_vtable0(bitmap, 1);
|
| 281 |
+
}
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
/// Call `CBitmap.vtable[0]` (the scalar deleting destructor) β `CBitmap`'s
|
| 285 |
+
/// vtable isn't yet typed in Rust, so we dispatch via a raw transmute. The
|
| 286 |
+
/// vtable is `0x643F64` for the standard tile-cache CBitmap and the
|
| 287 |
+
/// destructor returns `*mut CBitmap` per the MSVC ABI.
|
| 288 |
+
unsafe fn cbitmap_vtable0(bitmap: *mut CBitmap, flags: u32) {
|
| 289 |
+
let vtable = (*bitmap).vtable as *const usize;
|
| 290 |
+
let dtor: unsafe extern "thiscall" fn(*mut CBitmap, u32) -> *mut CBitmap =
|
| 291 |
+
core::mem::transmute(*vtable);
|
| 292 |
+
dtor(bitmap, flags);
|
| 293 |
+
}
|
|
@@ -1,5 +1,6 @@
|
|
| 1 |
pub mod base;
|
| 2 |
pub mod context;
|
|
|
|
| 3 |
pub mod font;
|
| 4 |
pub mod frame_hook;
|
| 5 |
pub mod gfx;
|
|
|
|
| 1 |
pub mod base;
|
| 2 |
pub mod context;
|
| 3 |
+
pub mod destructor;
|
| 4 |
pub mod font;
|
| 5 |
pub mod frame_hook;
|
| 6 |
pub mod gfx;
|
|
@@ -691,6 +691,7 @@ fn install_render_queue() -> Result<(), String> {
|
|
| 691 |
|
| 692 |
use openwa_core::bitgrid::DisplayBitGrid;
|
| 693 |
use openwa_core::fixed::Fixed;
|
|
|
|
| 694 |
use openwa_core::render::display::vtable::{self as display_vtable, DisplayGfxVtable};
|
| 695 |
use openwa_core::render::display::DisplayBase;
|
| 696 |
use openwa_core::render::display::DisplayGfx;
|
|
@@ -1330,6 +1331,7 @@ fn install_display() -> Result<(), String> {
|
|
| 1330 |
|
| 1331 |
// Patch DisplayGfx vtable (0x66A218): replace ported methods with Rust.
|
| 1332 |
vtable_replace!(DisplayGfxVtable, va::DISPLAY_GFX_VTABLE, {
|
|
|
|
| 1333 |
get_dimensions => display_vtable::get_dimensions,
|
| 1334 |
set_layer_color => display_vtable::set_layer_color,
|
| 1335 |
set_active_layer => display_vtable::set_active_layer,
|
|
|
|
| 691 |
|
| 692 |
use openwa_core::bitgrid::DisplayBitGrid;
|
| 693 |
use openwa_core::fixed::Fixed;
|
| 694 |
+
use openwa_core::render::display::destructor as display_destructor;
|
| 695 |
use openwa_core::render::display::vtable::{self as display_vtable, DisplayGfxVtable};
|
| 696 |
use openwa_core::render::display::DisplayBase;
|
| 697 |
use openwa_core::render::display::DisplayGfx;
|
|
|
|
| 1331 |
|
| 1332 |
// Patch DisplayGfx vtable (0x66A218): replace ported methods with Rust.
|
| 1333 |
vtable_replace!(DisplayGfxVtable, va::DISPLAY_GFX_VTABLE, {
|
| 1334 |
+
destructor => display_destructor::display_gfx_destructor,
|
| 1335 |
get_dimensions => display_vtable::get_dimensions,
|
| 1336 |
set_layer_color => display_vtable::set_layer_color,
|
| 1337 |
set_active_layer => display_vtable::set_active_layer,
|