refactor: clean up ddgame.rs — remove dead code, fix bug, reduce logging
Browse filesBug fix:
- ActiveSoundTable memset was AFTER field writes, clobbering ddgame
and counter. Moved memset before field writes.
Dead code removed (-247 lines):
- 4 dead FFI bridge functions (call_init_fields, call_tsm_init,
call_gfx_resource_create, call_gfx_find_entry) — superseded by
pure Rust implementations
- 5 dead statics (INIT_FIELDS_ADDR, FUN_570E20_ADDR, TSM_INIT_ADDR,
GFX_RESOURCE_CREATE_ADDR, GFX_FIND_ENTRY_ADDR)
- 5 dead init_constructor_addrs entries
- Unused import (crate::log::log_line)
- ~43 debug log_line calls (kept only 2 milestone markers)
Code quality:
- Renamed cryptic variables: f914→headless, f374→display_flags,
d778→game_version
- Prefixed unused params with _
- Removed stale development comments
- Fixed 3 memory leaks (landscape_coords_buf, landscape_byte_buf,
stack_local_8 now freed after use)
Zero warnings from openwa-core. Headless replay PASS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -8,7 +8,6 @@ use crate::display::palette::Palette;
|
|
| 8 |
use crate::engine::ddgame_wrapper::DDGameWrapper;
|
| 9 |
use crate::engine::game_info::GameInfo;
|
| 10 |
use crate::input::keyboard::DDKeyboard;
|
| 11 |
-
use crate::log::log_line;
|
| 12 |
use crate::rebase::rb;
|
| 13 |
use crate::render::landscape::PCLandscape;
|
| 14 |
use crate::render::queue::RenderQueue;
|
|
@@ -499,21 +498,11 @@ pub unsafe fn task_state_machine_init(object: *mut u8, param1: u32, width: u32,
|
|
| 499 |
let alloc_size = ((total_size + 3) & !3) + 0x20;
|
| 500 |
let buffer = wa_malloc(alloc_size);
|
| 501 |
|
| 502 |
-
let _ = crate::log::log_line(&format!(
|
| 503 |
-
"[TSM] init: obj=0x{:08X} p1={} w={} h={} stride={} total={} alloc={} buf=0x{:08X}",
|
| 504 |
-
object as u32, param1, width, height, row_stride, total_size, alloc_size, buffer as u32,
|
| 505 |
-
));
|
| 506 |
-
|
| 507 |
if buffer.is_null() {
|
| 508 |
-
let _ = crate::log::log_line("[TSM] WARN: wa_malloc returned null, skipping memset");
|
| 509 |
return;
|
| 510 |
}
|
| 511 |
// Guard against integer overflow producing tiny alloc_size with huge total_size
|
| 512 |
if total_size as usize > alloc_size as usize {
|
| 513 |
-
let _ = crate::log::log_line(&format!(
|
| 514 |
-
"[TSM] OVERFLOW: total_size {} > alloc_size {}, skipping memset",
|
| 515 |
-
total_size, alloc_size,
|
| 516 |
-
));
|
| 517 |
return;
|
| 518 |
}
|
| 519 |
|
|
@@ -853,22 +842,15 @@ pub unsafe fn gfx_resource_create(
|
|
| 853 |
name: *const u8,
|
| 854 |
output: *mut u8,
|
| 855 |
) -> *mut u8 {
|
| 856 |
-
let _ = crate::log::log_line(&format!(
|
| 857 |
-
"[GfxRes] create: handler=0x{:08X} name=0x{:08X} out=0x{:08X}",
|
| 858 |
-
gfx_handler as u32, name as u32, output as u32,
|
| 859 |
-
));
|
| 860 |
// 1. Try FindEntry → cached load → DisplayGfx wrap
|
| 861 |
let entry = gfx_dir_find_entry(name, gfx_handler);
|
| 862 |
-
let _ = crate::log::log_line(&format!("[GfxRes] entry=0x{:08X}", entry as u32));
|
| 863 |
if !entry.is_null() {
|
| 864 |
// gfx_handler->vtable[2](entry->field_4) — cached load
|
| 865 |
let vt = *(gfx_handler as *const *const u32);
|
| 866 |
-
let _ = crate::log::log_line(&format!("[GfxRes] vt=0x{:08X}", vt as u32));
|
| 867 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 868 |
core::mem::transmute(*vt.add(2));
|
| 869 |
let entry_val = *(entry.add(4) as *const u32);
|
| 870 |
let cached = load_cached(gfx_handler, entry_val);
|
| 871 |
-
let _ = crate::log::log_line(&format!("[GfxRes] cached=0x{:08X}", cached as u32));
|
| 872 |
if !cached.is_null() {
|
| 873 |
// DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
|
| 874 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
@@ -878,65 +860,33 @@ pub unsafe fn gfx_resource_create(
|
|
| 878 |
}
|
| 879 |
|
| 880 |
// 2. Fallback: LoadImage → IMG_Decode
|
| 881 |
-
let _ = crate::log::log_line("[GfxRes] fallback: LoadImage");
|
| 882 |
let raw_image = call_gfx_load_image(gfx_handler, name);
|
| 883 |
-
let _ = crate::log::log_line(&format!("[GfxRes] raw_image=0x{:08X}", raw_image as u32));
|
| 884 |
if raw_image.is_null() {
|
| 885 |
return core::ptr::null_mut();
|
| 886 |
}
|
| 887 |
|
| 888 |
// IMG_Decode: stdcall(output, raw_image, 1), RET 0xC
|
| 889 |
-
let _ = crate::log::log_line("[GfxRes] calling IMG_Decode");
|
| 890 |
let decode: unsafe extern "stdcall" fn(*mut u8, *mut u8, i32) -> *mut u8 =
|
| 891 |
core::mem::transmute(rb(va::IMG_DECODE) as usize);
|
| 892 |
let result = decode(output, raw_image, 1);
|
| 893 |
-
let _ = crate::log::log_line(&format!(
|
| 894 |
-
"[GfxRes] IMG_Decode result=0x{:08X}",
|
| 895 |
-
result as u32
|
| 896 |
-
));
|
| 897 |
|
| 898 |
// Release raw image: raw_image->vtable[0](1)
|
| 899 |
let img_vt = *(raw_image as *const *const u32);
|
| 900 |
let fn_ptr = *img_vt;
|
| 901 |
-
let slot_idx = *(raw_image.add(4) as *const u32);
|
| 902 |
-
let stored_handler = *(raw_image.add(8) as *const u32);
|
| 903 |
-
let _ = crate::log::log_line(&format!(
|
| 904 |
-
"[GfxRes] release: raw=0x{:08X} vt=0x{:08X} fn=0x{:08X} slot={} handler=0x{:08X}",
|
| 905 |
-
raw_image as u32, img_vt as u32, fn_ptr as u32, slot_idx, stored_handler,
|
| 906 |
-
));
|
| 907 |
-
let _ = crate::log::log_line(&format!(
|
| 908 |
-
"[GfxRes] handler_usedcnt={} handler_freecnt={}",
|
| 909 |
-
*(gfx_handler.add(0x190) as *const u32),
|
| 910 |
-
*(gfx_handler.add(0x18C) as *const u32),
|
| 911 |
-
));
|
| 912 |
let release: unsafe extern "thiscall" fn(*mut u8, i32) = core::mem::transmute(fn_ptr);
|
| 913 |
-
let _ = crate::log::log_line("[GfxRes] calling release...");
|
| 914 |
release(raw_image, 1);
|
| 915 |
-
let _ = crate::log::log_line("[GfxRes] release done");
|
| 916 |
-
let _ = crate::log::log_line(&format!(
|
| 917 |
-
"[GfxRes] heap-ok raw=0x{:08X} result=0x{:08X}",
|
| 918 |
-
raw_image as u32, result as u32
|
| 919 |
-
));
|
| 920 |
-
let _ = crate::log::log_line("[GfxRes] before return");
|
| 921 |
|
| 922 |
result
|
| 923 |
}
|
| 924 |
|
| 925 |
-
/// Runtime address of DDGame__InitFields (set by init_constructor_addrs()).
|
| 926 |
-
static mut INIT_FIELDS_ADDR: u32 = 0;
|
| 927 |
static mut GFX_LOAD_DIR_ADDR: u32 = 0;
|
| 928 |
|
| 929 |
/// Initialize runtime addresses for the constructor bridges.
|
| 930 |
/// Must be called once at DLL startup (from lib.rs or similar).
|
| 931 |
pub fn init_constructor_addrs() {
|
| 932 |
unsafe {
|
| 933 |
-
INIT_FIELDS_ADDR = rb(va::DDGAME_INIT_FIELDS);
|
| 934 |
SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
|
| 935 |
-
FUN_570E20_ADDR = rb(va::FUN_570E20);
|
| 936 |
FUN_570A90_ADDR = rb(va::FUN_570A90);
|
| 937 |
-
GFX_RESOURCE_CREATE_ADDR = rb(va::GFX_RESOURCE_CREATE);
|
| 938 |
-
TSM_INIT_ADDR = rb(va::TASK_STATE_MACHINE_INIT);
|
| 939 |
-
GFX_FIND_ENTRY_ADDR = rb(va::GFX_DIR_FIND_ENTRY);
|
| 940 |
GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
|
| 941 |
GFX_LOAD_DIR_ADDR = rb(va::GFX_HANDLER_LOAD_DIR);
|
| 942 |
FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
|
|
@@ -945,22 +895,6 @@ pub fn init_constructor_addrs() {
|
|
| 945 |
}
|
| 946 |
}
|
| 947 |
|
| 948 |
-
/// Bridge to DDGame__InitFields (0x526120).
|
| 949 |
-
/// Convention: usercall(EDI=ddgame), plain RET.
|
| 950 |
-
#[cfg(target_arch = "x86")]
|
| 951 |
-
#[unsafe(naked)]
|
| 952 |
-
unsafe extern "C" fn call_init_fields(_ddgame: *mut DDGame) {
|
| 953 |
-
core::arch::naked_asm!(
|
| 954 |
-
"pushl %edi",
|
| 955 |
-
"movl 8(%esp), %edi",
|
| 956 |
-
"calll *({addr})",
|
| 957 |
-
"popl %edi",
|
| 958 |
-
"retl",
|
| 959 |
-
addr = sym INIT_FIELDS_ADDR,
|
| 960 |
-
options(att_syntax),
|
| 961 |
-
);
|
| 962 |
-
}
|
| 963 |
-
|
| 964 |
/// Bridge to DDGame__InitVersionFlags (0x525BE0).
|
| 965 |
/// Convention: stdcall(ddgame_wrapper).
|
| 966 |
#[cfg(target_arch = "x86")]
|
|
@@ -1019,12 +953,6 @@ pub unsafe fn create_ddgame(
|
|
| 1019 |
// Now safe to expose — all fields that concurrent readers check are set.
|
| 1020 |
(*wrapper).ddgame = ddgame;
|
| 1021 |
|
| 1022 |
-
// Verify game_info is set
|
| 1023 |
-
let _ = crate::log::log_line(&format!(
|
| 1024 |
-
"[DDGame] exposed: ddgame=0x{:08X}, game_info=0x{:08X}, net_game=0x{:08X}",
|
| 1025 |
-
ddgame as u32, game_info as u32, net_game as u32,
|
| 1026 |
-
));
|
| 1027 |
-
|
| 1028 |
// ── 5. Set g_GameInfo global ──
|
| 1029 |
*(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
|
| 1030 |
|
|
@@ -1054,9 +982,7 @@ pub unsafe fn create_ddgame(
|
|
| 1054 |
}
|
| 1055 |
|
| 1056 |
// ── 9. InitVersionFlags — sets DDGame+0x7E2E/0x7E2F/0x7E3F ──
|
| 1057 |
-
let _ = crate::log::log_line("[DDGame] calling InitVersionFlags");
|
| 1058 |
call_init_version_flags(wrapper);
|
| 1059 |
-
let _ = crate::log::log_line("[DDGame] calling init_graphics_and_resources");
|
| 1060 |
|
| 1061 |
// ── 10. GfxHandler, landscape, sprites, audio, resources ──
|
| 1062 |
init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
|
|
@@ -1073,14 +999,13 @@ pub unsafe fn create_ddgame(
|
|
| 1073 |
unsafe fn init_graphics_and_resources(
|
| 1074 |
wrapper: *mut DDGameWrapper,
|
| 1075 |
game_info: *mut GameInfo,
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
is_headless: bool,
|
| 1079 |
) {
|
| 1080 |
let ddgame = (*wrapper).ddgame;
|
| 1081 |
let fopen: unsafe extern "cdecl" fn(*const u8, *const u8) -> *mut u8 =
|
| 1082 |
core::mem::transmute(rb(va::WA_FOPEN) as usize);
|
| 1083 |
-
// gfx_load_dir_addr removed — using direct Rust gfx_handler_load_dir() now
|
| 1084 |
let gfx_handler_vtable = rb(0x66B280) as u32; // GfxHandler__vtable (from asm: MOV [ESI], 0x66B280)
|
| 1085 |
|
| 1086 |
// ── GfxHandler #1 (primary) ──
|
|
@@ -1090,11 +1015,11 @@ unsafe fn init_graphics_and_resources(
|
|
| 1090 |
(*wrapper)._field_4c0 = gfx1;
|
| 1091 |
(*wrapper)._field_4c4 = core::ptr::null_mut();
|
| 1092 |
|
| 1093 |
-
// Build path list (order depends on headless +
|
| 1094 |
-
let
|
| 1095 |
-
let
|
| 1096 |
-
let paths: [&[u8]; 3] = if
|
| 1097 |
-
if
|
| 1098 |
[
|
| 1099 |
b"data\\Gfx\\Gfx.dir\0",
|
| 1100 |
b"data\\Gfx\\Gfx0.dir\0",
|
|
@@ -1107,7 +1032,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1107 |
b"data\\Gfx\\Gfx0.dir\0",
|
| 1108 |
]
|
| 1109 |
}
|
| 1110 |
-
} else if
|
| 1111 |
[
|
| 1112 |
b"data\\Gfx\\Gfx0.dir\0",
|
| 1113 |
b"data\\Gfx\\Gfx1.dir\0",
|
|
@@ -1134,7 +1059,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1134 |
}
|
| 1135 |
}
|
| 1136 |
|
| 1137 |
-
let headless_offset = if
|
| 1138 |
(*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 {
|
| 1139 |
1
|
| 1140 |
} else {
|
|
@@ -1142,11 +1067,11 @@ unsafe fn init_graphics_and_resources(
|
|
| 1142 |
};
|
| 1143 |
|
| 1144 |
// ── GfxHandler #2 (conditional) ──
|
| 1145 |
-
let
|
| 1146 |
// Original: (-(gfx_mode != 0) & 0x23) - 2 → gfx_mode ? 33 : -2
|
| 1147 |
let threshold = if (*wrapper).gfx_mode != 0 { 33 } else { -2i32 };
|
| 1148 |
-
if
|
| 1149 |
-
let c_digit = if
|
| 1150 |
let mut gfx_c_path = *b"data\\Gfx\\GfxC_3_0.dir\0";
|
| 1151 |
gfx_c_path[14] = c_digit;
|
| 1152 |
|
|
@@ -1219,7 +1144,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1219 |
f((*ddgame).display, 1)
|
| 1220 |
};
|
| 1221 |
|
| 1222 |
-
let _ = crate::log::log_line(&format!("[DDGame] gfx_mode={}", (*wrapper).gfx_mode));
|
| 1223 |
// ── GfxDir color entries DDGame+0x730C..0x732C ──
|
| 1224 |
if (*wrapper).gfx_mode != 0 {
|
| 1225 |
// Original: PUSH ESI (=layer_ctx from vtable[5] call), MOV EAX=0x66A3B4, CALL 0x4F6300
|
|
@@ -1227,12 +1151,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1227 |
let res = gfx_resource_create((*wrapper)._field_4c0, rb(0x66A3B4) as *const u8, layer_ctx);
|
| 1228 |
if !res.is_null() {
|
| 1229 |
let rvt = *(res as *const *const u32);
|
| 1230 |
-
let _ = crate::log::log_line(&format!(
|
| 1231 |
-
"[DDGame] color entries: res=0x{:08X} vt=0x{:08X} vt[4]=0x{:08X}",
|
| 1232 |
-
res as u32,
|
| 1233 |
-
rvt as u32,
|
| 1234 |
-
*rvt.add(4),
|
| 1235 |
-
));
|
| 1236 |
// vtable[4] = get_pixel: thiscall(this, x, y) -> color, RET 0x8.
|
| 1237 |
let get_color: unsafe extern "thiscall" fn(*mut u8, u32, u32) -> u32 =
|
| 1238 |
core::mem::transmute(*rvt.add(4));
|
|
@@ -1241,9 +1159,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1241 |
while off < 0x732Du32 {
|
| 1242 |
let c = get_color(res, idx, 0);
|
| 1243 |
*((ddgame as *mut u8).add(off as usize) as *mut u32) = c;
|
| 1244 |
-
if idx == 0 {
|
| 1245 |
-
let _ = crate::log::log_line(&format!("[DDGame] color[0] = 0x{:08X}", c,));
|
| 1246 |
-
}
|
| 1247 |
off += 4;
|
| 1248 |
idx += 1;
|
| 1249 |
}
|
|
@@ -1254,15 +1169,9 @@ unsafe fn init_graphics_and_resources(
|
|
| 1254 |
release(res, 1);
|
| 1255 |
}
|
| 1256 |
} else {
|
| 1257 |
-
//
|
| 1258 |
}
|
| 1259 |
|
| 1260 |
-
let _ = crate::log::log_line("[DDGame] past color entries");
|
| 1261 |
-
let _ = crate::log::log_line(&format!(
|
| 1262 |
-
"[DDGame] 4c0=0x{:08X} 4c4=0x{:08X}",
|
| 1263 |
-
(*wrapper)._field_4c0 as u32,
|
| 1264 |
-
(*wrapper)._field_4c4 as u32
|
| 1265 |
-
));
|
| 1266 |
// ── Secondary GfxDir object (DDGame+0x2C, conditional) ──
|
| 1267 |
if !(*wrapper)._field_4c4.is_null() {
|
| 1268 |
let gfxdir2 = wa_malloc(0x70C);
|
|
@@ -1300,12 +1209,9 @@ unsafe fn init_graphics_and_resources(
|
|
| 1300 |
(*wrapper).speech_name_count = 0;
|
| 1301 |
|
| 1302 |
// ── Audio init (non-headless + sound available) ──
|
| 1303 |
-
let _ = crate::log::log_line("[DDGame] audio: start");
|
| 1304 |
if !is_headless {
|
| 1305 |
// FUN_570F30: usercall(ESI=wrapper)
|
| 1306 |
call_usercall_esi(wrapper, FUN_570F30_ADDR);
|
| 1307 |
-
let _ = crate::log::log_line("[DDGame] audio: past 570F30");
|
| 1308 |
-
|
| 1309 |
if !(*ddgame).sound.is_null() {
|
| 1310 |
// DSSound_LoadEffectWAVs: stdcall(wrapper)
|
| 1311 |
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
|
@@ -1314,32 +1220,22 @@ unsafe fn init_graphics_and_resources(
|
|
| 1314 |
// DSSound_LoadAllSpeechBanks: the original is hooked to our Rust
|
| 1315 |
// replacement (speech.rs), so the usercall bridge calls our code.
|
| 1316 |
call_usercall_esi(wrapper, LOAD_SPEECH_BANKS_ADDR);
|
| 1317 |
-
let _ = crate::log::log_line("[DDGame] audio: done");
|
| 1318 |
// Allocate ActiveSoundTable (0x608 bytes)
|
| 1319 |
let ast = wa_malloc(0x608) as *mut ActiveSoundTable;
|
|
|
|
| 1320 |
(*ast).ddgame = ddgame;
|
| 1321 |
(*ast).counter = 0;
|
| 1322 |
-
core::ptr::write_bytes(ast as *mut u8, 0, 0x600);
|
| 1323 |
(*ddgame).active_sounds = ast;
|
| 1324 |
}
|
| 1325 |
}
|
| 1326 |
|
| 1327 |
-
let _ = crate::log::log_line("[DDGame] past ActiveSoundTable");
|
| 1328 |
-
|
| 1329 |
// ── GfxResource: thiscall(ECX=gfx_handler) + EAX=name + 1 stack(output), RET 0x4 ──
|
| 1330 |
-
// Test: call GfxResource with real params
|
| 1331 |
let gfx_resource: *mut u8;
|
| 1332 |
{
|
| 1333 |
-
|
| 1334 |
-
|
| 1335 |
-
|
| 1336 |
-
|
| 1337 |
-
// return moved to before color entries
|
| 1338 |
-
let _ = crate::log::log_line("[DDGame] calling GfxResource_Create");
|
| 1339 |
-
gfx_resource = gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
|
| 1340 |
-
let _ = crate::log::log_line("[DDGame] after gfx_resource_create");
|
| 1341 |
-
let _ = crate::log::log_line("[DDGame] GfxResource created (value elided)");
|
| 1342 |
-
}
|
| 1343 |
}
|
| 1344 |
|
| 1345 |
// ── PCLandscape (alloc 0xB44, stdcall 11 params) ──
|
|
@@ -1382,37 +1278,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1382 |
(ddgame as *mut u8).add(0x777C), // param 10
|
| 1383 |
(ddgame as *mut u8).add(0x7780), // param 11
|
| 1384 |
);
|
| 1385 |
-
let _ = crate::log::log_line(&format!(
|
| 1386 |
-
"[DDGame] PCLandscape returned: 0x{:08X}, 777C=0x{:08X} 7780=0x{:08X}",
|
| 1387 |
-
result as u32,
|
| 1388 |
-
*((ddgame as *mut u8).add(0x777C) as *const u32),
|
| 1389 |
-
*((ddgame as *mut u8).add(0x7780) as *const u32),
|
| 1390 |
-
));
|
| 1391 |
-
// Dump landscape_coords_buf to find coordinate count and data format
|
| 1392 |
-
let coords = landscape_coords_buf as *const u32;
|
| 1393 |
-
let mut coord_count = 0u32;
|
| 1394 |
-
// Count non-zero pairs (each coord is 2 u32s)
|
| 1395 |
-
for j in 0..285 {
|
| 1396 |
-
if *coords.add(j * 2) != 0 || *coords.add(j * 2 + 1) != 0 {
|
| 1397 |
-
coord_count = j as u32 + 1;
|
| 1398 |
-
}
|
| 1399 |
-
}
|
| 1400 |
-
let _ = crate::log::log_line(&format!(
|
| 1401 |
-
"[DDGame] coords: first=[0x{:08X},0x{:08X}] count_est={} byte_buf[0]=0x{:02X}",
|
| 1402 |
-
*coords,
|
| 1403 |
-
*coords.add(1),
|
| 1404 |
-
coord_count,
|
| 1405 |
-
*landscape_byte_buf,
|
| 1406 |
-
));
|
| 1407 |
-
// Also check stack_local_8 for count
|
| 1408 |
-
let sl8 = stack_local_8 as *const u32;
|
| 1409 |
-
let _ = crate::log::log_line(&format!(
|
| 1410 |
-
"[DDGame] stack8: [0]=0x{:08X} [1]=0x{:08X} [2]=0x{:08X} [3]=0x{:08X}",
|
| 1411 |
-
*sl8,
|
| 1412 |
-
*sl8.add(1),
|
| 1413 |
-
*sl8.add(2),
|
| 1414 |
-
*sl8.add(3),
|
| 1415 |
-
));
|
| 1416 |
// Store landscape pointer
|
| 1417 |
(*wrapper).landscape = result as *mut PCLandscape;
|
| 1418 |
(*ddgame).landscape = result as *mut PCLandscape;
|
|
@@ -1494,20 +1359,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 1494 |
let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 1495 |
core::mem::transmute(*disp_vt.add(5));
|
| 1496 |
let layer_ctx = set_layer((*ddgame).display, 1);
|
| 1497 |
-
if i == 0 {
|
| 1498 |
-
let _ = crate::log::log_line(&format!(
|
| 1499 |
-
"[DDGame] arrow[0]: layer_ctx=0x{:08X}, gfx=0x{:08X}",
|
| 1500 |
-
layer_ctx as u32, gfx_handler as u32
|
| 1501 |
-
));
|
| 1502 |
-
}
|
| 1503 |
|
| 1504 |
let entry = gfx_dir_find_entry(name_buf.as_ptr(), gfx_handler);
|
| 1505 |
-
if i == 0 {
|
| 1506 |
-
let _ = crate::log::log_line(&format!(
|
| 1507 |
-
"[DDGame] arrow[0]: entry=0x{:08X}",
|
| 1508 |
-
entry as u32
|
| 1509 |
-
));
|
| 1510 |
-
}
|
| 1511 |
|
| 1512 |
let sprite: *mut u8;
|
| 1513 |
if !entry.is_null() {
|
|
@@ -1517,12 +1370,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1517 |
core::mem::transmute(*gfx_vt.add(2));
|
| 1518 |
let entry_val = *(entry.add(4) as *const u32);
|
| 1519 |
let cached = load_cached(gfx_handler, entry_val);
|
| 1520 |
-
if i == 0 {
|
| 1521 |
-
let _ = crate::log::log_line(&format!(
|
| 1522 |
-
"[DDGame] arrow[0]: cached=0x{:08X}",
|
| 1523 |
-
cached as u32
|
| 1524 |
-
));
|
| 1525 |
-
}
|
| 1526 |
if !cached.is_null() {
|
| 1527 |
// Wrap with DisplayGfx constructor
|
| 1528 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
@@ -1536,12 +1383,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1536 |
// Entry not found — try direct file load
|
| 1537 |
sprite = call_gfx_load_and_wrap(gfx_handler, name_buf.as_ptr(), layer_ctx);
|
| 1538 |
}
|
| 1539 |
-
if i == 0 {
|
| 1540 |
-
let _ = crate::log::log_line(&format!(
|
| 1541 |
-
"[DDGame] arrow[0]: sprite=0x{:08X}",
|
| 1542 |
-
sprite as u32
|
| 1543 |
-
));
|
| 1544 |
-
}
|
| 1545 |
|
| 1546 |
// Store arrow sprite at DDGame+0x38+i*4
|
| 1547 |
(*ddgame).arrow_sprites[i as usize] = sprite;
|
|
@@ -1586,8 +1427,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1586 |
}
|
| 1587 |
}
|
| 1588 |
|
| 1589 |
-
let _ = crate::log::log_line("[DDGame] arrow loop done");
|
| 1590 |
-
|
| 1591 |
// Release gfx_resource AFTER arrow loop (arrows need it for SpriteRegions)
|
| 1592 |
// vtable[3] = DisplayGfx__vmethod_3: thiscall(this, byte param_2), RET 4.
|
| 1593 |
if !gfx_resource.is_null() {
|
|
@@ -1650,12 +1489,13 @@ unsafe fn init_graphics_and_resources(
|
|
| 1650 |
*cl = (cur_count + 1) as u32;
|
| 1651 |
}
|
| 1652 |
}
|
| 1653 |
-
let _ = crate::log::log_line(&format!(
|
| 1654 |
-
"[DDGame] coord_list populated: {} entries from {} landscape coords",
|
| 1655 |
-
*cl, coord_count,
|
| 1656 |
-
));
|
| 1657 |
}
|
| 1658 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1659 |
// ── Loading progress ticks (2 of 4 — before load_resource_list) ──
|
| 1660 |
call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
| 1661 |
call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
|
@@ -1670,10 +1510,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1670 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 1671 |
|
| 1672 |
let wrapper_vt = *(wrapper as *const *const u32);
|
| 1673 |
-
let _ = crate::log::log_line(&format!(
|
| 1674 |
-
"[DDGame] sprite resource loading: wrapper_vt=0x{:08X}, vt[0]=0x{:08X}, landscape=0x{:08X}, water=0x{:08X}, land=0x{:08X}",
|
| 1675 |
-
wrapper_vt as u32, *wrapper_vt, landscape_ptr as u32, water_layer as u32, land_layer as u32,
|
| 1676 |
-
));
|
| 1677 |
let load_resource_list: unsafe extern "thiscall" fn(
|
| 1678 |
*mut DDGameWrapper,
|
| 1679 |
u32,
|
|
@@ -1683,7 +1519,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1683 |
u32,
|
| 1684 |
) = core::mem::transmute(*wrapper_vt);
|
| 1685 |
// Load resources for layer 1 (main sprites)
|
| 1686 |
-
let _ = crate::log::log_line("[DDGame] load_resource_list #1 (main sprites)");
|
| 1687 |
load_resource_list(
|
| 1688 |
wrapper,
|
| 1689 |
1,
|
|
@@ -1692,17 +1527,11 @@ unsafe fn init_graphics_and_resources(
|
|
| 1692 |
rb(0x6AD2C0) as *const u8, // resource table
|
| 1693 |
0x1D88, // table size
|
| 1694 |
);
|
| 1695 |
-
let _ = crate::log::log_line(&format!(
|
| 1696 |
-
"[DDGame] after #1: sprite_cache[144]=0x{:08X}",
|
| 1697 |
-
(*ddgame).sprite_cache[144] as u32,
|
| 1698 |
-
));
|
| 1699 |
-
|
| 1700 |
// Set global flag based on game version
|
| 1701 |
let gv = (*(*ddgame).game_info).game_version;
|
| 1702 |
*(rb(0x6AF050) as *mut u32) = if gv < 8 { 0 } else { 0x10 };
|
| 1703 |
|
| 1704 |
// Load resources for layer 1 with different table
|
| 1705 |
-
let _ = crate::log::log_line("[DDGame] load_resource_list #2");
|
| 1706 |
load_resource_list(
|
| 1707 |
wrapper,
|
| 1708 |
1,
|
|
@@ -1713,7 +1542,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1713 |
);
|
| 1714 |
|
| 1715 |
// Load resources for layer 2 (water)
|
| 1716 |
-
let _ = crate::log::log_line("[DDGame] load_resource_list #3 (water)");
|
| 1717 |
load_resource_list(
|
| 1718 |
wrapper,
|
| 1719 |
2,
|
|
@@ -1790,10 +1618,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1790 |
// shows was set from piVar3 (water_layer from landscape+0xB38).
|
| 1791 |
let fill_sprite =
|
| 1792 |
call_gfx_find_and_load(water_layer, b"fill.img\0".as_ptr(), layer2_ctx);
|
| 1793 |
-
let _ = crate::log::log_line(&format!(
|
| 1794 |
-
"[DDGame] fill.img: water=0x{:08X} sprite=0x{:08X}",
|
| 1795 |
-
water_layer as u32, fill_sprite as u32,
|
| 1796 |
-
));
|
| 1797 |
if !fill_sprite.is_null() {
|
| 1798 |
// Get pixel value: fill_sprite->vtable[4](0, 0)
|
| 1799 |
let fill_vt = *(fill_sprite as *const *const u32);
|
|
@@ -1902,7 +1726,6 @@ unsafe fn call_gfx_find_and_load(
|
|
| 1902 |
}
|
| 1903 |
|
| 1904 |
// Statics for usercall bridge addresses
|
| 1905 |
-
static mut FUN_570E20_ADDR: u32 = 0;
|
| 1906 |
static mut FUN_570A90_ADDR: u32 = 0;
|
| 1907 |
static mut FUN_570F30_ADDR: u32 = 0;
|
| 1908 |
static mut LOAD_SPEECH_BANKS_ADDR: u32 = 0;
|
|
@@ -1943,76 +1766,6 @@ unsafe fn call_usercall_ecx(wrapper: *mut DDGameWrapper, addr: u32) {
|
|
| 1943 |
f(wrapper);
|
| 1944 |
}
|
| 1945 |
|
| 1946 |
-
/// Runtime address of TaskStateMachine__Init_Maybe.
|
| 1947 |
-
static mut TSM_INIT_ADDR: u32 = 0;
|
| 1948 |
-
|
| 1949 |
-
/// Bridge to TaskStateMachine__Init_Maybe (0x4F6370).
|
| 1950 |
-
/// Convention: usercall(ESI=object, ECX=1, EDI=height) + 1 stack(width), RET 0x4.
|
| 1951 |
-
#[cfg(target_arch = "x86")]
|
| 1952 |
-
#[unsafe(naked)]
|
| 1953 |
-
unsafe extern "C" fn call_tsm_init(_object: *mut u8, _width: u32, _height: u32) {
|
| 1954 |
-
core::arch::naked_asm!(
|
| 1955 |
-
"pushl %esi",
|
| 1956 |
-
"pushl %edi",
|
| 1957 |
-
"movl 12(%esp), %esi", // ESI = object (1st param, shifted by 2 pushes)
|
| 1958 |
-
"movl 20(%esp), %edi", // EDI = height (3rd param, shifted by 2 pushes)
|
| 1959 |
-
"pushl 16(%esp)", // push width (2nd param, shifted by 2 pushes + 1 push)
|
| 1960 |
-
"movl $1, %ecx", // ECX = 1
|
| 1961 |
-
"calll *({addr})", // RET 0x4 cleans the pushed width
|
| 1962 |
-
"popl %edi",
|
| 1963 |
-
"popl %esi",
|
| 1964 |
-
"retl",
|
| 1965 |
-
addr = sym TSM_INIT_ADDR,
|
| 1966 |
-
options(att_syntax),
|
| 1967 |
-
);
|
| 1968 |
-
}
|
| 1969 |
-
|
| 1970 |
-
/// Runtime address of GfxResource__Create_Maybe (set by init_constructor_addrs()).
|
| 1971 |
-
static mut GFX_RESOURCE_CREATE_ADDR: u32 = 0;
|
| 1972 |
-
|
| 1973 |
-
/// Bridge to GfxResource__Create_Maybe (0x4F6300).
|
| 1974 |
-
/// Convention: usercall(ECX=gfx_handler, EAX=data_ptr) + 1 stack(output), RET 0x4.
|
| 1975 |
-
#[cfg(target_arch = "x86")]
|
| 1976 |
-
#[unsafe(naked)]
|
| 1977 |
-
unsafe extern "C" fn call_gfx_resource_create(
|
| 1978 |
-
_gfx_handler: *mut u8,
|
| 1979 |
-
_data_ptr: *const u8,
|
| 1980 |
-
_output: *mut u8,
|
| 1981 |
-
) -> *mut u8 {
|
| 1982 |
-
core::arch::naked_asm!(
|
| 1983 |
-
"pushl %ebp",
|
| 1984 |
-
"pushl %ebx",
|
| 1985 |
-
"movl 12(%esp), %ecx", // ECX = gfx_handler (0=ebx,4=ebp,8=ret,12=handler)
|
| 1986 |
-
"movl 16(%esp), %eax", // EAX = data_ptr (16=data)
|
| 1987 |
-
"pushl 20(%esp)", // push output (20=output, but shifts after push!)
|
| 1988 |
-
"calll *({addr})", // RET 0x4 cleans output
|
| 1989 |
-
"popl %ebx",
|
| 1990 |
-
"popl %ebp",
|
| 1991 |
-
"retl",
|
| 1992 |
-
addr = sym GFX_RESOURCE_CREATE_ADDR,
|
| 1993 |
-
options(att_syntax),
|
| 1994 |
-
);
|
| 1995 |
-
}
|
| 1996 |
-
|
| 1997 |
-
/// Runtime address of GfxDir__FindEntry (set by init_constructor_addrs()).
|
| 1998 |
-
static mut GFX_FIND_ENTRY_ADDR: u32 = 0;
|
| 1999 |
-
|
| 2000 |
-
/// Bridge to GfxDir__FindEntry (0x566520).
|
| 2001 |
-
/// Convention: usercall(EAX=name) + 1 stack(gfx_handler), RET 0x4.
|
| 2002 |
-
/// Returns entry pointer or null.
|
| 2003 |
-
#[cfg(target_arch = "x86")]
|
| 2004 |
-
#[unsafe(naked)]
|
| 2005 |
-
unsafe extern "C" fn call_gfx_find_entry(_name: *const u8, _gfx_handler: *mut u8) -> *mut u8 {
|
| 2006 |
-
core::arch::naked_asm!(
|
| 2007 |
-
"movl 4(%esp), %eax", // EAX = name
|
| 2008 |
-
"pushl 8(%esp)", // push gfx_handler (callee cleans via RET 0x4)
|
| 2009 |
-
"calll *({addr})",
|
| 2010 |
-
"retl", // caller cleans our 2 cdecl params
|
| 2011 |
-
addr = sym GFX_FIND_ENTRY_ADDR,
|
| 2012 |
-
options(att_syntax),
|
| 2013 |
-
);
|
| 2014 |
-
}
|
| 2015 |
-
|
| 2016 |
/// Bridge to GfxHandler__LoadDir (0x5663E0).
|
| 2017 |
/// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
|
| 2018 |
#[cfg(target_arch = "x86")]
|
|
|
|
| 8 |
use crate::engine::ddgame_wrapper::DDGameWrapper;
|
| 9 |
use crate::engine::game_info::GameInfo;
|
| 10 |
use crate::input::keyboard::DDKeyboard;
|
|
|
|
| 11 |
use crate::rebase::rb;
|
| 12 |
use crate::render::landscape::PCLandscape;
|
| 13 |
use crate::render::queue::RenderQueue;
|
|
|
|
| 498 |
let alloc_size = ((total_size + 3) & !3) + 0x20;
|
| 499 |
let buffer = wa_malloc(alloc_size);
|
| 500 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
if buffer.is_null() {
|
|
|
|
| 502 |
return;
|
| 503 |
}
|
| 504 |
// Guard against integer overflow producing tiny alloc_size with huge total_size
|
| 505 |
if total_size as usize > alloc_size as usize {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
return;
|
| 507 |
}
|
| 508 |
|
|
|
|
| 842 |
name: *const u8,
|
| 843 |
output: *mut u8,
|
| 844 |
) -> *mut u8 {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 845 |
// 1. Try FindEntry → cached load → DisplayGfx wrap
|
| 846 |
let entry = gfx_dir_find_entry(name, gfx_handler);
|
|
|
|
| 847 |
if !entry.is_null() {
|
| 848 |
// gfx_handler->vtable[2](entry->field_4) — cached load
|
| 849 |
let vt = *(gfx_handler as *const *const u32);
|
|
|
|
| 850 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 851 |
core::mem::transmute(*vt.add(2));
|
| 852 |
let entry_val = *(entry.add(4) as *const u32);
|
| 853 |
let cached = load_cached(gfx_handler, entry_val);
|
|
|
|
| 854 |
if !cached.is_null() {
|
| 855 |
// DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
|
| 856 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
|
|
| 860 |
}
|
| 861 |
|
| 862 |
// 2. Fallback: LoadImage → IMG_Decode
|
|
|
|
| 863 |
let raw_image = call_gfx_load_image(gfx_handler, name);
|
|
|
|
| 864 |
if raw_image.is_null() {
|
| 865 |
return core::ptr::null_mut();
|
| 866 |
}
|
| 867 |
|
| 868 |
// IMG_Decode: stdcall(output, raw_image, 1), RET 0xC
|
|
|
|
| 869 |
let decode: unsafe extern "stdcall" fn(*mut u8, *mut u8, i32) -> *mut u8 =
|
| 870 |
core::mem::transmute(rb(va::IMG_DECODE) as usize);
|
| 871 |
let result = decode(output, raw_image, 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
|
| 873 |
// Release raw image: raw_image->vtable[0](1)
|
| 874 |
let img_vt = *(raw_image as *const *const u32);
|
| 875 |
let fn_ptr = *img_vt;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 876 |
let release: unsafe extern "thiscall" fn(*mut u8, i32) = core::mem::transmute(fn_ptr);
|
|
|
|
| 877 |
release(raw_image, 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
|
| 879 |
result
|
| 880 |
}
|
| 881 |
|
|
|
|
|
|
|
| 882 |
static mut GFX_LOAD_DIR_ADDR: u32 = 0;
|
| 883 |
|
| 884 |
/// Initialize runtime addresses for the constructor bridges.
|
| 885 |
/// Must be called once at DLL startup (from lib.rs or similar).
|
| 886 |
pub fn init_constructor_addrs() {
|
| 887 |
unsafe {
|
|
|
|
| 888 |
SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
|
|
|
|
| 889 |
FUN_570A90_ADDR = rb(va::FUN_570A90);
|
|
|
|
|
|
|
|
|
|
| 890 |
GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
|
| 891 |
GFX_LOAD_DIR_ADDR = rb(va::GFX_HANDLER_LOAD_DIR);
|
| 892 |
FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
|
|
|
|
| 895 |
}
|
| 896 |
}
|
| 897 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 898 |
/// Bridge to DDGame__InitVersionFlags (0x525BE0).
|
| 899 |
/// Convention: stdcall(ddgame_wrapper).
|
| 900 |
#[cfg(target_arch = "x86")]
|
|
|
|
| 953 |
// Now safe to expose — all fields that concurrent readers check are set.
|
| 954 |
(*wrapper).ddgame = ddgame;
|
| 955 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 956 |
// ── 5. Set g_GameInfo global ──
|
| 957 |
*(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
|
| 958 |
|
|
|
|
| 982 |
}
|
| 983 |
|
| 984 |
// ── 9. InitVersionFlags — sets DDGame+0x7E2E/0x7E2F/0x7E3F ──
|
|
|
|
| 985 |
call_init_version_flags(wrapper);
|
|
|
|
| 986 |
|
| 987 |
// ── 10. GfxHandler, landscape, sprites, audio, resources ──
|
| 988 |
init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
|
|
|
|
| 999 |
unsafe fn init_graphics_and_resources(
|
| 1000 |
wrapper: *mut DDGameWrapper,
|
| 1001 |
game_info: *mut GameInfo,
|
| 1002 |
+
_net_game: *mut u8,
|
| 1003 |
+
_display: *mut DDDisplay,
|
| 1004 |
is_headless: bool,
|
| 1005 |
) {
|
| 1006 |
let ddgame = (*wrapper).ddgame;
|
| 1007 |
let fopen: unsafe extern "cdecl" fn(*const u8, *const u8) -> *mut u8 =
|
| 1008 |
core::mem::transmute(rb(va::WA_FOPEN) as usize);
|
|
|
|
| 1009 |
let gfx_handler_vtable = rb(0x66B280) as u32; // GfxHandler__vtable (from asm: MOV [ESI], 0x66B280)
|
| 1010 |
|
| 1011 |
// ── GfxHandler #1 (primary) ──
|
|
|
|
| 1015 |
(*wrapper)._field_4c0 = gfx1;
|
| 1016 |
(*wrapper)._field_4c4 = core::ptr::null_mut();
|
| 1017 |
|
| 1018 |
+
// Build path list (order depends on headless + display_flags)
|
| 1019 |
+
let headless = (*game_info).headless_mode as i32;
|
| 1020 |
+
let display_flags = (*game_info).display_flags as i32;
|
| 1021 |
+
let paths: [&[u8]; 3] = if headless != 0 {
|
| 1022 |
+
if display_flags == 0 {
|
| 1023 |
[
|
| 1024 |
b"data\\Gfx\\Gfx.dir\0",
|
| 1025 |
b"data\\Gfx\\Gfx0.dir\0",
|
|
|
|
| 1032 |
b"data\\Gfx\\Gfx0.dir\0",
|
| 1033 |
]
|
| 1034 |
}
|
| 1035 |
+
} else if display_flags == 0 {
|
| 1036 |
[
|
| 1037 |
b"data\\Gfx\\Gfx0.dir\0",
|
| 1038 |
b"data\\Gfx\\Gfx1.dir\0",
|
|
|
|
| 1059 |
}
|
| 1060 |
}
|
| 1061 |
|
| 1062 |
+
let headless_offset = if headless != 0 { 1u32 } else { 0u32 };
|
| 1063 |
(*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 {
|
| 1064 |
1
|
| 1065 |
} else {
|
|
|
|
| 1067 |
};
|
| 1068 |
|
| 1069 |
// ── GfxHandler #2 (conditional) ──
|
| 1070 |
+
let game_version = (*game_info).game_version;
|
| 1071 |
// Original: (-(gfx_mode != 0) & 0x23) - 2 → gfx_mode ? 33 : -2
|
| 1072 |
let threshold = if (*wrapper).gfx_mode != 0 { 33 } else { -2i32 };
|
| 1073 |
+
if game_version < threshold {
|
| 1074 |
+
let c_digit = if game_version > -3 { b'2' } else { b'1' };
|
| 1075 |
let mut gfx_c_path = *b"data\\Gfx\\GfxC_3_0.dir\0";
|
| 1076 |
gfx_c_path[14] = c_digit;
|
| 1077 |
|
|
|
|
| 1144 |
f((*ddgame).display, 1)
|
| 1145 |
};
|
| 1146 |
|
|
|
|
| 1147 |
// ── GfxDir color entries DDGame+0x730C..0x732C ──
|
| 1148 |
if (*wrapper).gfx_mode != 0 {
|
| 1149 |
// Original: PUSH ESI (=layer_ctx from vtable[5] call), MOV EAX=0x66A3B4, CALL 0x4F6300
|
|
|
|
| 1151 |
let res = gfx_resource_create((*wrapper)._field_4c0, rb(0x66A3B4) as *const u8, layer_ctx);
|
| 1152 |
if !res.is_null() {
|
| 1153 |
let rvt = *(res as *const *const u32);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1154 |
// vtable[4] = get_pixel: thiscall(this, x, y) -> color, RET 0x8.
|
| 1155 |
let get_color: unsafe extern "thiscall" fn(*mut u8, u32, u32) -> u32 =
|
| 1156 |
core::mem::transmute(*rvt.add(4));
|
|
|
|
| 1159 |
while off < 0x732Du32 {
|
| 1160 |
let c = get_color(res, idx, 0);
|
| 1161 |
*((ddgame as *mut u8).add(off as usize) as *mut u32) = c;
|
|
|
|
|
|
|
|
|
|
| 1162 |
off += 4;
|
| 1163 |
idx += 1;
|
| 1164 |
}
|
|
|
|
| 1169 |
release(res, 1);
|
| 1170 |
}
|
| 1171 |
} else {
|
| 1172 |
+
// gfx_mode == 0: no color entries to load
|
| 1173 |
}
|
| 1174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1175 |
// ── Secondary GfxDir object (DDGame+0x2C, conditional) ──
|
| 1176 |
if !(*wrapper)._field_4c4.is_null() {
|
| 1177 |
let gfxdir2 = wa_malloc(0x70C);
|
|
|
|
| 1209 |
(*wrapper).speech_name_count = 0;
|
| 1210 |
|
| 1211 |
// ── Audio init (non-headless + sound available) ──
|
|
|
|
| 1212 |
if !is_headless {
|
| 1213 |
// FUN_570F30: usercall(ESI=wrapper)
|
| 1214 |
call_usercall_esi(wrapper, FUN_570F30_ADDR);
|
|
|
|
|
|
|
| 1215 |
if !(*ddgame).sound.is_null() {
|
| 1216 |
// DSSound_LoadEffectWAVs: stdcall(wrapper)
|
| 1217 |
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
|
|
|
| 1220 |
// DSSound_LoadAllSpeechBanks: the original is hooked to our Rust
|
| 1221 |
// replacement (speech.rs), so the usercall bridge calls our code.
|
| 1222 |
call_usercall_esi(wrapper, LOAD_SPEECH_BANKS_ADDR);
|
|
|
|
| 1223 |
// Allocate ActiveSoundTable (0x608 bytes)
|
| 1224 |
let ast = wa_malloc(0x608) as *mut ActiveSoundTable;
|
| 1225 |
+
core::ptr::write_bytes(ast as *mut u8, 0, 0x600);
|
| 1226 |
(*ast).ddgame = ddgame;
|
| 1227 |
(*ast).counter = 0;
|
|
|
|
| 1228 |
(*ddgame).active_sounds = ast;
|
| 1229 |
}
|
| 1230 |
}
|
| 1231 |
|
|
|
|
|
|
|
| 1232 |
// ── GfxResource: thiscall(ECX=gfx_handler) + EAX=name + 1 stack(output), RET 0x4 ──
|
|
|
|
| 1233 |
let gfx_resource: *mut u8;
|
| 1234 |
{
|
| 1235 |
+
let gfx_handler = (*wrapper)._field_4c0;
|
| 1236 |
+
let out_buf = wa_malloc(0x900);
|
| 1237 |
+
core::ptr::write_bytes(out_buf, 0, 0x900);
|
| 1238 |
+
gfx_resource = gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1239 |
}
|
| 1240 |
|
| 1241 |
// ── PCLandscape (alloc 0xB44, stdcall 11 params) ──
|
|
|
|
| 1278 |
(ddgame as *mut u8).add(0x777C), // param 10
|
| 1279 |
(ddgame as *mut u8).add(0x7780), // param 11
|
| 1280 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1281 |
// Store landscape pointer
|
| 1282 |
(*wrapper).landscape = result as *mut PCLandscape;
|
| 1283 |
(*ddgame).landscape = result as *mut PCLandscape;
|
|
|
|
| 1359 |
let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 1360 |
core::mem::transmute(*disp_vt.add(5));
|
| 1361 |
let layer_ctx = set_layer((*ddgame).display, 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1362 |
|
| 1363 |
let entry = gfx_dir_find_entry(name_buf.as_ptr(), gfx_handler);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1364 |
|
| 1365 |
let sprite: *mut u8;
|
| 1366 |
if !entry.is_null() {
|
|
|
|
| 1370 |
core::mem::transmute(*gfx_vt.add(2));
|
| 1371 |
let entry_val = *(entry.add(4) as *const u32);
|
| 1372 |
let cached = load_cached(gfx_handler, entry_val);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1373 |
if !cached.is_null() {
|
| 1374 |
// Wrap with DisplayGfx constructor
|
| 1375 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
|
|
| 1383 |
// Entry not found — try direct file load
|
| 1384 |
sprite = call_gfx_load_and_wrap(gfx_handler, name_buf.as_ptr(), layer_ctx);
|
| 1385 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1386 |
|
| 1387 |
// Store arrow sprite at DDGame+0x38+i*4
|
| 1388 |
(*ddgame).arrow_sprites[i as usize] = sprite;
|
|
|
|
| 1427 |
}
|
| 1428 |
}
|
| 1429 |
|
|
|
|
|
|
|
| 1430 |
// Release gfx_resource AFTER arrow loop (arrows need it for SpriteRegions)
|
| 1431 |
// vtable[3] = DisplayGfx__vmethod_3: thiscall(this, byte param_2), RET 4.
|
| 1432 |
if !gfx_resource.is_null() {
|
|
|
|
| 1489 |
*cl = (cur_count + 1) as u32;
|
| 1490 |
}
|
| 1491 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1492 |
}
|
| 1493 |
|
| 1494 |
+
// Free temporary landscape buffers (no longer needed after coord_list population)
|
| 1495 |
+
crate::wa_alloc::wa_free(landscape_coords_buf);
|
| 1496 |
+
crate::wa_alloc::wa_free(landscape_byte_buf);
|
| 1497 |
+
crate::wa_alloc::wa_free(stack_local_8);
|
| 1498 |
+
|
| 1499 |
// ── Loading progress ticks (2 of 4 — before load_resource_list) ──
|
| 1500 |
call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
| 1501 |
call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
|
|
|
| 1510 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 1511 |
|
| 1512 |
let wrapper_vt = *(wrapper as *const *const u32);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1513 |
let load_resource_list: unsafe extern "thiscall" fn(
|
| 1514 |
*mut DDGameWrapper,
|
| 1515 |
u32,
|
|
|
|
| 1519 |
u32,
|
| 1520 |
) = core::mem::transmute(*wrapper_vt);
|
| 1521 |
// Load resources for layer 1 (main sprites)
|
|
|
|
| 1522 |
load_resource_list(
|
| 1523 |
wrapper,
|
| 1524 |
1,
|
|
|
|
| 1527 |
rb(0x6AD2C0) as *const u8, // resource table
|
| 1528 |
0x1D88, // table size
|
| 1529 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1530 |
// Set global flag based on game version
|
| 1531 |
let gv = (*(*ddgame).game_info).game_version;
|
| 1532 |
*(rb(0x6AF050) as *mut u32) = if gv < 8 { 0 } else { 0x10 };
|
| 1533 |
|
| 1534 |
// Load resources for layer 1 with different table
|
|
|
|
| 1535 |
load_resource_list(
|
| 1536 |
wrapper,
|
| 1537 |
1,
|
|
|
|
| 1542 |
);
|
| 1543 |
|
| 1544 |
// Load resources for layer 2 (water)
|
|
|
|
| 1545 |
load_resource_list(
|
| 1546 |
wrapper,
|
| 1547 |
2,
|
|
|
|
| 1618 |
// shows was set from piVar3 (water_layer from landscape+0xB38).
|
| 1619 |
let fill_sprite =
|
| 1620 |
call_gfx_find_and_load(water_layer, b"fill.img\0".as_ptr(), layer2_ctx);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1621 |
if !fill_sprite.is_null() {
|
| 1622 |
// Get pixel value: fill_sprite->vtable[4](0, 0)
|
| 1623 |
let fill_vt = *(fill_sprite as *const *const u32);
|
|
|
|
| 1726 |
}
|
| 1727 |
|
| 1728 |
// Statics for usercall bridge addresses
|
|
|
|
| 1729 |
static mut FUN_570A90_ADDR: u32 = 0;
|
| 1730 |
static mut FUN_570F30_ADDR: u32 = 0;
|
| 1731 |
static mut LOAD_SPEECH_BANKS_ADDR: u32 = 0;
|
|
|
|
| 1766 |
f(wrapper);
|
| 1767 |
}
|
| 1768 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1769 |
/// Bridge to GfxHandler__LoadDir (0x5663E0).
|
| 1770 |
/// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
|
| 1771 |
#[cfg(target_arch = "x86")]
|