fix: correct vtable calling conventions in Rust DDGame constructor
Browse filesMultiple DisplayGfx__vmethod_3 (RET 4) and CTaskOilDrum__ProcessFrame_stub
(RET 8) calls were missing required stack parameters, causing stack corruption.
- vtable[3] = DisplayGfx__vmethod_3: thiscall(this, byte param_2), RET 4
- release(res) β release(res, 1) in color entries loop
- release(gfx_resource) β release(gfx_resource, 1)
- vtable[4] = CTaskOilDrum__ProcessFrame_stub: XOR EAX,EAX; RET 8
- get_color(res) β get_color(res, 0, 0) β stub returns 0 regardless
Also add minimal gradient_image stub (DDGame+0x30) to prevent null pointer
crash in CTaskLand__InitLandscape when sound_available != 0.
Reverts USE_RUST_CTOR to false while sprite/gradient loading is not yet ported;
the 7 individual sub-function Rust hooks remain active. All 133 validations pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@@ -484,6 +484,24 @@ pub unsafe fn task_state_machine_init(
|
|
| 484 |
let alloc_size = ((total_size + 3) & !3) + 0x20;
|
| 485 |
let buffer = wa_malloc(alloc_size);
|
| 486 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
// Memset twice (matches original β likely redundant but exact match)
|
| 488 |
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
| 489 |
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
|
@@ -811,15 +829,22 @@ pub unsafe fn gfx_resource_create(
|
|
| 811 |
name: *const u8,
|
| 812 |
output: *mut u8,
|
| 813 |
) -> *mut u8 {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 814 |
// 1. Try FindEntry β cached load β DisplayGfx wrap
|
| 815 |
let entry = gfx_dir_find_entry(name, gfx_handler);
|
|
|
|
| 816 |
if !entry.is_null() {
|
| 817 |
// gfx_handler->vtable[2](entry->field_4) β cached load
|
| 818 |
let vt = *(gfx_handler as *const *const u32);
|
|
|
|
| 819 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 820 |
core::mem::transmute(*vt.add(2));
|
| 821 |
let entry_val = *(entry.add(4) as *const u32);
|
| 822 |
let cached = load_cached(gfx_handler, entry_val);
|
|
|
|
| 823 |
if !cached.is_null() {
|
| 824 |
// DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
|
| 825 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
@@ -829,27 +854,48 @@ pub unsafe fn gfx_resource_create(
|
|
| 829 |
}
|
| 830 |
|
| 831 |
// 2. Fallback: LoadImage β IMG_Decode
|
|
|
|
| 832 |
let raw_image = call_gfx_load_image(gfx_handler, name);
|
|
|
|
| 833 |
if raw_image.is_null() {
|
| 834 |
return core::ptr::null_mut();
|
| 835 |
}
|
| 836 |
|
| 837 |
// IMG_Decode: stdcall(output, raw_image, 1), RET 0xC
|
|
|
|
| 838 |
let decode: unsafe extern "stdcall" fn(*mut u8, *mut u8, i32) -> *mut u8 =
|
| 839 |
core::mem::transmute(rb(va::IMG_DECODE) as usize);
|
| 840 |
let result = decode(output, raw_image, 1);
|
|
|
|
| 841 |
|
| 842 |
// Release raw image: raw_image->vtable[0](1)
|
| 843 |
let img_vt = *(raw_image as *const *const u32);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 844 |
let release: unsafe extern "thiscall" fn(*mut u8, i32) =
|
| 845 |
-
core::mem::transmute(
|
|
|
|
| 846 |
release(raw_image, 1);
|
|
|
|
|
|
|
|
|
|
| 847 |
|
| 848 |
result
|
| 849 |
}
|
| 850 |
|
| 851 |
/// Runtime address of DDGame__InitFields (set by init_constructor_addrs()).
|
| 852 |
static mut INIT_FIELDS_ADDR: u32 = 0;
|
|
|
|
| 853 |
|
| 854 |
/// Initialize runtime addresses for the constructor bridges.
|
| 855 |
/// Must be called once at DLL startup (from lib.rs or similar).
|
|
@@ -863,6 +909,7 @@ pub fn init_constructor_addrs() {
|
|
| 863 |
TSM_INIT_ADDR = rb(va::TASK_STATE_MACHINE_INIT);
|
| 864 |
GFX_FIND_ENTRY_ADDR = rb(va::GFX_DIR_FIND_ENTRY);
|
| 865 |
GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
|
|
|
|
| 866 |
FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
|
| 867 |
LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
|
| 868 |
LOAD_WEAPON_SPRITES_ADDR = rb(va::DDGAME_LOAD_WEAPON_SPRITES);
|
|
@@ -1039,7 +1086,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1039 |
for (i, path) in paths.iter().enumerate() {
|
| 1040 |
let fp = fopen(path.as_ptr(), b"rb\0".as_ptr());
|
| 1041 |
*(gfx1.add(0x198) as *mut *mut u8) = fp;
|
| 1042 |
-
if !fp.is_null() &&
|
| 1043 |
gfx_loaded_idx = i as u32;
|
| 1044 |
break;
|
| 1045 |
}
|
|
@@ -1067,10 +1114,10 @@ unsafe fn init_graphics_and_resources(
|
|
| 1067 |
|
| 1068 |
let fp = fopen(gfx_c_path.as_ptr(), b"rb\0".as_ptr());
|
| 1069 |
*(gfx2.add(0x198) as *mut *mut u8) = fp;
|
| 1070 |
-
if fp.is_null() ||
|
| 1071 |
let fp2 = fopen(b"data\\Gfx\\Gfx.dir\0".as_ptr(), b"rb\0".as_ptr());
|
| 1072 |
*(gfx2.add(0x198) as *mut *mut u8) = fp2;
|
| 1073 |
-
if fp2.is_null() ||
|
| 1074 |
panic!("DDGame: couldn't open secondary Gfx.dir");
|
| 1075 |
}
|
| 1076 |
}
|
|
@@ -1120,39 +1167,47 @@ unsafe fn init_graphics_and_resources(
|
|
| 1120 |
display_layer_color_init(wrapper);
|
| 1121 |
|
| 1122 |
// ββ Display vtable slot 5 (offset 0x14) ββ
|
| 1123 |
-
|
|
|
|
|
|
|
| 1124 |
let vt = *((*ddgame).display as *const *const u32);
|
| 1125 |
let f: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 1126 |
core::mem::transmute(*vt.add(5));
|
| 1127 |
-
f((*ddgame).display, 1)
|
| 1128 |
-
}
|
| 1129 |
|
| 1130 |
let _ = crate::log::log_line(&format!("[DDGame] gfx_mode={}", (*wrapper).gfx_mode));
|
| 1131 |
// ββ GfxDir color entries DDGame+0x730C..0x732C ββ
|
| 1132 |
if (*wrapper).gfx_mode != 0 {
|
| 1133 |
-
//
|
| 1134 |
-
//
|
| 1135 |
let res = gfx_resource_create(
|
| 1136 |
-
(*wrapper)._field_4c0, rb(0x66A3B4) as *const u8,
|
| 1137 |
);
|
| 1138 |
if !res.is_null() {
|
| 1139 |
let rvt = *(res as *const *const u32);
|
| 1140 |
-
|
|
|
|
|
|
|
|
|
|
| 1141 |
core::mem::transmute(*rvt.add(4));
|
| 1142 |
let mut off = 0x730Cu32;
|
| 1143 |
while off < 0x732Du32 {
|
| 1144 |
-
*((ddgame as *mut u8).add(off as usize) as *mut u32) = get_color(res);
|
| 1145 |
off += 4;
|
| 1146 |
}
|
| 1147 |
-
|
|
|
|
|
|
|
| 1148 |
core::mem::transmute(*rvt.add(3));
|
| 1149 |
-
release(res);
|
| 1150 |
}
|
| 1151 |
} else {
|
| 1152 |
// GFX_HANDLER_LOAD_SPRITES: SKIP for testing (gfx_mode==0 path)
|
| 1153 |
}
|
| 1154 |
|
| 1155 |
let _ = crate::log::log_line("[DDGame] past color entries");
|
|
|
|
| 1156 |
// ββ Secondary GfxDir object (DDGame+0x2C, conditional) ββ
|
| 1157 |
if !(*wrapper)._field_4c4.is_null() {
|
| 1158 |
let gfxdir2 = wa_malloc(0x70C);
|
|
@@ -1223,7 +1278,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 1223 |
// return moved to before color entries
|
| 1224 |
let _ = crate::log::log_line("[DDGame] calling GfxResource_Create");
|
| 1225 |
gfx_resource = gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
|
| 1226 |
-
let _ = crate::log::log_line(
|
|
|
|
| 1227 |
}
|
| 1228 |
}
|
| 1229 |
|
|
@@ -1336,11 +1392,12 @@ unsafe fn init_graphics_and_resources(
|
|
| 1336 |
}
|
| 1337 |
|
| 1338 |
// Release gfx_resource if non-null
|
|
|
|
| 1339 |
if !gfx_resource.is_null() {
|
| 1340 |
let rvt = *(gfx_resource as *const *const u32);
|
| 1341 |
-
let release: unsafe extern "thiscall" fn(*mut u8) =
|
| 1342 |
core::mem::transmute(*rvt.add(3));
|
| 1343 |
-
release(gfx_resource);
|
| 1344 |
}
|
| 1345 |
|
| 1346 |
// GfxDir addresses are resolved via statics (GFX_FIND_ENTRY_ADDR, GFX_LOAD_IMAGE_ADDR)
|
|
@@ -1598,6 +1655,21 @@ unsafe fn init_graphics_and_resources(
|
|
| 1598 |
// TODO: enable one by one to find which causes DEP crash
|
| 1599 |
} // if false
|
| 1600 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1601 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1602 |
}
|
| 1603 |
|
|
|
|
| 484 |
let alloc_size = ((total_size + 3) & !3) + 0x20;
|
| 485 |
let buffer = wa_malloc(alloc_size);
|
| 486 |
|
| 487 |
+
let _ = crate::log::log_line(&format!(
|
| 488 |
+
"[TSM] init: obj=0x{:08X} p1={} w={} h={} stride={} total={} alloc={} buf=0x{:08X}",
|
| 489 |
+
object as u32, param1, width, height, row_stride, total_size, alloc_size, buffer as u32,
|
| 490 |
+
));
|
| 491 |
+
|
| 492 |
+
if buffer.is_null() {
|
| 493 |
+
let _ = crate::log::log_line("[TSM] WARN: wa_malloc returned null, skipping memset");
|
| 494 |
+
return;
|
| 495 |
+
}
|
| 496 |
+
// Guard against integer overflow producing tiny alloc_size with huge total_size
|
| 497 |
+
if total_size as usize > alloc_size as usize {
|
| 498 |
+
let _ = crate::log::log_line(&format!(
|
| 499 |
+
"[TSM] OVERFLOW: total_size {} > alloc_size {}, skipping memset",
|
| 500 |
+
total_size, alloc_size,
|
| 501 |
+
));
|
| 502 |
+
return;
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
// Memset twice (matches original β likely redundant but exact match)
|
| 506 |
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
| 507 |
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
|
|
|
| 829 |
name: *const u8,
|
| 830 |
output: *mut u8,
|
| 831 |
) -> *mut u8 {
|
| 832 |
+
let _ = crate::log::log_line(&format!(
|
| 833 |
+
"[GfxRes] create: handler=0x{:08X} name=0x{:08X} out=0x{:08X}",
|
| 834 |
+
gfx_handler as u32, name as u32, output as u32,
|
| 835 |
+
));
|
| 836 |
// 1. Try FindEntry β cached load β DisplayGfx wrap
|
| 837 |
let entry = gfx_dir_find_entry(name, gfx_handler);
|
| 838 |
+
let _ = crate::log::log_line(&format!("[GfxRes] entry=0x{:08X}", entry as u32));
|
| 839 |
if !entry.is_null() {
|
| 840 |
// gfx_handler->vtable[2](entry->field_4) β cached load
|
| 841 |
let vt = *(gfx_handler as *const *const u32);
|
| 842 |
+
let _ = crate::log::log_line(&format!("[GfxRes] vt=0x{:08X}", vt as u32));
|
| 843 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 844 |
core::mem::transmute(*vt.add(2));
|
| 845 |
let entry_val = *(entry.add(4) as *const u32);
|
| 846 |
let cached = load_cached(gfx_handler, entry_val);
|
| 847 |
+
let _ = crate::log::log_line(&format!("[GfxRes] cached=0x{:08X}", cached as u32));
|
| 848 |
if !cached.is_null() {
|
| 849 |
// DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
|
| 850 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
|
|
| 854 |
}
|
| 855 |
|
| 856 |
// 2. Fallback: LoadImage β IMG_Decode
|
| 857 |
+
let _ = crate::log::log_line("[GfxRes] fallback: LoadImage");
|
| 858 |
let raw_image = call_gfx_load_image(gfx_handler, name);
|
| 859 |
+
let _ = crate::log::log_line(&format!("[GfxRes] raw_image=0x{:08X}", raw_image as u32));
|
| 860 |
if raw_image.is_null() {
|
| 861 |
return core::ptr::null_mut();
|
| 862 |
}
|
| 863 |
|
| 864 |
// IMG_Decode: stdcall(output, raw_image, 1), RET 0xC
|
| 865 |
+
let _ = crate::log::log_line("[GfxRes] calling IMG_Decode");
|
| 866 |
let decode: unsafe extern "stdcall" fn(*mut u8, *mut u8, i32) -> *mut u8 =
|
| 867 |
core::mem::transmute(rb(va::IMG_DECODE) as usize);
|
| 868 |
let result = decode(output, raw_image, 1);
|
| 869 |
+
let _ = crate::log::log_line(&format!("[GfxRes] IMG_Decode result=0x{:08X}", result as u32));
|
| 870 |
|
| 871 |
// Release raw image: raw_image->vtable[0](1)
|
| 872 |
let img_vt = *(raw_image as *const *const u32);
|
| 873 |
+
let fn_ptr = *img_vt;
|
| 874 |
+
let slot_idx = *(raw_image.add(4) as *const u32);
|
| 875 |
+
let stored_handler = *(raw_image.add(8) as *const u32);
|
| 876 |
+
let _ = crate::log::log_line(&format!(
|
| 877 |
+
"[GfxRes] release: raw=0x{:08X} vt=0x{:08X} fn=0x{:08X} slot={} handler=0x{:08X}",
|
| 878 |
+
raw_image as u32, img_vt as u32, fn_ptr as u32, slot_idx, stored_handler,
|
| 879 |
+
));
|
| 880 |
+
let _ = crate::log::log_line(&format!(
|
| 881 |
+
"[GfxRes] handler_usedcnt={} handler_freecnt={}",
|
| 882 |
+
*(gfx_handler.add(0x190) as *const u32),
|
| 883 |
+
*(gfx_handler.add(0x18C) as *const u32),
|
| 884 |
+
));
|
| 885 |
let release: unsafe extern "thiscall" fn(*mut u8, i32) =
|
| 886 |
+
core::mem::transmute(fn_ptr);
|
| 887 |
+
let _ = crate::log::log_line("[GfxRes] calling release...");
|
| 888 |
release(raw_image, 1);
|
| 889 |
+
let _ = crate::log::log_line("[GfxRes] release done");
|
| 890 |
+
let _ = crate::log::log_line(&format!("[GfxRes] heap-ok raw=0x{:08X} result=0x{:08X}", raw_image as u32, result as u32));
|
| 891 |
+
let _ = crate::log::log_line("[GfxRes] before return");
|
| 892 |
|
| 893 |
result
|
| 894 |
}
|
| 895 |
|
| 896 |
/// Runtime address of DDGame__InitFields (set by init_constructor_addrs()).
|
| 897 |
static mut INIT_FIELDS_ADDR: u32 = 0;
|
| 898 |
+
static mut GFX_LOAD_DIR_ADDR: u32 = 0;
|
| 899 |
|
| 900 |
/// Initialize runtime addresses for the constructor bridges.
|
| 901 |
/// Must be called once at DLL startup (from lib.rs or similar).
|
|
|
|
| 909 |
TSM_INIT_ADDR = rb(va::TASK_STATE_MACHINE_INIT);
|
| 910 |
GFX_FIND_ENTRY_ADDR = rb(va::GFX_DIR_FIND_ENTRY);
|
| 911 |
GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
|
| 912 |
+
GFX_LOAD_DIR_ADDR = rb(va::GFX_HANDLER_LOAD_DIR);
|
| 913 |
FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
|
| 914 |
LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
|
| 915 |
LOAD_WEAPON_SPRITES_ADDR = rb(va::DDGAME_LOAD_WEAPON_SPRITES);
|
|
|
|
| 1086 |
for (i, path) in paths.iter().enumerate() {
|
| 1087 |
let fp = fopen(path.as_ptr(), b"rb\0".as_ptr());
|
| 1088 |
*(gfx1.add(0x198) as *mut *mut u8) = fp;
|
| 1089 |
+
if !fp.is_null() && call_gfx_load_dir(gfx1, GFX_LOAD_DIR_ADDR) != 0 {
|
| 1090 |
gfx_loaded_idx = i as u32;
|
| 1091 |
break;
|
| 1092 |
}
|
|
|
|
| 1114 |
|
| 1115 |
let fp = fopen(gfx_c_path.as_ptr(), b"rb\0".as_ptr());
|
| 1116 |
*(gfx2.add(0x198) as *mut *mut u8) = fp;
|
| 1117 |
+
if fp.is_null() || call_gfx_load_dir(gfx2, GFX_LOAD_DIR_ADDR) == 0 {
|
| 1118 |
let fp2 = fopen(b"data\\Gfx\\Gfx.dir\0".as_ptr(), b"rb\0".as_ptr());
|
| 1119 |
*(gfx2.add(0x198) as *mut *mut u8) = fp2;
|
| 1120 |
+
if fp2.is_null() || call_gfx_load_dir(gfx2, GFX_LOAD_DIR_ADDR) == 0 {
|
| 1121 |
panic!("DDGame: couldn't open secondary Gfx.dir");
|
| 1122 |
}
|
| 1123 |
}
|
|
|
|
| 1167 |
display_layer_color_init(wrapper);
|
| 1168 |
|
| 1169 |
// ββ Display vtable slot 5 (offset 0x14) ββ
|
| 1170 |
+
// Original: CALL EAX (vtable[5]), saves return value in ESI for use as
|
| 1171 |
+
// the `output` parameter in the color-entries GfxResource__Create call below.
|
| 1172 |
+
let layer_ctx = {
|
| 1173 |
let vt = *((*ddgame).display as *const *const u32);
|
| 1174 |
let f: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 1175 |
core::mem::transmute(*vt.add(5));
|
| 1176 |
+
f((*ddgame).display, 1)
|
| 1177 |
+
};
|
| 1178 |
|
| 1179 |
let _ = crate::log::log_line(&format!("[DDGame] gfx_mode={}", (*wrapper).gfx_mode));
|
| 1180 |
// ββ GfxDir color entries DDGame+0x730C..0x732C ββ
|
| 1181 |
if (*wrapper).gfx_mode != 0 {
|
| 1182 |
+
// Original: PUSH ESI (=layer_ctx from vtable[5] call), MOV EAX=0x66A3B4, CALL 0x4F6300
|
| 1183 |
+
// The layer_ctx is used as the output buffer, not a plain stack alloc.
|
| 1184 |
let res = gfx_resource_create(
|
| 1185 |
+
(*wrapper)._field_4c0, rb(0x66A3B4) as *const u8, layer_ctx,
|
| 1186 |
);
|
| 1187 |
if !res.is_null() {
|
| 1188 |
let rvt = *(res as *const *const u32);
|
| 1189 |
+
// vtable[4] = CTaskOilDrum__ProcessFrame_stub: XOR EAX,EAX; RET 0x8
|
| 1190 |
+
// RET 0x8 = 2 stack params consumed. Always returns 0 (stub).
|
| 1191 |
+
// Must pass 2 dummy u32s so the callee's RET 0x8 balances the stack.
|
| 1192 |
+
let get_color: unsafe extern "thiscall" fn(*mut u8, u32, u32) -> u32 =
|
| 1193 |
core::mem::transmute(*rvt.add(4));
|
| 1194 |
let mut off = 0x730Cu32;
|
| 1195 |
while off < 0x732Du32 {
|
| 1196 |
+
*((ddgame as *mut u8).add(off as usize) as *mut u32) = get_color(res, 0, 0);
|
| 1197 |
off += 4;
|
| 1198 |
}
|
| 1199 |
+
// DisplayGfx__vmethod_3: thiscall(this, byte param_2), RET 4.
|
| 1200 |
+
// param_2 & 1 = free the object itself.
|
| 1201 |
+
let release: unsafe extern "thiscall" fn(*mut u8, u8) =
|
| 1202 |
core::mem::transmute(*rvt.add(3));
|
| 1203 |
+
release(res, 1);
|
| 1204 |
}
|
| 1205 |
} else {
|
| 1206 |
// GFX_HANDLER_LOAD_SPRITES: SKIP for testing (gfx_mode==0 path)
|
| 1207 |
}
|
| 1208 |
|
| 1209 |
let _ = crate::log::log_line("[DDGame] past color entries");
|
| 1210 |
+
let _ = crate::log::log_line(&format!("[DDGame] 4c0=0x{:08X} 4c4=0x{:08X}", (*wrapper)._field_4c0 as u32, (*wrapper)._field_4c4 as u32));
|
| 1211 |
// ββ Secondary GfxDir object (DDGame+0x2C, conditional) ββ
|
| 1212 |
if !(*wrapper)._field_4c4.is_null() {
|
| 1213 |
let gfxdir2 = wa_malloc(0x70C);
|
|
|
|
| 1278 |
// return moved to before color entries
|
| 1279 |
let _ = crate::log::log_line("[DDGame] calling GfxResource_Create");
|
| 1280 |
gfx_resource = gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
|
| 1281 |
+
let _ = crate::log::log_line("[DDGame] after gfx_resource_create");
|
| 1282 |
+
let _ = crate::log::log_line("[DDGame] GfxResource created (value elided)");
|
| 1283 |
}
|
| 1284 |
}
|
| 1285 |
|
|
|
|
| 1392 |
}
|
| 1393 |
|
| 1394 |
// Release gfx_resource if non-null
|
| 1395 |
+
// vtable[3] = DisplayGfx__vmethod_3: thiscall(this, byte param_2), RET 4.
|
| 1396 |
if !gfx_resource.is_null() {
|
| 1397 |
let rvt = *(gfx_resource as *const *const u32);
|
| 1398 |
+
let release: unsafe extern "thiscall" fn(*mut u8, u8) =
|
| 1399 |
core::mem::transmute(*rvt.add(3));
|
| 1400 |
+
release(gfx_resource, 1);
|
| 1401 |
}
|
| 1402 |
|
| 1403 |
// GfxDir addresses are resolved via statics (GFX_FIND_ENTRY_ADDR, GFX_LOAD_IMAGE_ADDR)
|
|
|
|
| 1655 |
// TODO: enable one by one to find which causes DEP crash
|
| 1656 |
} // if false
|
| 1657 |
}
|
| 1658 |
+
// ββ Gradient image stub (DDGame+0x30) ββ
|
| 1659 |
+
// The original constructor loads "gradient.img" here and stores the result at DDGame+0x30.
|
| 1660 |
+
// CTaskLand__InitLandscape reads DDGame+0x30/+0x34 and calls vtable[4](0, i) for each
|
| 1661 |
+
// pixel column. vtable[4] = CTaskOilDrum__ProcessFrame_stub (XOR EAX,EAX; RET 0x8) returns 0.
|
| 1662 |
+
// A minimal stub with [6]=0 (zero-width) causes the loop to not execute at all.
|
| 1663 |
+
if (*ddgame).gradient_image.is_null() {
|
| 1664 |
+
let obj = wa_malloc(0x2C);
|
| 1665 |
+
core::ptr::write_bytes(obj, 0, 0x2C);
|
| 1666 |
+
if !obj.is_null() {
|
| 1667 |
+
*(obj as *mut u32) = rb(0x6640EC); // vtable (DisplayGfx vtable, vtable[4]=ProcessFrame_stub)
|
| 1668 |
+
// [6] = height/width = 0 β CTaskLand loop: `if (0 < 0)` β skip
|
| 1669 |
+
(*ddgame).gradient_image = obj;
|
| 1670 |
+
}
|
| 1671 |
+
}
|
| 1672 |
+
|
| 1673 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1674 |
}
|
| 1675 |
|
|
@@ -113,6 +113,7 @@ pub(crate) unsafe fn construct_ddgame_wrapper(
|
|
| 113 |
));
|
| 114 |
|
| 115 |
// Toggle: use Rust constructor (true) or original (false)
|
|
|
|
| 116 |
const USE_RUST_CTOR: bool = false;
|
| 117 |
if USE_RUST_CTOR {
|
| 118 |
create_ddgame(
|
|
|
|
| 113 |
));
|
| 114 |
|
| 115 |
// Toggle: use Rust constructor (true) or original (false)
|
| 116 |
+
// TODO: set back to true once sprite/gradient loading is ported.
|
| 117 |
const USE_RUST_CTOR: bool = false;
|
| 118 |
if USE_RUST_CTOR {
|
| 119 |
create_ddgame(
|