refactor: create GfxDir struct, rename GfxHandler → GfxDir throughout
Browse filesNew GfxDir struct (0x19C bytes, #[repr(C)]):
- Typed fields: vtable, bucket_array, fallback_alloc, cache_slots,
index_table, slot_count, loaded, file_handle
- GfxDir::alloc(vtable) constructor replaces wa_malloc+memset+vtable pattern
- Size assertion: size_of::<GfxDir>() == 0x19C
Renamed across entire codebase:
- GfxHandler → GfxDir (struct, module, constants)
- gfx_handler.rs → gfx_dir.rs (module file)
- GFX_HANDLER_VTABLE → GFX_DIR_VTABLE
- GFX_HANDLER_LOAD_DIR → GFX_DIR_LOAD_DIR
- GFX_HANDLER_LOAD_SPRITES → GFX_DIR_LOAD_SPRITES
- DDGameWrapper._field_4c0 → primary_gfx_dir
- DDGameWrapper._field_4c4 → secondary_gfx_dir
- Ghidra: GfxHandler__LoadDir → GfxDir__LoadDir, vtable label updated
Eliminated pointer arithmetic:
- gfx_dir_reset: handler.add(0x04/0x08/0x194/etc.) → gfx.field
- gfx_dir_load_dir: handler.add(4/8/0x194) → gfx.field
- gfx_dir_find_entry: handler.add(4) → gfx.bucket_array
- ddgame.rs: gfx.add(0x198) → (*gfx).file_handle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/address.rs +4 -4
- crates/openwa-core/src/display/gradient.rs +1 -1
- crates/openwa-core/src/engine/ddgame.rs +50 -67
- crates/openwa-core/src/engine/ddgame_wrapper.rs +4 -4
- crates/openwa-core/src/render/{gfx_handler.rs → gfx_dir.rs} +85 -45
- crates/openwa-core/src/render/landscape.rs +2 -2
- crates/openwa-core/src/render/mod.rs +1 -1
- crates/openwa-harness/src/lib.rs +2 -2
- crates/openwa-wormkit/src/replacements/ddgame_init.rs +10 -11
- crates/openwa-wormkit/src/validation/mod.rs +6 -6
|
@@ -345,7 +345,7 @@ pub mod va {
|
|
| 345 |
pub const DDGAME_WRAPPER_LOADING_PROGRESS_TICK: u32 = 0x0057_17A0;
|
| 346 |
/// DDGame__LoadHudAndWeaponSprites (0x53D0E0): loads weapon icons, wind indicators,
|
| 347 |
/// girder sprites, and creates DDGame+0x37C DisplayGfx object.
|
| 348 |
-
/// thiscall(ECX=
|
| 349 |
pub const DDGAME_LOAD_HUD_AND_WEAPON_SPRITES: u32 = 0x0053_D0E0;
|
| 350 |
/// PaletteContext__Init (0x5411A0): initialize palette context free-list.
|
| 351 |
/// usercall(EAX=context), plain RET.
|
|
@@ -359,12 +359,12 @@ pub mod va {
|
|
| 359 |
/// g_GameInfo global — set to current GameInfo* during DDGame construction.
|
| 360 |
pub const G_GAME_INFO: u32 = 0x0077_49A0;
|
| 361 |
/// GfxHandler__LoadSprites_Maybe (0x570B50): loads sprites from GfxHandler.
|
| 362 |
-
pub const
|
| 363 |
/// FUN_00570A90: called before display palette setup (non-headless only).
|
| 364 |
pub const FUN_570A90: u32 = 0x0057_0A90;
|
| 365 |
/// FUN_00570E20: called after GfxHandler initialization.
|
| 366 |
pub const FUN_570E20: u32 = 0x0057_0E20;
|
| 367 |
-
pub const
|
| 368 |
pub const GFX_DIR_FIND_ENTRY: u32 = 0x0056_6520;
|
| 369 |
pub const GFX_DIR_LOAD_IMAGE: u32 = 0x0056_66D0;
|
| 370 |
/// DisplayGfx__Constructor_Maybe (0x4F5E80): stdcall(raw_image) -> DisplayGfx*, RET 0x4.
|
|
@@ -377,7 +377,7 @@ pub mod va {
|
|
| 377 |
// === Vtables ===
|
| 378 |
|
| 379 |
/// GfxHandler vtable (0x66B280).
|
| 380 |
-
pub const
|
| 381 |
/// BitGrid base vtable (0x6640EC): shared by BitGrid, DisplayGfx base, gradient stubs.
|
| 382 |
pub const BIT_GRID_VTABLE: u32 = 0x0066_40EC;
|
| 383 |
/// BitGrid variant vtable (0x664118): used by TaskStateMachine-class objects.
|
|
|
|
| 345 |
pub const DDGAME_WRAPPER_LOADING_PROGRESS_TICK: u32 = 0x0057_17A0;
|
| 346 |
/// DDGame__LoadHudAndWeaponSprites (0x53D0E0): loads weapon icons, wind indicators,
|
| 347 |
/// girder sprites, and creates DDGame+0x37C DisplayGfx object.
|
| 348 |
+
/// thiscall(ECX=gfx_dir_4c0) + 2 stack(ddgame, wrapper_4c4), RET 0x8.
|
| 349 |
pub const DDGAME_LOAD_HUD_AND_WEAPON_SPRITES: u32 = 0x0053_D0E0;
|
| 350 |
/// PaletteContext__Init (0x5411A0): initialize palette context free-list.
|
| 351 |
/// usercall(EAX=context), plain RET.
|
|
|
|
| 359 |
/// g_GameInfo global — set to current GameInfo* during DDGame construction.
|
| 360 |
pub const G_GAME_INFO: u32 = 0x0077_49A0;
|
| 361 |
/// GfxHandler__LoadSprites_Maybe (0x570B50): loads sprites from GfxHandler.
|
| 362 |
+
pub const GFX_DIR_LOAD_SPRITES: u32 = 0x0057_0B50;
|
| 363 |
/// FUN_00570A90: called before display palette setup (non-headless only).
|
| 364 |
pub const FUN_570A90: u32 = 0x0057_0A90;
|
| 365 |
/// FUN_00570E20: called after GfxHandler initialization.
|
| 366 |
pub const FUN_570E20: u32 = 0x0057_0E20;
|
| 367 |
+
pub const GFX_DIR_LOAD_DIR: u32 = 0x0056_63E0;
|
| 368 |
pub const GFX_DIR_FIND_ENTRY: u32 = 0x0056_6520;
|
| 369 |
pub const GFX_DIR_LOAD_IMAGE: u32 = 0x0056_66D0;
|
| 370 |
/// DisplayGfx__Constructor_Maybe (0x4F5E80): stdcall(raw_image) -> DisplayGfx*, RET 0x4.
|
|
|
|
| 377 |
// === Vtables ===
|
| 378 |
|
| 379 |
/// GfxHandler vtable (0x66B280).
|
| 380 |
+
pub const GFX_DIR_VTABLE: u32 = 0x0066_B280;
|
| 381 |
/// BitGrid base vtable (0x6640EC): shared by BitGrid, DisplayGfx base, gradient stubs.
|
| 382 |
pub const BIT_GRID_VTABLE: u32 = 0x0066_40EC;
|
| 383 |
/// BitGrid variant vtable (0x664118): used by TaskStateMachine-class objects.
|
|
@@ -8,7 +8,7 @@
|
|
| 8 |
use crate::address::va;
|
| 9 |
use crate::engine::ddgame::DDGame;
|
| 10 |
use crate::rebase::rb;
|
| 11 |
-
use crate::render::
|
| 12 |
use crate::task::bit_grid::BitGrid;
|
| 13 |
use crate::wa_alloc::{wa_free, wa_malloc};
|
| 14 |
|
|
|
|
| 8 |
use crate::address::va;
|
| 9 |
use crate::engine::ddgame::DDGame;
|
| 10 |
use crate::rebase::rb;
|
| 11 |
+
use crate::render::gfx_dir::call_gfx_find_and_load;
|
| 12 |
use crate::task::bit_grid::BitGrid;
|
| 13 |
use crate::wa_alloc::{wa_free, wa_malloc};
|
| 14 |
|
|
@@ -11,12 +11,10 @@ use crate::engine::game_info::GameInfo;
|
|
| 11 |
use crate::input::keyboard::DDKeyboard;
|
| 12 |
use crate::rebase::rb;
|
| 13 |
// Re-export public GfxHandler functions so existing `engine::ddgame::*` imports keep working.
|
| 14 |
-
use crate::render::
|
| 15 |
-
call_gfx_find_and_load, call_gfx_load_and_wrap, call_gfx_load_dir,
|
| 16 |
-
};
|
| 17 |
-
pub use crate::render::gfx_handler::{
|
| 18 |
-
gfx_dir_find_entry, gfx_handler_load_dir, gfx_resource_create,
|
| 19 |
};
|
|
|
|
| 20 |
use crate::render::landscape::PCLandscape;
|
| 21 |
use crate::render::queue::RenderQueue;
|
| 22 |
use crate::render::turn_order::TurnOrderWidget;
|
|
@@ -200,7 +198,7 @@ pub struct DDGame {
|
|
| 200 |
pub rng_state_2: u32,
|
| 201 |
/// 0x72F4-0x7307: Unknown
|
| 202 |
pub _unknown_72f4: [u8; 0x7308 - 0x72F4],
|
| 203 |
-
/// 0x7308: Sprite/gfx dimension data (passed to
|
| 204 |
pub gfx_sprite_data: [u8; 0x730C - 0x7308],
|
| 205 |
/// 0x730C-0x731C: 5 GfxDir color entries
|
| 206 |
pub _gfx_color_entries: [u8; 0x7324 - 0x730C],
|
|
@@ -542,7 +540,7 @@ pub fn init_constructor_addrs() {
|
|
| 542 |
LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
|
| 543 |
LOADING_PROGRESS_TICK_ADDR = rb(va::DDGAME_WRAPPER_LOADING_PROGRESS_TICK);
|
| 544 |
}
|
| 545 |
-
crate::render::
|
| 546 |
}
|
| 547 |
|
| 548 |
// ── Typed wrappers for WA stdcall functions ────────────────────────────────
|
|
@@ -566,7 +564,7 @@ unsafe fn wa_load_sprites(
|
|
| 566 |
param4: u32,
|
| 567 |
) {
|
| 568 |
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
|
| 569 |
-
core::mem::transmute(rb(va::
|
| 570 |
f(wrapper, sprite_data, display_flags, param4);
|
| 571 |
}
|
| 572 |
|
|
@@ -638,12 +636,12 @@ unsafe fn wa_init_display_final(display: *mut DDDisplay) {
|
|
| 638 |
}
|
| 639 |
|
| 640 |
/// DDGame__LoadHudAndWeaponSprites (0x53D0E0): load weapon icons and HUD sprites.
|
| 641 |
-
/// thiscall(ECX=
|
| 642 |
#[cfg(target_arch = "x86")]
|
| 643 |
-
unsafe fn wa_load_hud_sprites(
|
| 644 |
let f: unsafe extern "thiscall" fn(*mut u8, *mut DDGame, *mut u8) =
|
| 645 |
core::mem::transmute(rb(va::DDGAME_LOAD_HUD_AND_WEAPON_SPRITES) as usize);
|
| 646 |
-
f(
|
| 647 |
}
|
| 648 |
|
| 649 |
/// Create and initialize DDGame, matching DDGame__Constructor (0x56E220).
|
|
@@ -744,14 +742,12 @@ unsafe fn init_graphics_and_resources(
|
|
| 744 |
let ddgame = (*wrapper).ddgame;
|
| 745 |
let fopen: unsafe extern "cdecl" fn(*const u8, *const u8) -> *mut u8 =
|
| 746 |
core::mem::transmute(rb(va::WA_FOPEN) as usize);
|
| 747 |
-
let
|
| 748 |
|
| 749 |
-
// ──
|
| 750 |
-
let gfx1 =
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
(*wrapper)._field_4c0 = gfx1;
|
| 754 |
-
(*wrapper)._field_4c4 = core::ptr::null_mut();
|
| 755 |
|
| 756 |
// Build path list (order depends on headless + display_flags)
|
| 757 |
let headless = (*game_info).headless_mode as i32;
|
|
@@ -787,9 +783,9 @@ unsafe fn init_graphics_and_resources(
|
|
| 787 |
let mut gfx_loaded_idx = 0u32;
|
| 788 |
for (i, path) in paths.iter().enumerate() {
|
| 789 |
let fp = fopen(path.as_ptr(), b"rb\0".as_ptr());
|
| 790 |
-
|
| 791 |
if !fp.is_null()
|
| 792 |
-
&& call_gfx_load_dir(gfx1, crate::render::
|
| 793 |
{
|
| 794 |
gfx_loaded_idx = i as u32;
|
| 795 |
break;
|
|
@@ -806,7 +802,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 806 |
0
|
| 807 |
};
|
| 808 |
|
| 809 |
-
// ──
|
| 810 |
let game_version = (*game_info).game_version;
|
| 811 |
let threshold = if (*wrapper).gfx_mode != 0 { 33 } else { -2i32 };
|
| 812 |
if game_version < threshold {
|
|
@@ -814,21 +810,16 @@ unsafe fn init_graphics_and_resources(
|
|
| 814 |
let mut gfx_c_path = *b"data\\Gfx\\GfxC_3_0.dir\0";
|
| 815 |
gfx_c_path[14] = c_digit;
|
| 816 |
|
| 817 |
-
let gfx2 =
|
| 818 |
-
|
| 819 |
-
*(gfx2 as *mut u32) = gfx_handler_vtable;
|
| 820 |
-
(*wrapper)._field_4c4 = gfx2;
|
| 821 |
|
| 822 |
let fp = fopen(gfx_c_path.as_ptr(), b"rb\0".as_ptr());
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
{
|
| 827 |
let fp2 = fopen(b"data\\Gfx\\Gfx.dir\0".as_ptr(), b"rb\0".as_ptr());
|
| 828 |
-
|
| 829 |
-
if fp2.is_null()
|
| 830 |
-
|| call_gfx_load_dir(gfx2, crate::render::gfx_handler::gfx_load_dir_addr()) == 0
|
| 831 |
-
{
|
| 832 |
panic!("DDGame: couldn't open secondary Gfx.dir");
|
| 833 |
}
|
| 834 |
}
|
|
@@ -840,16 +831,9 @@ unsafe fn init_graphics_and_resources(
|
|
| 840 |
call_usercall_eax(wrapper, FUN_570A90_ADDR);
|
| 841 |
}
|
| 842 |
let disp = (*wrapper).display;
|
| 843 |
-
let
|
| 844 |
DDDisplay::set_layer_color(disp, 1, 0xFE);
|
| 845 |
-
DDDisplay::load_sprite(
|
| 846 |
-
disp,
|
| 847 |
-
1,
|
| 848 |
-
1,
|
| 849 |
-
0,
|
| 850 |
-
gfx_handler,
|
| 851 |
-
rb(va::STR_CDROM_SPR) as *const u8,
|
| 852 |
-
);
|
| 853 |
DDDisplay::set_layer_visibility(disp, 1, -100);
|
| 854 |
|
| 855 |
// Palette slot range init
|
|
@@ -880,7 +864,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 880 |
if (*wrapper).gfx_mode != 0 {
|
| 881 |
// The layer_ctx is used as the output buffer, not a plain stack alloc.
|
| 882 |
let res = gfx_resource_create(
|
| 883 |
-
(*wrapper).
|
| 884 |
rb(va::STR_COLOURS_IMG) as *const u8,
|
| 885 |
layer_ctx,
|
| 886 |
);
|
|
@@ -906,7 +890,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 906 |
}
|
| 907 |
|
| 908 |
// ── Secondary GfxDir object (DDGame+0x2C, conditional) ──
|
| 909 |
-
if !(*wrapper).
|
| 910 |
let gfxdir2 = wa_malloc(0x70C);
|
| 911 |
core::ptr::write_bytes(gfxdir2, 0, 0x70C);
|
| 912 |
*(gfxdir2 as *mut u16) = 1;
|
|
@@ -952,14 +936,13 @@ unsafe fn init_graphics_and_resources(
|
|
| 952 |
}
|
| 953 |
}
|
| 954 |
|
| 955 |
-
// ── GfxResource: thiscall(ECX=
|
| 956 |
let gfx_resource: *mut u8;
|
| 957 |
{
|
| 958 |
-
let
|
| 959 |
let out_buf = wa_malloc(0x900);
|
| 960 |
core::ptr::write_bytes(out_buf, 0, 0x900);
|
| 961 |
-
gfx_resource =
|
| 962 |
-
gfx_resource_create(gfx_handler, rb(va::STR_MASKS_IMG) as *const u8, out_buf);
|
| 963 |
}
|
| 964 |
|
| 965 |
// ── PCLandscape (alloc 0xB44, stdcall 11 params) ──
|
|
@@ -1054,7 +1037,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1054 |
|
| 1055 |
// ── Arrow sprites + collision regions (32 iterations) ──
|
| 1056 |
{
|
| 1057 |
-
let
|
| 1058 |
|
| 1059 |
for i in 0u32..32 {
|
| 1060 |
// Format "arrow%02u.img\0" into stack buffer
|
|
@@ -1064,25 +1047,25 @@ unsafe fn init_graphics_and_resources(
|
|
| 1064 |
|
| 1065 |
let layer_ctx = DDDisplay::set_active_layer((*ddgame).display, 1);
|
| 1066 |
|
| 1067 |
-
let entry = gfx_dir_find_entry(name_buf.as_ptr(),
|
| 1068 |
|
| 1069 |
let sprite: *mut u8;
|
| 1070 |
if !entry.is_null() {
|
| 1071 |
-
// Try
|
| 1072 |
-
let gfx_vt = *(
|
| 1073 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 1074 |
core::mem::transmute(*gfx_vt.add(2));
|
| 1075 |
let entry_val = *(entry.add(4) as *const u32);
|
| 1076 |
-
let cached = load_cached(
|
| 1077 |
if !cached.is_null() {
|
| 1078 |
sprite = wa_displaygfx_ctor(cached);
|
| 1079 |
} else {
|
| 1080 |
// Fallback: load from file via GfxDir__LoadImage + IMG_Decode
|
| 1081 |
-
sprite = call_gfx_load_and_wrap(
|
| 1082 |
}
|
| 1083 |
} else {
|
| 1084 |
// Entry not found — try direct file load
|
| 1085 |
-
sprite = call_gfx_load_and_wrap(
|
| 1086 |
}
|
| 1087 |
|
| 1088 |
// Store arrow sprite at DDGame+0x38+i*4
|
|
@@ -1124,7 +1107,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1124 |
let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
|
| 1125 |
core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
|
| 1126 |
(*ddgame).arrow_gfxdirs[i as usize] =
|
| 1127 |
-
gfx_resource_create(
|
| 1128 |
}
|
| 1129 |
}
|
| 1130 |
}
|
|
@@ -1200,12 +1183,12 @@ unsafe fn init_graphics_and_resources(
|
|
| 1200 |
|
| 1201 |
// ── Sprite resource loading via DDGameWrapper vtable[0] ──
|
| 1202 |
// DDNetGameWrapper__LoadResourceList: thiscall(ECX=wrapper) +
|
| 1203 |
-
// 5 stack params (layer,
|
| 1204 |
{
|
| 1205 |
let landscape_ptr = (*wrapper).landscape;
|
| 1206 |
-
let water_layer = (*landscape_ptr).
|
| 1207 |
-
let land_layer = (*landscape_ptr).
|
| 1208 |
-
let
|
| 1209 |
|
| 1210 |
let wrapper_vt = *(wrapper as *const *const u32);
|
| 1211 |
let load_resource_list: unsafe extern "thiscall" fn(
|
|
@@ -1220,7 +1203,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1220 |
load_resource_list(
|
| 1221 |
wrapper,
|
| 1222 |
1,
|
| 1223 |
-
|
| 1224 |
rb(va::SPRITE_RESOURCE_BASE_PATH) as *const u8, // base path
|
| 1225 |
rb(va::SPRITE_RESOURCE_TABLE_1) as *const u8,
|
| 1226 |
0x1D88, // table size
|
|
@@ -1233,7 +1216,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1233 |
load_resource_list(
|
| 1234 |
wrapper,
|
| 1235 |
1,
|
| 1236 |
-
|
| 1237 |
rb(va::SPRITE_RESOURCE_BASE_PATH) as *const u8,
|
| 1238 |
rb(va::SPRITE_RESOURCE_TABLE_2) as *const u8,
|
| 1239 |
0x18,
|
|
@@ -1302,7 +1285,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1302 |
}
|
| 1303 |
|
| 1304 |
// ── DDGame__LoadHudAndWeaponSprites (0x53D0E0) ──
|
| 1305 |
-
wa_load_hud_sprites(
|
| 1306 |
|
| 1307 |
// ── Loading progress ticks (2 of 4 — after LoadHudAndWeaponSprites) ──
|
| 1308 |
call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
|
@@ -1321,12 +1304,12 @@ unsafe fn init_graphics_and_resources(
|
|
| 1321 |
}
|
| 1322 |
|
| 1323 |
// ── Release primary GfxHandler (vtable[3] = release, param 1 = free) ──
|
| 1324 |
-
let
|
| 1325 |
-
if !
|
| 1326 |
-
let gfx_vt = *(
|
| 1327 |
let release: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 1328 |
core::mem::transmute(*gfx_vt.add(3));
|
| 1329 |
-
release(
|
| 1330 |
}
|
| 1331 |
|
| 1332 |
if !is_headless {
|
|
|
|
| 11 |
use crate::input::keyboard::DDKeyboard;
|
| 12 |
use crate::rebase::rb;
|
| 13 |
// Re-export public GfxHandler functions so existing `engine::ddgame::*` imports keep working.
|
| 14 |
+
use crate::render::gfx_dir::{
|
| 15 |
+
call_gfx_find_and_load, call_gfx_load_and_wrap, call_gfx_load_dir, GfxDir,
|
|
|
|
|
|
|
|
|
|
| 16 |
};
|
| 17 |
+
pub use crate::render::gfx_dir::{gfx_dir_find_entry, gfx_dir_load_dir, gfx_resource_create};
|
| 18 |
use crate::render::landscape::PCLandscape;
|
| 19 |
use crate::render::queue::RenderQueue;
|
| 20 |
use crate::render::turn_order::TurnOrderWidget;
|
|
|
|
| 198 |
pub rng_state_2: u32,
|
| 199 |
/// 0x72F4-0x7307: Unknown
|
| 200 |
pub _unknown_72f4: [u8; 0x7308 - 0x72F4],
|
| 201 |
+
/// 0x7308: Sprite/gfx dimension data (passed to GFX_DIR_LOAD_SPRITES).
|
| 202 |
pub gfx_sprite_data: [u8; 0x730C - 0x7308],
|
| 203 |
/// 0x730C-0x731C: 5 GfxDir color entries
|
| 204 |
pub _gfx_color_entries: [u8; 0x7324 - 0x730C],
|
|
|
|
| 540 |
LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
|
| 541 |
LOADING_PROGRESS_TICK_ADDR = rb(va::DDGAME_WRAPPER_LOADING_PROGRESS_TICK);
|
| 542 |
}
|
| 543 |
+
crate::render::gfx_dir::init_addrs();
|
| 544 |
}
|
| 545 |
|
| 546 |
// ── Typed wrappers for WA stdcall functions ────────────────────────────────
|
|
|
|
| 564 |
param4: u32,
|
| 565 |
) {
|
| 566 |
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
|
| 567 |
+
core::mem::transmute(rb(va::GFX_DIR_LOAD_SPRITES) as usize);
|
| 568 |
f(wrapper, sprite_data, display_flags, param4);
|
| 569 |
}
|
| 570 |
|
|
|
|
| 636 |
}
|
| 637 |
|
| 638 |
/// DDGame__LoadHudAndWeaponSprites (0x53D0E0): load weapon icons and HUD sprites.
|
| 639 |
+
/// thiscall(ECX=gfx_dir) + 2 stack(ddgame, secondary_gfx_dir), RET 0x8.
|
| 640 |
#[cfg(target_arch = "x86")]
|
| 641 |
+
unsafe fn wa_load_hud_sprites(gfx_dir: *mut u8, ddgame: *mut DDGame, secondary: *mut u8) {
|
| 642 |
let f: unsafe extern "thiscall" fn(*mut u8, *mut DDGame, *mut u8) =
|
| 643 |
core::mem::transmute(rb(va::DDGAME_LOAD_HUD_AND_WEAPON_SPRITES) as usize);
|
| 644 |
+
f(gfx_dir, ddgame, secondary);
|
| 645 |
}
|
| 646 |
|
| 647 |
/// Create and initialize DDGame, matching DDGame__Constructor (0x56E220).
|
|
|
|
| 742 |
let ddgame = (*wrapper).ddgame;
|
| 743 |
let fopen: unsafe extern "cdecl" fn(*const u8, *const u8) -> *mut u8 =
|
| 744 |
core::mem::transmute(rb(va::WA_FOPEN) as usize);
|
| 745 |
+
let gfx_dir_vtable = rb(va::GFX_DIR_VTABLE) as u32;
|
| 746 |
|
| 747 |
+
// ── GfxDir #1 (primary) ──
|
| 748 |
+
let gfx1 = GfxDir::alloc(gfx_dir_vtable);
|
| 749 |
+
(*wrapper).primary_gfx_dir = gfx1 as *mut u8;
|
| 750 |
+
(*wrapper).secondary_gfx_dir = core::ptr::null_mut();
|
|
|
|
|
|
|
| 751 |
|
| 752 |
// Build path list (order depends on headless + display_flags)
|
| 753 |
let headless = (*game_info).headless_mode as i32;
|
|
|
|
| 783 |
let mut gfx_loaded_idx = 0u32;
|
| 784 |
for (i, path) in paths.iter().enumerate() {
|
| 785 |
let fp = fopen(path.as_ptr(), b"rb\0".as_ptr());
|
| 786 |
+
(*gfx1).file_handle = fp;
|
| 787 |
if !fp.is_null()
|
| 788 |
+
&& call_gfx_load_dir(gfx1 as *mut u8, crate::render::gfx_dir::gfx_load_dir_addr()) != 0
|
| 789 |
{
|
| 790 |
gfx_loaded_idx = i as u32;
|
| 791 |
break;
|
|
|
|
| 802 |
0
|
| 803 |
};
|
| 804 |
|
| 805 |
+
// ── GfxDir #2 (conditional — supplemental sprites for certain game versions) ──
|
| 806 |
let game_version = (*game_info).game_version;
|
| 807 |
let threshold = if (*wrapper).gfx_mode != 0 { 33 } else { -2i32 };
|
| 808 |
if game_version < threshold {
|
|
|
|
| 810 |
let mut gfx_c_path = *b"data\\Gfx\\GfxC_3_0.dir\0";
|
| 811 |
gfx_c_path[14] = c_digit;
|
| 812 |
|
| 813 |
+
let gfx2 = GfxDir::alloc(gfx_dir_vtable);
|
| 814 |
+
(*wrapper).secondary_gfx_dir = gfx2 as *mut u8;
|
|
|
|
|
|
|
| 815 |
|
| 816 |
let fp = fopen(gfx_c_path.as_ptr(), b"rb\0".as_ptr());
|
| 817 |
+
(*gfx2).file_handle = fp;
|
| 818 |
+
let load_addr = crate::render::gfx_dir::gfx_load_dir_addr();
|
| 819 |
+
if fp.is_null() || call_gfx_load_dir(gfx2 as *mut u8, load_addr) == 0 {
|
|
|
|
| 820 |
let fp2 = fopen(b"data\\Gfx\\Gfx.dir\0".as_ptr(), b"rb\0".as_ptr());
|
| 821 |
+
(*gfx2).file_handle = fp2;
|
| 822 |
+
if fp2.is_null() || call_gfx_load_dir(gfx2 as *mut u8, load_addr) == 0 {
|
|
|
|
|
|
|
| 823 |
panic!("DDGame: couldn't open secondary Gfx.dir");
|
| 824 |
}
|
| 825 |
}
|
|
|
|
| 831 |
call_usercall_eax(wrapper, FUN_570A90_ADDR);
|
| 832 |
}
|
| 833 |
let disp = (*wrapper).display;
|
| 834 |
+
let gfx_dir = (*wrapper).primary_gfx_dir;
|
| 835 |
DDDisplay::set_layer_color(disp, 1, 0xFE);
|
| 836 |
+
DDDisplay::load_sprite(disp, 1, 1, 0, gfx_dir, rb(va::STR_CDROM_SPR) as *const u8);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 837 |
DDDisplay::set_layer_visibility(disp, 1, -100);
|
| 838 |
|
| 839 |
// Palette slot range init
|
|
|
|
| 864 |
if (*wrapper).gfx_mode != 0 {
|
| 865 |
// The layer_ctx is used as the output buffer, not a plain stack alloc.
|
| 866 |
let res = gfx_resource_create(
|
| 867 |
+
(*wrapper).primary_gfx_dir,
|
| 868 |
rb(va::STR_COLOURS_IMG) as *const u8,
|
| 869 |
layer_ctx,
|
| 870 |
);
|
|
|
|
| 890 |
}
|
| 891 |
|
| 892 |
// ── Secondary GfxDir object (DDGame+0x2C, conditional) ──
|
| 893 |
+
if !(*wrapper).secondary_gfx_dir.is_null() {
|
| 894 |
let gfxdir2 = wa_malloc(0x70C);
|
| 895 |
core::ptr::write_bytes(gfxdir2, 0, 0x70C);
|
| 896 |
*(gfxdir2 as *mut u16) = 1;
|
|
|
|
| 936 |
}
|
| 937 |
}
|
| 938 |
|
| 939 |
+
// ── GfxResource: thiscall(ECX=gfx_dir) + EAX=name + 1 stack(output), RET 0x4 ──
|
| 940 |
let gfx_resource: *mut u8;
|
| 941 |
{
|
| 942 |
+
let gfx_dir = (*wrapper).primary_gfx_dir;
|
| 943 |
let out_buf = wa_malloc(0x900);
|
| 944 |
core::ptr::write_bytes(out_buf, 0, 0x900);
|
| 945 |
+
gfx_resource = gfx_resource_create(gfx_dir, rb(va::STR_MASKS_IMG) as *const u8, out_buf);
|
|
|
|
| 946 |
}
|
| 947 |
|
| 948 |
// ── PCLandscape (alloc 0xB44, stdcall 11 params) ──
|
|
|
|
| 1037 |
|
| 1038 |
// ── Arrow sprites + collision regions (32 iterations) ──
|
| 1039 |
{
|
| 1040 |
+
let gfx_dir = (*wrapper).primary_gfx_dir;
|
| 1041 |
|
| 1042 |
for i in 0u32..32 {
|
| 1043 |
// Format "arrow%02u.img\0" into stack buffer
|
|
|
|
| 1047 |
|
| 1048 |
let layer_ctx = DDDisplay::set_active_layer((*ddgame).display, 1);
|
| 1049 |
|
| 1050 |
+
let entry = gfx_dir_find_entry(name_buf.as_ptr(), gfx_dir);
|
| 1051 |
|
| 1052 |
let sprite: *mut u8;
|
| 1053 |
if !entry.is_null() {
|
| 1054 |
+
// Try gfx_dir->vtable[2](entry->field_4)
|
| 1055 |
+
let gfx_vt = *(gfx_dir as *const *const u32);
|
| 1056 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 1057 |
core::mem::transmute(*gfx_vt.add(2));
|
| 1058 |
let entry_val = *(entry.add(4) as *const u32);
|
| 1059 |
+
let cached = load_cached(gfx_dir, entry_val);
|
| 1060 |
if !cached.is_null() {
|
| 1061 |
sprite = wa_displaygfx_ctor(cached);
|
| 1062 |
} else {
|
| 1063 |
// Fallback: load from file via GfxDir__LoadImage + IMG_Decode
|
| 1064 |
+
sprite = call_gfx_load_and_wrap(gfx_dir, name_buf.as_ptr(), layer_ctx);
|
| 1065 |
}
|
| 1066 |
} else {
|
| 1067 |
// Entry not found — try direct file load
|
| 1068 |
+
sprite = call_gfx_load_and_wrap(gfx_dir, name_buf.as_ptr(), layer_ctx);
|
| 1069 |
}
|
| 1070 |
|
| 1071 |
// Store arrow sprite at DDGame+0x38+i*4
|
|
|
|
| 1107 |
let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
|
| 1108 |
core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
|
| 1109 |
(*ddgame).arrow_gfxdirs[i as usize] =
|
| 1110 |
+
gfx_resource_create(gfx_dir, core::ptr::null_mut());
|
| 1111 |
}
|
| 1112 |
}
|
| 1113 |
}
|
|
|
|
| 1183 |
|
| 1184 |
// ── Sprite resource loading via DDGameWrapper vtable[0] ──
|
| 1185 |
// DDNetGameWrapper__LoadResourceList: thiscall(ECX=wrapper) +
|
| 1186 |
+
// 5 stack params (layer, gfx_dir, base_path, data_table, table_size)
|
| 1187 |
{
|
| 1188 |
let landscape_ptr = (*wrapper).landscape;
|
| 1189 |
+
let water_layer = (*landscape_ptr).water_gfx_dir;
|
| 1190 |
+
let land_layer = (*landscape_ptr).level_gfx_dir;
|
| 1191 |
+
let gfx_dir = (*wrapper).primary_gfx_dir;
|
| 1192 |
|
| 1193 |
let wrapper_vt = *(wrapper as *const *const u32);
|
| 1194 |
let load_resource_list: unsafe extern "thiscall" fn(
|
|
|
|
| 1203 |
load_resource_list(
|
| 1204 |
wrapper,
|
| 1205 |
1,
|
| 1206 |
+
gfx_dir,
|
| 1207 |
rb(va::SPRITE_RESOURCE_BASE_PATH) as *const u8, // base path
|
| 1208 |
rb(va::SPRITE_RESOURCE_TABLE_1) as *const u8,
|
| 1209 |
0x1D88, // table size
|
|
|
|
| 1216 |
load_resource_list(
|
| 1217 |
wrapper,
|
| 1218 |
1,
|
| 1219 |
+
gfx_dir,
|
| 1220 |
rb(va::SPRITE_RESOURCE_BASE_PATH) as *const u8,
|
| 1221 |
rb(va::SPRITE_RESOURCE_TABLE_2) as *const u8,
|
| 1222 |
0x18,
|
|
|
|
| 1285 |
}
|
| 1286 |
|
| 1287 |
// ── DDGame__LoadHudAndWeaponSprites (0x53D0E0) ──
|
| 1288 |
+
wa_load_hud_sprites(gfx_dir, ddgame, (*wrapper).secondary_gfx_dir);
|
| 1289 |
|
| 1290 |
// ── Loading progress ticks (2 of 4 — after LoadHudAndWeaponSprites) ──
|
| 1291 |
call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
|
|
|
| 1304 |
}
|
| 1305 |
|
| 1306 |
// ── Release primary GfxHandler (vtable[3] = release, param 1 = free) ──
|
| 1307 |
+
let gfx_dir_4c0 = (*wrapper).primary_gfx_dir;
|
| 1308 |
+
if !gfx_dir_4c0.is_null() {
|
| 1309 |
+
let gfx_vt = *(gfx_dir_4c0 as *const *const u32);
|
| 1310 |
let release: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 1311 |
core::mem::transmute(*gfx_vt.add(3));
|
| 1312 |
+
release(gfx_dir_4c0, 1);
|
| 1313 |
}
|
| 1314 |
|
| 1315 |
if !is_headless {
|
|
@@ -31,10 +31,10 @@ pub struct DDGameWrapper {
|
|
| 31 |
pub ddgame_secondary: *mut u8,
|
| 32 |
/// 0x490-0x4BF: Unknown
|
| 33 |
pub _unknown_490: [u8; 0x30],
|
| 34 |
-
/// 0x4C0:
|
| 35 |
-
pub
|
| 36 |
-
/// 0x4C4:
|
| 37 |
-
pub
|
| 38 |
/// 0x4C8: Graphics mode flag (DWORD index 0x132)
|
| 39 |
pub gfx_mode: u32,
|
| 40 |
/// 0x4CC: PCLandscape object pointer (DWORD index 0x133)
|
|
|
|
| 31 |
pub ddgame_secondary: *mut u8,
|
| 32 |
/// 0x490-0x4BF: Unknown
|
| 33 |
pub _unknown_490: [u8; 0x30],
|
| 34 |
+
/// 0x4C0: Primary GfxDir — main sprite archive (Gfx.dir / Gfx0.dir / Gfx1.dir).
|
| 35 |
+
pub primary_gfx_dir: *mut u8,
|
| 36 |
+
/// 0x4C4: Secondary GfxDir — supplemental sprites (GfxC_3_0.dir), conditional on game version.
|
| 37 |
+
pub secondary_gfx_dir: *mut u8,
|
| 38 |
/// 0x4C8: Graphics mode flag (DWORD index 0x132)
|
| 39 |
pub gfx_mode: u32,
|
| 40 |
/// 0x4CC: PCLandscape object pointer (DWORD index 0x133)
|
|
@@ -1,15 +1,61 @@
|
|
| 1 |
-
//!
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
use crate::address::va;
|
| 4 |
use crate::rebase::rb;
|
| 5 |
use crate::wa_alloc::wa_malloc;
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
/// Entry in a GfxDir hash bucket linked list.
|
| 8 |
/// Each entry maps a name string to a cached resource value.
|
| 9 |
#[repr(C)]
|
| 10 |
pub struct GfxDirEntry {
|
| 11 |
pub next: *mut GfxDirEntry,
|
| 12 |
-
/// Passed to
|
| 13 |
pub value: u32,
|
| 14 |
pub _unknown_08: u32,
|
| 15 |
// +0x0C: null-terminated name (variable-length, not in struct)
|
|
@@ -43,7 +89,7 @@ unsafe fn gfx_dir_hash(name: *const u8) -> u32 {
|
|
| 43 |
|
| 44 |
/// Pure Rust implementation of GfxDir__FindEntry (0x566520).
|
| 45 |
///
|
| 46 |
-
/// Convention: usercall(EAX=name) + 1 stack(
|
| 47 |
///
|
| 48 |
/// Looks up a name in the GfxHandler's hash table. Names are case-insensitive.
|
| 49 |
/// Supports `|`-separated fallback names (e.g. "path\\file.img|fallback.img").
|
|
@@ -55,10 +101,10 @@ unsafe fn gfx_dir_hash(name: *const u8) -> u32 {
|
|
| 55 |
/// - entry+0x0C: name string (null-terminated, lowercase)
|
| 56 |
///
|
| 57 |
/// # Safety
|
| 58 |
-
/// `
|
| 59 |
/// `name` must be a valid null-terminated C string.
|
| 60 |
#[cfg(target_arch = "x86")]
|
| 61 |
-
pub unsafe fn gfx_dir_find_entry(name: *const u8,
|
| 62 |
let mut current_name = name;
|
| 63 |
|
| 64 |
loop {
|
|
@@ -106,8 +152,8 @@ pub unsafe fn gfx_dir_find_entry(name: *const u8, gfx_handler: *mut u8) -> *mut
|
|
| 106 |
let bucket = gfx_dir_hash(buf.as_ptr());
|
| 107 |
|
| 108 |
// Walk linked list in hash bucket
|
| 109 |
-
|
| 110 |
-
let bucket_array =
|
| 111 |
let mut entry = *bucket_array.add(bucket as usize) as *mut GfxDirEntry;
|
| 112 |
|
| 113 |
while !entry.is_null() {
|
|
@@ -145,20 +191,20 @@ pub unsafe fn gfx_dir_find_entry(name: *const u8, gfx_handler: *mut u8) -> *mut
|
|
| 145 |
/// Pure Rust implementation of FUN_5665F0 (GfxHandler reset).
|
| 146 |
///
|
| 147 |
/// Convention: usercall(ESI=handler), plain RET. Called at start of LoadDir.
|
| 148 |
-
unsafe fn
|
| 149 |
-
*(handler
|
| 150 |
-
|
| 151 |
-
|
|
|
|
| 152 |
|
| 153 |
for i in 0..16u32 {
|
| 154 |
-
//
|
| 155 |
-
*(
|
| 156 |
-
|
| 157 |
-
*(handler.add(0x10C + i as usize * 4) as *mut u32) = i;
|
| 158 |
}
|
| 159 |
|
| 160 |
-
|
| 161 |
-
|
| 162 |
}
|
| 163 |
|
| 164 |
/// Pure Rust implementation of GfxHandler__LoadDir (0x5663E0).
|
|
@@ -180,8 +226,9 @@ unsafe fn gfx_handler_reset(handler: *mut u8) {
|
|
| 180 |
/// # Safety
|
| 181 |
/// `handler` must be a valid GfxHandler with file handle at +0x198.
|
| 182 |
#[cfg(target_arch = "x86")]
|
| 183 |
-
pub unsafe fn
|
| 184 |
-
|
|
|
|
| 185 |
|
| 186 |
let vt = *(handler as *const *const u32);
|
| 187 |
// vtable[0] = read: thiscall(handler, buf, size) -> bytes_read
|
|
@@ -217,12 +264,10 @@ pub unsafe fn gfx_handler_load_dir(handler: *mut u8) -> i32 {
|
|
| 217 |
|
| 218 |
// Try fast path: vtable[2] allocate (memory-maps the data)
|
| 219 |
let data = vt_alloc(handler, alloc_size);
|
| 220 |
-
|
| 221 |
|
| 222 |
if data.is_null() {
|
| 223 |
-
// Fallback: seek past header, then malloc + read entire data block
|
| 224 |
-
// Original calls vt_seek(alloc_size) then WA_MallocMemset(EDI=read_size).
|
| 225 |
-
// WA_MallocMemset allocates (size+3)&~3 + 0x20 and memsets size bytes.
|
| 226 |
vt_seek(handler, alloc_size);
|
| 227 |
|
| 228 |
let read_size = total_file_size - data_size - 4;
|
|
@@ -232,7 +277,7 @@ pub unsafe fn gfx_handler_load_dir(handler: *mut u8) -> i32 {
|
|
| 232 |
return 0;
|
| 233 |
}
|
| 234 |
core::ptr::write_bytes(buf, 0, read_size as usize);
|
| 235 |
-
|
| 236 |
|
| 237 |
let bytes_read = vt_read(handler, buf, read_size);
|
| 238 |
if bytes_read != read_size {
|
|
@@ -240,13 +285,12 @@ pub unsafe fn gfx_handler_load_dir(handler: *mut u8) -> i32 {
|
|
| 240 |
return 0;
|
| 241 |
}
|
| 242 |
|
| 243 |
-
|
| 244 |
-
*(handler.add(8) as *mut u32) = 1;
|
| 245 |
}
|
| 246 |
|
| 247 |
// Fix up relative pointers in the hash table
|
| 248 |
// 1024 buckets at data[0..0x1000], each is a pointer to a linked list node
|
| 249 |
-
let data =
|
| 250 |
let base = data as u32;
|
| 251 |
|
| 252 |
for bucket in 0..1024u32 {
|
|
@@ -276,14 +320,14 @@ pub unsafe fn gfx_handler_load_dir(handler: *mut u8) -> i32 {
|
|
| 276 |
}
|
| 277 |
}
|
| 278 |
|
| 279 |
-
|
| 280 |
|
| 281 |
1 // success
|
| 282 |
}
|
| 283 |
|
| 284 |
/// Pure Rust implementation of GfxResource__Create_Maybe (0x4F6300).
|
| 285 |
///
|
| 286 |
-
/// Convention: usercall(ECX=
|
| 287 |
///
|
| 288 |
/// Looks up `name` in the GfxHandler's directory, tries cached load, wraps
|
| 289 |
/// as DisplayGfx. Falls back to loading the raw image and decoding it.
|
|
@@ -291,20 +335,16 @@ pub unsafe fn gfx_handler_load_dir(handler: *mut u8) -> i32 {
|
|
| 291 |
/// # Safety
|
| 292 |
/// All pointers must be valid WA objects.
|
| 293 |
#[cfg(target_arch = "x86")]
|
| 294 |
-
pub unsafe fn gfx_resource_create(
|
| 295 |
-
gfx_handler: *mut u8,
|
| 296 |
-
name: *const u8,
|
| 297 |
-
output: *mut u8,
|
| 298 |
-
) -> *mut u8 {
|
| 299 |
// 1. Try FindEntry → cached load → DisplayGfx wrap
|
| 300 |
-
let entry = gfx_dir_find_entry(name,
|
| 301 |
if !entry.is_null() {
|
| 302 |
-
//
|
| 303 |
-
let vt = *(
|
| 304 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 305 |
core::mem::transmute(*vt.add(2));
|
| 306 |
let entry_val = (*(entry as *const GfxDirEntry)).value;
|
| 307 |
-
let cached = load_cached(
|
| 308 |
if !cached.is_null() {
|
| 309 |
// DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
|
| 310 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
@@ -314,7 +354,7 @@ pub unsafe fn gfx_resource_create(
|
|
| 314 |
}
|
| 315 |
|
| 316 |
// 2. Fallback: LoadImage → IMG_Decode
|
| 317 |
-
let raw_image = call_gfx_load_image(
|
| 318 |
if raw_image.is_null() {
|
| 319 |
return core::ptr::null_mut();
|
| 320 |
}
|
|
@@ -366,11 +406,11 @@ pub(crate) unsafe fn call_gfx_find_and_load(
|
|
| 366 |
/// Used by arrow sprite loop when GfxDir__FindEntry returns null.
|
| 367 |
#[cfg(target_arch = "x86")]
|
| 368 |
pub(crate) unsafe fn call_gfx_load_and_wrap(
|
| 369 |
-
|
| 370 |
name: *const u8,
|
| 371 |
display_ctx: *mut u8,
|
| 372 |
) -> *mut u8 {
|
| 373 |
-
let image = call_gfx_load_image(
|
| 374 |
if image.is_null() {
|
| 375 |
return core::ptr::null_mut();
|
| 376 |
}
|
|
@@ -387,7 +427,7 @@ pub(crate) unsafe fn call_gfx_load_and_wrap(
|
|
| 387 |
|
| 388 |
static mut GFX_LOAD_DIR_ADDR: u32 = 0;
|
| 389 |
|
| 390 |
-
// GfxDir__LoadImage is usercall(ESI=
|
| 391 |
// Returns raw image pointer or null.
|
| 392 |
static mut GFX_LOAD_IMAGE_ADDR: u32 = 0;
|
| 393 |
|
|
@@ -409,10 +449,10 @@ pub(crate) unsafe fn call_gfx_load_dir(handler: *mut u8, addr: u32) -> i32 {
|
|
| 409 |
|
| 410 |
#[cfg(target_arch = "x86")]
|
| 411 |
#[unsafe(naked)]
|
| 412 |
-
unsafe extern "C" fn call_gfx_load_image(
|
| 413 |
core::arch::naked_asm!(
|
| 414 |
"pushl %esi",
|
| 415 |
-
"movl 8(%esp), %esi", // ESI =
|
| 416 |
"pushl 12(%esp)", // push name (callee cleans via RET 0x4)
|
| 417 |
"calll *({addr})",
|
| 418 |
"popl %esi",
|
|
@@ -427,7 +467,7 @@ unsafe extern "C" fn call_gfx_load_image(_gfx_handler: *mut u8, _name: *const u8
|
|
| 427 |
pub fn init_addrs() {
|
| 428 |
unsafe {
|
| 429 |
GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
|
| 430 |
-
GFX_LOAD_DIR_ADDR = rb(va::
|
| 431 |
}
|
| 432 |
}
|
| 433 |
|
|
|
|
| 1 |
+
//! GfxDir — .dir sprite archive reader.
|
| 2 |
+
//!
|
| 3 |
+
//! A GfxDir manages a hash table loaded from `.dir` files (e.g. `Gfx.dir`,
|
| 4 |
+
//! `Gfx0.dir`). It provides name→resource lookup via a 1024-bucket hash table
|
| 5 |
+
//! and delegates file I/O and caching to vtable methods.
|
| 6 |
|
| 7 |
use crate::address::va;
|
| 8 |
use crate::rebase::rb;
|
| 9 |
use crate::wa_alloc::wa_malloc;
|
| 10 |
|
| 11 |
+
/// .dir archive reader (0x19C bytes, vtable 0x66B280).
|
| 12 |
+
///
|
| 13 |
+
/// Loaded from `.dir` files via `gfx_dir_load`. Contains a 1024-bucket
|
| 14 |
+
/// hash table for name→resource lookups, plus a 16-slot cache.
|
| 15 |
+
#[repr(C)]
|
| 16 |
+
pub struct GfxDir {
|
| 17 |
+
/// 0x000: Vtable pointer (0x66B280).
|
| 18 |
+
pub vtable: u32,
|
| 19 |
+
/// 0x004: Bucket array — 1024 pointers to GfxDirEntry linked lists.
|
| 20 |
+
pub bucket_array: *mut u8,
|
| 21 |
+
/// 0x008: 1 if bucket_array was allocated via malloc fallback (needs free).
|
| 22 |
+
pub fallback_alloc: u32,
|
| 23 |
+
/// 0x00C-0x10B: 16 cache slots (0x10 bytes each, first u32 zeroed on reset).
|
| 24 |
+
pub cache_slots: [u8; 0x100],
|
| 25 |
+
/// 0x10C-0x14B: Index table (16 u32 entries, identity permutation on reset).
|
| 26 |
+
pub index_table: [u32; 16],
|
| 27 |
+
/// 0x14C-0x18B: Unknown padding.
|
| 28 |
+
pub _unknown_14c: [u8; 0x40],
|
| 29 |
+
/// 0x18C: Number of cache slots (always 0x10).
|
| 30 |
+
pub slot_count: u32,
|
| 31 |
+
/// 0x190: Unknown (zeroed on reset).
|
| 32 |
+
pub _unknown_190: u32,
|
| 33 |
+
/// 0x194: Loaded flag (1 after successful gfx_dir_load).
|
| 34 |
+
pub loaded: u32,
|
| 35 |
+
/// 0x198: FILE* handle to the open .dir file.
|
| 36 |
+
pub file_handle: *mut u8,
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
const _: () = assert!(core::mem::size_of::<GfxDir>() == 0x19C);
|
| 40 |
+
|
| 41 |
+
impl GfxDir {
|
| 42 |
+
/// Allocate and initialize a new GfxDir with the given vtable.
|
| 43 |
+
pub unsafe fn alloc(vtable: u32) -> *mut Self {
|
| 44 |
+
let ptr = wa_malloc(core::mem::size_of::<Self>() as u32) as *mut Self;
|
| 45 |
+
if !ptr.is_null() {
|
| 46 |
+
core::ptr::write_bytes(ptr as *mut u8, 0, core::mem::size_of::<Self>());
|
| 47 |
+
(*ptr).vtable = vtable;
|
| 48 |
+
}
|
| 49 |
+
ptr
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
/// Entry in a GfxDir hash bucket linked list.
|
| 54 |
/// Each entry maps a name string to a cached resource value.
|
| 55 |
#[repr(C)]
|
| 56 |
pub struct GfxDirEntry {
|
| 57 |
pub next: *mut GfxDirEntry,
|
| 58 |
+
/// Passed to GfxDir vtable[2] for cached resource lookup.
|
| 59 |
pub value: u32,
|
| 60 |
pub _unknown_08: u32,
|
| 61 |
// +0x0C: null-terminated name (variable-length, not in struct)
|
|
|
|
| 89 |
|
| 90 |
/// Pure Rust implementation of GfxDir__FindEntry (0x566520).
|
| 91 |
///
|
| 92 |
+
/// Convention: usercall(EAX=name) + 1 stack(gfx_dir), RET 0x4.
|
| 93 |
///
|
| 94 |
/// Looks up a name in the GfxHandler's hash table. Names are case-insensitive.
|
| 95 |
/// Supports `|`-separated fallback names (e.g. "path\\file.img|fallback.img").
|
|
|
|
| 101 |
/// - entry+0x0C: name string (null-terminated, lowercase)
|
| 102 |
///
|
| 103 |
/// # Safety
|
| 104 |
+
/// `gfx_dir` must be a valid GfxHandler with initialized hash table at +0x04.
|
| 105 |
/// `name` must be a valid null-terminated C string.
|
| 106 |
#[cfg(target_arch = "x86")]
|
| 107 |
+
pub unsafe fn gfx_dir_find_entry(name: *const u8, gfx_dir: *mut u8) -> *mut u8 {
|
| 108 |
let mut current_name = name;
|
| 109 |
|
| 110 |
loop {
|
|
|
|
| 152 |
let bucket = gfx_dir_hash(buf.as_ptr());
|
| 153 |
|
| 154 |
// Walk linked list in hash bucket
|
| 155 |
+
let gfx = &*(gfx_dir as *const GfxDir);
|
| 156 |
+
let bucket_array = gfx.bucket_array as *const u32;
|
| 157 |
let mut entry = *bucket_array.add(bucket as usize) as *mut GfxDirEntry;
|
| 158 |
|
| 159 |
while !entry.is_null() {
|
|
|
|
| 191 |
/// Pure Rust implementation of FUN_5665F0 (GfxHandler reset).
|
| 192 |
///
|
| 193 |
/// Convention: usercall(ESI=handler), plain RET. Called at start of LoadDir.
|
| 194 |
+
unsafe fn gfx_dir_reset(handler: *mut u8) {
|
| 195 |
+
let gfx = &mut *(handler as *mut GfxDir);
|
| 196 |
+
gfx.bucket_array = core::ptr::null_mut();
|
| 197 |
+
gfx.fallback_alloc = 0;
|
| 198 |
+
gfx.loaded = 0;
|
| 199 |
|
| 200 |
for i in 0..16u32 {
|
| 201 |
+
// Zero first u32 of each cache slot (stride 0x10)
|
| 202 |
+
*(gfx.cache_slots.as_mut_ptr().add(i as usize * 0x10) as *mut u32) = 0;
|
| 203 |
+
gfx.index_table[i as usize] = i;
|
|
|
|
| 204 |
}
|
| 205 |
|
| 206 |
+
gfx._unknown_190 = 0;
|
| 207 |
+
gfx.slot_count = 0x10;
|
| 208 |
}
|
| 209 |
|
| 210 |
/// Pure Rust implementation of GfxHandler__LoadDir (0x5663E0).
|
|
|
|
| 226 |
/// # Safety
|
| 227 |
/// `handler` must be a valid GfxHandler with file handle at +0x198.
|
| 228 |
#[cfg(target_arch = "x86")]
|
| 229 |
+
pub unsafe fn gfx_dir_load_dir(handler: *mut u8) -> i32 {
|
| 230 |
+
gfx_dir_reset(handler);
|
| 231 |
+
let gfx = &mut *(handler as *mut GfxDir);
|
| 232 |
|
| 233 |
let vt = *(handler as *const *const u32);
|
| 234 |
// vtable[0] = read: thiscall(handler, buf, size) -> bytes_read
|
|
|
|
| 264 |
|
| 265 |
// Try fast path: vtable[2] allocate (memory-maps the data)
|
| 266 |
let data = vt_alloc(handler, alloc_size);
|
| 267 |
+
gfx.bucket_array = data;
|
| 268 |
|
| 269 |
if data.is_null() {
|
| 270 |
+
// Fallback: seek past header, then malloc + read entire data block
|
|
|
|
|
|
|
| 271 |
vt_seek(handler, alloc_size);
|
| 272 |
|
| 273 |
let read_size = total_file_size - data_size - 4;
|
|
|
|
| 277 |
return 0;
|
| 278 |
}
|
| 279 |
core::ptr::write_bytes(buf, 0, read_size as usize);
|
| 280 |
+
gfx.bucket_array = buf;
|
| 281 |
|
| 282 |
let bytes_read = vt_read(handler, buf, read_size);
|
| 283 |
if bytes_read != read_size {
|
|
|
|
| 285 |
return 0;
|
| 286 |
}
|
| 287 |
|
| 288 |
+
gfx.fallback_alloc = 1;
|
|
|
|
| 289 |
}
|
| 290 |
|
| 291 |
// Fix up relative pointers in the hash table
|
| 292 |
// 1024 buckets at data[0..0x1000], each is a pointer to a linked list node
|
| 293 |
+
let data = gfx.bucket_array;
|
| 294 |
let base = data as u32;
|
| 295 |
|
| 296 |
for bucket in 0..1024u32 {
|
|
|
|
| 320 |
}
|
| 321 |
}
|
| 322 |
|
| 323 |
+
gfx.loaded = 1;
|
| 324 |
|
| 325 |
1 // success
|
| 326 |
}
|
| 327 |
|
| 328 |
/// Pure Rust implementation of GfxResource__Create_Maybe (0x4F6300).
|
| 329 |
///
|
| 330 |
+
/// Convention: usercall(ECX=gfx_dir, EAX=name) + 1 stack(output), RET 0x4.
|
| 331 |
///
|
| 332 |
/// Looks up `name` in the GfxHandler's directory, tries cached load, wraps
|
| 333 |
/// as DisplayGfx. Falls back to loading the raw image and decoding it.
|
|
|
|
| 335 |
/// # Safety
|
| 336 |
/// All pointers must be valid WA objects.
|
| 337 |
#[cfg(target_arch = "x86")]
|
| 338 |
+
pub unsafe fn gfx_resource_create(gfx_dir: *mut u8, name: *const u8, output: *mut u8) -> *mut u8 {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
// 1. Try FindEntry → cached load → DisplayGfx wrap
|
| 340 |
+
let entry = gfx_dir_find_entry(name, gfx_dir);
|
| 341 |
if !entry.is_null() {
|
| 342 |
+
// gfx_dir->vtable[2](entry->field_4) — cached load
|
| 343 |
+
let vt = *(gfx_dir as *const *const u32);
|
| 344 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 345 |
core::mem::transmute(*vt.add(2));
|
| 346 |
let entry_val = (*(entry as *const GfxDirEntry)).value;
|
| 347 |
+
let cached = load_cached(gfx_dir, entry_val);
|
| 348 |
if !cached.is_null() {
|
| 349 |
// DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
|
| 350 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
|
|
| 354 |
}
|
| 355 |
|
| 356 |
// 2. Fallback: LoadImage → IMG_Decode
|
| 357 |
+
let raw_image = call_gfx_load_image(gfx_dir, name);
|
| 358 |
if raw_image.is_null() {
|
| 359 |
return core::ptr::null_mut();
|
| 360 |
}
|
|
|
|
| 406 |
/// Used by arrow sprite loop when GfxDir__FindEntry returns null.
|
| 407 |
#[cfg(target_arch = "x86")]
|
| 408 |
pub(crate) unsafe fn call_gfx_load_and_wrap(
|
| 409 |
+
gfx_dir: *mut u8,
|
| 410 |
name: *const u8,
|
| 411 |
display_ctx: *mut u8,
|
| 412 |
) -> *mut u8 {
|
| 413 |
+
let image = call_gfx_load_image(gfx_dir, name);
|
| 414 |
if image.is_null() {
|
| 415 |
return core::ptr::null_mut();
|
| 416 |
}
|
|
|
|
| 427 |
|
| 428 |
static mut GFX_LOAD_DIR_ADDR: u32 = 0;
|
| 429 |
|
| 430 |
+
// GfxDir__LoadImage is usercall(ESI=gfx_dir) + 1 stack(name), RET 0x4.
|
| 431 |
// Returns raw image pointer or null.
|
| 432 |
static mut GFX_LOAD_IMAGE_ADDR: u32 = 0;
|
| 433 |
|
|
|
|
| 449 |
|
| 450 |
#[cfg(target_arch = "x86")]
|
| 451 |
#[unsafe(naked)]
|
| 452 |
+
unsafe extern "C" fn call_gfx_load_image(_gfx_dir: *mut u8, _name: *const u8) -> *mut u8 {
|
| 453 |
core::arch::naked_asm!(
|
| 454 |
"pushl %esi",
|
| 455 |
+
"movl 8(%esp), %esi", // ESI = gfx_dir
|
| 456 |
"pushl 12(%esp)", // push name (callee cleans via RET 0x4)
|
| 457 |
"calll *({addr})",
|
| 458 |
"popl %esi",
|
|
|
|
| 467 |
pub fn init_addrs() {
|
| 468 |
unsafe {
|
| 469 |
GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
|
| 470 |
+
GFX_LOAD_DIR_ADDR = rb(va::GFX_DIR_LOAD_DIR);
|
| 471 |
}
|
| 472 |
}
|
| 473 |
|
|
@@ -86,9 +86,9 @@ pub struct PCLandscape {
|
|
| 86 |
pub visible_right: u32,
|
| 87 |
pub visible_bottom: u32,
|
| 88 |
/// 0xB34: GfxHandler for Level.dir (param_1[0x2CD])
|
| 89 |
-
pub
|
| 90 |
/// 0xB38: GfxHandler for Water.dir (param_1[0x2CE])
|
| 91 |
-
pub
|
| 92 |
/// 0xB3C: Remaining control flag
|
| 93 |
pub _unknown_b3c: u32,
|
| 94 |
}
|
|
|
|
| 86 |
pub visible_right: u32,
|
| 87 |
pub visible_bottom: u32,
|
| 88 |
/// 0xB34: GfxHandler for Level.dir (param_1[0x2CD])
|
| 89 |
+
pub level_gfx_dir: *mut u8,
|
| 90 |
/// 0xB38: GfxHandler for Water.dir (param_1[0x2CE])
|
| 91 |
+
pub water_gfx_dir: *mut u8,
|
| 92 |
/// 0xB3C: Remaining control flag
|
| 93 |
pub _unknown_b3c: u32,
|
| 94 |
}
|
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
pub mod
|
| 2 |
pub mod landscape;
|
| 3 |
pub mod queue;
|
| 4 |
pub mod sprite;
|
|
|
|
| 1 |
+
pub mod gfx_dir;
|
| 2 |
pub mod landscape;
|
| 3 |
pub mod queue;
|
| 4 |
pub mod sprite;
|
|
@@ -301,7 +301,7 @@ mod tests {
|
|
| 301 |
("CGameTask", va::CGAMETASK_VTABLE),
|
| 302 |
("CGameTask2", va::CGAMETASK_VTABLE2),
|
| 303 |
("DDGameWrapper", va::DDGAME_WRAPPER_VTABLE),
|
| 304 |
-
("GfxHandler", va::
|
| 305 |
("DisplayGfx", va::DISPLAY_GFX_VTABLE),
|
| 306 |
("PCLandscape", va::PC_LANDSCAPE_VTABLE),
|
| 307 |
("LandscapeShader", va::LANDSCAPE_SHADER_VTABLE),
|
|
@@ -326,7 +326,7 @@ mod tests {
|
|
| 326 |
("CTask", va::CTASK_VTABLE),
|
| 327 |
("CGameTask", va::CGAMETASK_VTABLE),
|
| 328 |
("DDGameWrapper", va::DDGAME_WRAPPER_VTABLE),
|
| 329 |
-
("GfxHandler", va::
|
| 330 |
("DisplayGfx", va::DISPLAY_GFX_VTABLE),
|
| 331 |
("PCLandscape", va::PC_LANDSCAPE_VTABLE),
|
| 332 |
("LandscapeShader", va::LANDSCAPE_SHADER_VTABLE),
|
|
|
|
| 301 |
("CGameTask", va::CGAMETASK_VTABLE),
|
| 302 |
("CGameTask2", va::CGAMETASK_VTABLE2),
|
| 303 |
("DDGameWrapper", va::DDGAME_WRAPPER_VTABLE),
|
| 304 |
+
("GfxHandler", va::GFX_DIR_VTABLE),
|
| 305 |
("DisplayGfx", va::DISPLAY_GFX_VTABLE),
|
| 306 |
("PCLandscape", va::PC_LANDSCAPE_VTABLE),
|
| 307 |
("LandscapeShader", va::LANDSCAPE_SHADER_VTABLE),
|
|
|
|
| 326 |
("CTask", va::CTASK_VTABLE),
|
| 327 |
("CGameTask", va::CGAMETASK_VTABLE),
|
| 328 |
("DDGameWrapper", va::DDGAME_WRAPPER_VTABLE),
|
| 329 |
+
("GfxHandler", va::GFX_DIR_VTABLE),
|
| 330 |
("DisplayGfx", va::DISPLAY_GFX_VTABLE),
|
| 331 |
("PCLandscape", va::PC_LANDSCAPE_VTABLE),
|
| 332 |
("LandscapeShader", va::LANDSCAPE_SHADER_VTABLE),
|
|
@@ -15,7 +15,7 @@ use crate::hook;
|
|
| 15 |
use openwa_core::address::va;
|
| 16 |
use openwa_core::engine::ddgame::{
|
| 17 |
bit_grid_init, ddgame_init_fields, ddgame_init_render_indices, display_layer_color_init,
|
| 18 |
-
gfx_dir_find_entry,
|
| 19 |
};
|
| 20 |
use openwa_core::engine::DDGameWrapper;
|
| 21 |
|
|
@@ -83,10 +83,9 @@ extern "cdecl" fn impl_display_layer_init(wrapper: u32) -> u32 {
|
|
| 83 |
|
| 84 |
// ─── GfxResource__Create_Maybe (0x4F6300) ───────────────────────────────────
|
| 85 |
|
| 86 |
-
extern "cdecl" fn impl_gfx_resource_create(
|
| 87 |
-
let result =
|
| 88 |
-
gfx_resource_create(
|
| 89 |
-
};
|
| 90 |
result as u32
|
| 91 |
}
|
| 92 |
|
|
@@ -96,7 +95,7 @@ unsafe extern "C" fn gfx_resource_create_trampoline() {
|
|
| 96 |
"push edx",
|
| 97 |
"push [esp+8]", // output (stack param)
|
| 98 |
"push eax", // name
|
| 99 |
-
"push ecx", //
|
| 100 |
"call {impl_fn}",
|
| 101 |
"add esp, 12",
|
| 102 |
"pop edx",
|
|
@@ -107,7 +106,7 @@ unsafe extern "C" fn gfx_resource_create_trampoline() {
|
|
| 107 |
|
| 108 |
// ─── GfxDir__FindEntry (0x566520) ────────────────────────────────────────────
|
| 109 |
//
|
| 110 |
-
// Convention: usercall(EAX=name) + 1 stack(
|
| 111 |
|
| 112 |
hook::usercall_trampoline!(
|
| 113 |
fn find_entry_trampoline;
|
|
@@ -118,8 +117,8 @@ hook::usercall_trampoline!(
|
|
| 118 |
|
| 119 |
static FIND_ENTRY_LOG_COUNT: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
|
| 120 |
|
| 121 |
-
extern "cdecl" fn impl_find_entry(name: u32,
|
| 122 |
-
let result = unsafe { gfx_dir_find_entry(name as *const u8,
|
| 123 |
|
| 124 |
// Log first 20 lookups for debugging
|
| 125 |
let count = FIND_ENTRY_LOG_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
|
@@ -151,7 +150,7 @@ hook::usercall_trampoline!(
|
|
| 151 |
);
|
| 152 |
|
| 153 |
extern "cdecl" fn impl_load_dir(handler: u32) -> u32 {
|
| 154 |
-
unsafe {
|
| 155 |
}
|
| 156 |
|
| 157 |
// ─── Hook installation ──────────────────────────────────────────────────────
|
|
@@ -196,7 +195,7 @@ pub fn install() -> Result<(), String> {
|
|
| 196 |
|
| 197 |
hook::install(
|
| 198 |
"GfxHandler__LoadDir",
|
| 199 |
-
va::
|
| 200 |
load_dir_trampoline as *const (),
|
| 201 |
)?;
|
| 202 |
}
|
|
|
|
| 15 |
use openwa_core::address::va;
|
| 16 |
use openwa_core::engine::ddgame::{
|
| 17 |
bit_grid_init, ddgame_init_fields, ddgame_init_render_indices, display_layer_color_init,
|
| 18 |
+
gfx_dir_find_entry, gfx_dir_load_dir, gfx_resource_create, DDGame,
|
| 19 |
};
|
| 20 |
use openwa_core::engine::DDGameWrapper;
|
| 21 |
|
|
|
|
| 83 |
|
| 84 |
// ─── GfxResource__Create_Maybe (0x4F6300) ───────────────────────────────────
|
| 85 |
|
| 86 |
+
extern "cdecl" fn impl_gfx_resource_create(gfx_dir: u32, name: u32, output: u32) -> u32 {
|
| 87 |
+
let result =
|
| 88 |
+
unsafe { gfx_resource_create(gfx_dir as *mut u8, name as *const u8, output as *mut u8) };
|
|
|
|
| 89 |
result as u32
|
| 90 |
}
|
| 91 |
|
|
|
|
| 95 |
"push edx",
|
| 96 |
"push [esp+8]", // output (stack param)
|
| 97 |
"push eax", // name
|
| 98 |
+
"push ecx", // gfx_dir
|
| 99 |
"call {impl_fn}",
|
| 100 |
"add esp, 12",
|
| 101 |
"pop edx",
|
|
|
|
| 106 |
|
| 107 |
// ─── GfxDir__FindEntry (0x566520) ────────────────────────────────────────────
|
| 108 |
//
|
| 109 |
+
// Convention: usercall(EAX=name) + 1 stack(gfx_dir), RET 0x4.
|
| 110 |
|
| 111 |
hook::usercall_trampoline!(
|
| 112 |
fn find_entry_trampoline;
|
|
|
|
| 117 |
|
| 118 |
static FIND_ENTRY_LOG_COUNT: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
|
| 119 |
|
| 120 |
+
extern "cdecl" fn impl_find_entry(name: u32, gfx_dir: u32) -> u32 {
|
| 121 |
+
let result = unsafe { gfx_dir_find_entry(name as *const u8, gfx_dir as *mut u8) };
|
| 122 |
|
| 123 |
// Log first 20 lookups for debugging
|
| 124 |
let count = FIND_ENTRY_LOG_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
|
|
|
| 150 |
);
|
| 151 |
|
| 152 |
extern "cdecl" fn impl_load_dir(handler: u32) -> u32 {
|
| 153 |
+
unsafe { gfx_dir_load_dir(handler as *mut u8) as u32 }
|
| 154 |
}
|
| 155 |
|
| 156 |
// ─── Hook installation ──────────────────────────────────────────────────────
|
|
|
|
| 195 |
|
| 196 |
hook::install(
|
| 197 |
"GfxHandler__LoadDir",
|
| 198 |
+
va::GFX_DIR_LOAD_DIR,
|
| 199 |
load_dir_trampoline as *const (),
|
| 200 |
)?;
|
| 201 |
}
|
|
@@ -116,7 +116,7 @@ fn validate_addresses(result: &mut ValidationResult) {
|
|
| 116 |
va::CGAMETASK_SOUND_EMITTER_VT,
|
| 117 |
),
|
| 118 |
("DDGameWrapper vtable", va::DDGAME_WRAPPER_VTABLE),
|
| 119 |
-
("GfxHandler vtable", va::
|
| 120 |
("DisplayGfx vtable", va::DISPLAY_GFX_VTABLE),
|
| 121 |
("PCLandscape vtable", va::PC_LANDSCAPE_VTABLE),
|
| 122 |
("LandscapeShader vtable", va::LANDSCAPE_SHADER_VTABLE),
|
|
@@ -316,7 +316,7 @@ fn validate_struct_offsets(result: &mut ValidationResult) {
|
|
| 316 |
let _ = log_validation(" DDGameWrapper:");
|
| 317 |
check_offset!(result, DDGameWrapper, vtable, 0x00);
|
| 318 |
check_offset!(result, DDGameWrapper, ddgame, 0x488);
|
| 319 |
-
check_offset!(result, DDGameWrapper,
|
| 320 |
check_offset!(result, DDGameWrapper, landscape, 0x4CC);
|
| 321 |
check_offset!(result, DDGameWrapper, display, 0x4D0);
|
| 322 |
|
|
@@ -825,12 +825,12 @@ fn dump_landscape() {
|
|
| 825 |
land.resource_handle as u32
|
| 826 |
));
|
| 827 |
let _ = log_validation(&format!(
|
| 828 |
-
"
|
| 829 |
-
land.
|
| 830 |
));
|
| 831 |
let _ = log_validation(&format!(
|
| 832 |
-
"
|
| 833 |
-
land.
|
| 834 |
));
|
| 835 |
let _ = log_validation(&format!(
|
| 836 |
" visible_bounds: left={} top={} right={} bottom={}",
|
|
|
|
| 116 |
va::CGAMETASK_SOUND_EMITTER_VT,
|
| 117 |
),
|
| 118 |
("DDGameWrapper vtable", va::DDGAME_WRAPPER_VTABLE),
|
| 119 |
+
("GfxHandler vtable", va::GFX_DIR_VTABLE),
|
| 120 |
("DisplayGfx vtable", va::DISPLAY_GFX_VTABLE),
|
| 121 |
("PCLandscape vtable", va::PC_LANDSCAPE_VTABLE),
|
| 122 |
("LandscapeShader vtable", va::LANDSCAPE_SHADER_VTABLE),
|
|
|
|
| 316 |
let _ = log_validation(" DDGameWrapper:");
|
| 317 |
check_offset!(result, DDGameWrapper, vtable, 0x00);
|
| 318 |
check_offset!(result, DDGameWrapper, ddgame, 0x488);
|
| 319 |
+
check_offset!(result, DDGameWrapper, primary_gfx_dir, 0x4C0);
|
| 320 |
check_offset!(result, DDGameWrapper, landscape, 0x4CC);
|
| 321 |
check_offset!(result, DDGameWrapper, display, 0x4D0);
|
| 322 |
|
|
|
|
| 825 |
land.resource_handle as u32
|
| 826 |
));
|
| 827 |
let _ = log_validation(&format!(
|
| 828 |
+
" level_gfx_dir: 0x{:08X}",
|
| 829 |
+
land.level_gfx_dir as u32
|
| 830 |
));
|
| 831 |
let _ = log_validation(&format!(
|
| 832 |
+
" water_gfx_dir: 0x{:08X}",
|
| 833 |
+
land.water_gfx_dir as u32
|
| 834 |
));
|
| 835 |
let _ = log_validation(&format!(
|
| 836 |
" visible_bounds: left={} top={} right={} bottom={}",
|