feat: enable USE_RUST_CTOR — Rust DDGame constructor now functional
Browse filesEnable the three previously disabled sections:
- Sprite resource loading (load_resource_list calls)
- SpriteRegion constructors (8 regions)
- DDGame__LoadHudAndWeaponSprites (0x53D0E0) — creates weapon icons,
wind indicators, and the DDGame+0x37C DisplayGfx object needed by
CTaskLand__InitLandscape
Add missing constructor end-section calls:
- Release primary GfxHandler (vtable[3])
- DDGame__InitDisplayFinal_Maybe (0x56A830)
- Display layer visibility (vtable[0x17] for layers 1-3)
Rename DDGame__LoadWeaponSprites_Maybe → DDGameWrapper__LoadingProgressTick
(0x5717A0 is just a progress bar + message pump, not a sprite loader).
Headful replay completes successfully. Headless shows frame-0 checksum
mismatch — some initial DDGame state differs from the original constructor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -345,8 +345,12 @@ pub mod va {
|
|
| 345 |
pub const TASK_STATE_MACHINE_INIT: u32 = 0x004F_6370;
|
| 346 |
/// DDGame__InitSoundPaths_Maybe (0x570F30): called before DSSound_LoadEffectWAVs.
|
| 347 |
pub const DDGAME_INIT_SOUND_PATHS: u32 = 0x0057_0F30;
|
| 348 |
-
///
|
| 349 |
-
pub const
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
/// DDGame__InitDisplayFinal_Maybe (0x56A830): display finalization for non-headless.
|
| 351 |
pub const DDGAME_INIT_DISPLAY_FINAL: u32 = 0x0056_A830;
|
| 352 |
/// PCLandscape__Constructor (0x57ACB0): constructs 0xB44-byte landscape object.
|
|
|
|
| 345 |
pub const TASK_STATE_MACHINE_INIT: u32 = 0x004F_6370;
|
| 346 |
/// DDGame__InitSoundPaths_Maybe (0x570F30): called before DSSound_LoadEffectWAVs.
|
| 347 |
pub const DDGAME_INIT_SOUND_PATHS: u32 = 0x0057_0F30;
|
| 348 |
+
/// DDGameWrapper__LoadingProgressTick (0x5717A0): loading progress bar + message pump.
|
| 349 |
+
pub const DDGAME_WRAPPER_LOADING_PROGRESS_TICK: u32 = 0x0057_17A0;
|
| 350 |
+
/// DDGame__LoadHudAndWeaponSprites (0x53D0E0): loads weapon icons, wind indicators,
|
| 351 |
+
/// girder sprites, and creates DDGame+0x37C DisplayGfx object.
|
| 352 |
+
/// thiscall(ECX=gfx_handler_4c0) + 2 stack(ddgame, wrapper_4c4), RET 0x8.
|
| 353 |
+
pub const DDGAME_LOAD_HUD_AND_WEAPON_SPRITES: u32 = 0x0053_D0E0;
|
| 354 |
/// DDGame__InitDisplayFinal_Maybe (0x56A830): display finalization for non-headless.
|
| 355 |
pub const DDGAME_INIT_DISPLAY_FINAL: u32 = 0x0056_A830;
|
| 356 |
/// PCLandscape__Constructor (0x57ACB0): constructs 0xB44-byte landscape object.
|
|
@@ -912,7 +912,7 @@ pub fn init_constructor_addrs() {
|
|
| 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 |
-
|
| 916 |
}
|
| 917 |
}
|
| 918 |
|
|
@@ -1264,8 +1264,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 1264 |
let _ = crate::log::log_line("[DDGame] past ActiveSoundTable");
|
| 1265 |
|
| 1266 |
// ── Loading tick: disabled (pumps message loop, causes crashes) ──
|
| 1267 |
-
// call_usercall_ecx(wrapper,
|
| 1268 |
-
// call_usercall_ecx(wrapper,
|
| 1269 |
|
| 1270 |
// ── GfxResource: thiscall(ECX=gfx_handler) + EAX=name + 1 stack(output), RET 0x4 ──
|
| 1271 |
// Test: call GfxResource with real params
|
|
@@ -1368,8 +1368,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1368 |
(0x480, 0x2D, 0x08, 0x2D, 0x07, 0x2E, 0x07, gr),
|
| 1369 |
];
|
| 1370 |
|
| 1371 |
-
// SKIP: testing if SpriteRegion bridge corrupts stack
|
| 1372 |
-
if false {
|
| 1373 |
for &(offset, ecx, edx, p2, p3, p4, p5, p6) in ®ions {
|
| 1374 |
let alloc = wa_malloc(0x9C);
|
| 1375 |
core::ptr::write_bytes(alloc, 0, 0x9C);
|
|
@@ -1380,7 +1378,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 1380 |
};
|
| 1381 |
*((ddgame as *mut u8).add(offset as usize) as *mut *mut u8) = result;
|
| 1382 |
}
|
| 1383 |
-
} // if false
|
| 1384 |
}
|
| 1385 |
|
| 1386 |
// ── Landscape-derived value at DDGame+0x468 ──
|
|
@@ -1522,8 +1519,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 1522 |
}
|
| 1523 |
|
| 1524 |
// ── Loading tick: disabled (pumps message loop) ──
|
| 1525 |
-
// call_usercall_ecx(wrapper,
|
| 1526 |
-
// call_usercall_ecx(wrapper,
|
| 1527 |
|
| 1528 |
// ── Sprite resource loading via DDGameWrapper vtable[0] ──
|
| 1529 |
// DDNetGameWrapper__LoadResourceList: thiscall(ECX=wrapper) +
|
|
@@ -1534,26 +1531,33 @@ unsafe fn init_graphics_and_resources(
|
|
| 1534 |
let land_layer = *(landscape_ptr.add(0xB34) as *const *mut u8);
|
| 1535 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 1536 |
|
| 1537 |
-
// Sprite resource loading + remaining: disabled to isolate DEP crash
|
| 1538 |
-
let _ = crate::log::log_line("[DDGame] skipping sprite resources+");
|
| 1539 |
-
if false {
|
| 1540 |
let wrapper_vt = *(wrapper as *const *const u32);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1541 |
let load_resource_list: unsafe extern "thiscall" fn(
|
| 1542 |
*mut DDGameWrapper, u32, *mut u8, *const u8, *const u8, u32,
|
| 1543 |
) = core::mem::transmute(*wrapper_vt);
|
| 1544 |
// Load resources for layer 1 (main sprites)
|
|
|
|
| 1545 |
load_resource_list(
|
| 1546 |
wrapper, 1, gfx_handler,
|
| 1547 |
rb(0x643F2B) as *const u8, // base path
|
| 1548 |
rb(0x6AD2C0) as *const u8, // resource table
|
| 1549 |
0x1D88, // table size
|
| 1550 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1551 |
|
| 1552 |
// Set global flag based on game version
|
| 1553 |
let gv = (*(*ddgame).game_info).game_version;
|
| 1554 |
*(rb(0x6AF050) as *mut u32) = if gv < 8 { 0 } else { 0x10 };
|
| 1555 |
|
| 1556 |
// Load resources for layer 1 with different table
|
|
|
|
| 1557 |
load_resource_list(
|
| 1558 |
wrapper, 1, gfx_handler,
|
| 1559 |
rb(0x643F2B) as *const u8,
|
|
@@ -1562,6 +1566,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1562 |
);
|
| 1563 |
|
| 1564 |
// Load resources for layer 2 (water)
|
|
|
|
| 1565 |
load_resource_list(
|
| 1566 |
wrapper, 2, water_layer,
|
| 1567 |
rb(0x643F2B) as *const u8,
|
|
@@ -1638,22 +1643,20 @@ unsafe fn init_graphics_and_resources(
|
|
| 1638 |
}
|
| 1639 |
}
|
| 1640 |
|
| 1641 |
-
// ──
|
| 1642 |
-
//
|
| 1643 |
-
//
|
| 1644 |
-
//
|
| 1645 |
-
//
|
| 1646 |
-
|
| 1647 |
-
|
| 1648 |
-
|
| 1649 |
-
|
| 1650 |
-
|
| 1651 |
-
|
| 1652 |
-
|
| 1653 |
-
|
| 1654 |
-
|
| 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.
|
|
@@ -1670,6 +1673,38 @@ unsafe fn init_graphics_and_resources(
|
|
| 1670 |
}
|
| 1671 |
}
|
| 1672 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1673 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1674 |
}
|
| 1675 |
|
|
@@ -1707,7 +1742,7 @@ static mut FUN_570E20_ADDR: u32 = 0;
|
|
| 1707 |
static mut FUN_570A90_ADDR: u32 = 0;
|
| 1708 |
static mut FUN_570F30_ADDR: u32 = 0;
|
| 1709 |
static mut LOAD_SPEECH_BANKS_ADDR: u32 = 0;
|
| 1710 |
-
static mut
|
| 1711 |
|
| 1712 |
/// Bridge: usercall(ESI=wrapper), plain RET. Used by FUN_570E20, FUN_570F30, LoadSpeechBanks.
|
| 1713 |
#[cfg(target_arch = "x86")]
|
|
|
|
| 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 |
+
LOADING_PROGRESS_TICK_ADDR = rb(va::DDGAME_WRAPPER_LOADING_PROGRESS_TICK);
|
| 916 |
}
|
| 917 |
}
|
| 918 |
|
|
|
|
| 1264 |
let _ = crate::log::log_line("[DDGame] past ActiveSoundTable");
|
| 1265 |
|
| 1266 |
// ── Loading tick: disabled (pumps message loop, causes crashes) ──
|
| 1267 |
+
// call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
| 1268 |
+
// call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
| 1269 |
|
| 1270 |
// ── GfxResource: thiscall(ECX=gfx_handler) + EAX=name + 1 stack(output), RET 0x4 ──
|
| 1271 |
// Test: call GfxResource with real params
|
|
|
|
| 1368 |
(0x480, 0x2D, 0x08, 0x2D, 0x07, 0x2E, 0x07, gr),
|
| 1369 |
];
|
| 1370 |
|
|
|
|
|
|
|
| 1371 |
for &(offset, ecx, edx, p2, p3, p4, p5, p6) in ®ions {
|
| 1372 |
let alloc = wa_malloc(0x9C);
|
| 1373 |
core::ptr::write_bytes(alloc, 0, 0x9C);
|
|
|
|
| 1378 |
};
|
| 1379 |
*((ddgame as *mut u8).add(offset as usize) as *mut *mut u8) = result;
|
| 1380 |
}
|
|
|
|
| 1381 |
}
|
| 1382 |
|
| 1383 |
// ── Landscape-derived value at DDGame+0x468 ──
|
|
|
|
| 1519 |
}
|
| 1520 |
|
| 1521 |
// ── Loading tick: disabled (pumps message loop) ──
|
| 1522 |
+
// call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
| 1523 |
+
// call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
| 1524 |
|
| 1525 |
// ── Sprite resource loading via DDGameWrapper vtable[0] ──
|
| 1526 |
// DDNetGameWrapper__LoadResourceList: thiscall(ECX=wrapper) +
|
|
|
|
| 1531 |
let land_layer = *(landscape_ptr.add(0xB34) as *const *mut u8);
|
| 1532 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 1533 |
|
|
|
|
|
|
|
|
|
|
| 1534 |
let wrapper_vt = *(wrapper as *const *const u32);
|
| 1535 |
+
let _ = crate::log::log_line(&format!(
|
| 1536 |
+
"[DDGame] sprite resource loading: wrapper_vt=0x{:08X}, vt[0]=0x{:08X}, landscape=0x{:08X}, water=0x{:08X}, land=0x{:08X}",
|
| 1537 |
+
wrapper_vt as u32, *wrapper_vt, landscape_ptr as u32, water_layer as u32, land_layer as u32,
|
| 1538 |
+
));
|
| 1539 |
let load_resource_list: unsafe extern "thiscall" fn(
|
| 1540 |
*mut DDGameWrapper, u32, *mut u8, *const u8, *const u8, u32,
|
| 1541 |
) = core::mem::transmute(*wrapper_vt);
|
| 1542 |
// Load resources for layer 1 (main sprites)
|
| 1543 |
+
let _ = crate::log::log_line("[DDGame] load_resource_list #1 (main sprites)");
|
| 1544 |
load_resource_list(
|
| 1545 |
wrapper, 1, gfx_handler,
|
| 1546 |
rb(0x643F2B) as *const u8, // base path
|
| 1547 |
rb(0x6AD2C0) as *const u8, // resource table
|
| 1548 |
0x1D88, // table size
|
| 1549 |
);
|
| 1550 |
+
let _ = crate::log::log_line(&format!(
|
| 1551 |
+
"[DDGame] after #1: sprite_cache[144]=0x{:08X}",
|
| 1552 |
+
(*ddgame).sprite_cache[144] as u32,
|
| 1553 |
+
));
|
| 1554 |
|
| 1555 |
// Set global flag based on game version
|
| 1556 |
let gv = (*(*ddgame).game_info).game_version;
|
| 1557 |
*(rb(0x6AF050) as *mut u32) = if gv < 8 { 0 } else { 0x10 };
|
| 1558 |
|
| 1559 |
// Load resources for layer 1 with different table
|
| 1560 |
+
let _ = crate::log::log_line("[DDGame] load_resource_list #2");
|
| 1561 |
load_resource_list(
|
| 1562 |
wrapper, 1, gfx_handler,
|
| 1563 |
rb(0x643F2B) as *const u8,
|
|
|
|
| 1566 |
);
|
| 1567 |
|
| 1568 |
// Load resources for layer 2 (water)
|
| 1569 |
+
let _ = crate::log::log_line("[DDGame] load_resource_list #3 (water)");
|
| 1570 |
load_resource_list(
|
| 1571 |
wrapper, 2, water_layer,
|
| 1572 |
rb(0x643F2B) as *const u8,
|
|
|
|
| 1643 |
}
|
| 1644 |
}
|
| 1645 |
|
| 1646 |
+
// ── DDGame__LoadHudAndWeaponSprites (0x53D0E0) ──
|
| 1647 |
+
// Loads weapon icons (cow.img, pigeon.img, etc.), wind indicators, stop sign,
|
| 1648 |
+
// girder sprites, and creates the DDGame+0x37C DisplayGfx object needed by
|
| 1649 |
+
// CTaskLand__InitLandscape.
|
| 1650 |
+
// Convention: thiscall(ECX=gfx_handler_4c0) + 2 stack(ddgame, wrapper_4c4), RET 0x8.
|
| 1651 |
+
{
|
| 1652 |
+
let hud_load: unsafe extern "thiscall" fn(*mut u8, *mut DDGame, *mut u8) =
|
| 1653 |
+
core::mem::transmute(rb(va::DDGAME_LOAD_HUD_AND_WEAPON_SPRITES) as usize);
|
| 1654 |
+
hud_load(gfx_handler, ddgame, (*wrapper)._field_4c4);
|
| 1655 |
+
let _ = crate::log::log_line(&format!(
|
| 1656 |
+
"[DDGame] after LoadHudAndWeaponSprites: +0x37C=0x{:08X}",
|
| 1657 |
+
(*ddgame).sprite_cache[144] as u32,
|
| 1658 |
+
));
|
| 1659 |
+
}
|
|
|
|
|
|
|
| 1660 |
}
|
| 1661 |
// ── Gradient image stub (DDGame+0x30) ──
|
| 1662 |
// The original constructor loads "gradient.img" here and stores the result at DDGame+0x30.
|
|
|
|
| 1673 |
}
|
| 1674 |
}
|
| 1675 |
|
| 1676 |
+
// ── Release primary GfxHandler (vtable[3] = release, param 1 = free) ──
|
| 1677 |
+
let gfx_handler_4c0 = (*wrapper)._field_4c0;
|
| 1678 |
+
if !gfx_handler_4c0.is_null() {
|
| 1679 |
+
let gfx_vt = *(gfx_handler_4c0 as *const *const u32);
|
| 1680 |
+
let release: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 1681 |
+
core::mem::transmute(*gfx_vt.add(3));
|
| 1682 |
+
release(gfx_handler_4c0, 1);
|
| 1683 |
+
}
|
| 1684 |
+
|
| 1685 |
+
// ── DDGame__InitDisplayFinal_Maybe (0x56A830): non-headless display finalization ──
|
| 1686 |
+
if !is_headless {
|
| 1687 |
+
let f: unsafe extern "stdcall" fn(*mut DDDisplay) =
|
| 1688 |
+
core::mem::transmute(rb(va::DDGAME_INIT_DISPLAY_FINAL) as usize);
|
| 1689 |
+
f((*wrapper).display);
|
| 1690 |
+
}
|
| 1691 |
+
|
| 1692 |
+
// ── FUN_00570A90 (second call, conditional) ──
|
| 1693 |
+
if *(rb(0x88E485) as *const u8) == 0 {
|
| 1694 |
+
call_usercall_eax(wrapper, FUN_570A90_ADDR);
|
| 1695 |
+
}
|
| 1696 |
+
|
| 1697 |
+
// ── Final display layer visibility (vtable[0x17], offset 0x5C) ──
|
| 1698 |
+
{
|
| 1699 |
+
let disp = (*wrapper).display as *mut u8;
|
| 1700 |
+
let vt = *(disp as *const *const u32);
|
| 1701 |
+
let set_vis: unsafe extern "thiscall" fn(*mut u8, i32, i32) =
|
| 1702 |
+
core::mem::transmute(*vt.add(0x17));
|
| 1703 |
+
set_vis(disp, 1, 0);
|
| 1704 |
+
set_vis(disp, 2, 0);
|
| 1705 |
+
set_vis(disp, 3, 1);
|
| 1706 |
+
}
|
| 1707 |
+
|
| 1708 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1709 |
}
|
| 1710 |
|
|
|
|
| 1742 |
static mut FUN_570A90_ADDR: u32 = 0;
|
| 1743 |
static mut FUN_570F30_ADDR: u32 = 0;
|
| 1744 |
static mut LOAD_SPEECH_BANKS_ADDR: u32 = 0;
|
| 1745 |
+
static mut LOADING_PROGRESS_TICK_ADDR: u32 = 0;
|
| 1746 |
|
| 1747 |
/// Bridge: usercall(ESI=wrapper), plain RET. Used by FUN_570E20, FUN_570F30, LoadSpeechBanks.
|
| 1748 |
#[cfg(target_arch = "x86")]
|
|
@@ -114,7 +114,7 @@ pub(crate) unsafe fn construct_ddgame_wrapper(
|
|
| 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 =
|
| 118 |
if USE_RUST_CTOR {
|
| 119 |
create_ddgame(
|
| 120 |
this,
|
|
|
|
| 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 = true;
|
| 118 |
if USE_RUST_CTOR {
|
| 119 |
create_ddgame(
|
| 120 |
this,
|