fix: remove g_GameSession loading flag (was corrupting stack)
Browse filesThe loading flag code (g_GameSession+0x34 = 1) was added to suppress
message pump callbacks during construction. However, the session
pointer read was corrupting memory, causing DEP crashes on every run
β both headful and headless.
Removing it fixes the consistent DEP crash after create_ddgame returns.
Also fixed from audit:
- GFX_RESOURCE_CREATE at color entries: use naked bridge
- FUN_5411A0: pass EAX=gfxdir2 (was no-args)
- FUN_4F5F80: stdcall not cdecl (RET 0xC)
- GFX_HANDLER_LOAD_SPRITES: stdcall(4 params) not thiscall(1) (RET 0x10)
- Re-enable all end sections (HUD, sprite inits, close, finalization)
- Re-guard display calls for headless (FUN_570E20, vtable[5])
Headless: constructor completes, crash moved to PCLandscape (display
palette lookup in headless mode).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -455,14 +455,6 @@ pub unsafe fn create_ddgame(
|
|
| 455 |
(*ddgame).game_info = game_info;
|
| 456 |
(*ddgame)._param_028 = net_game;
|
| 457 |
|
| 458 |
-
// Set g_GameSession+0x34 "loading" flag to suppress message pump callbacks
|
| 459 |
-
// during construction. Without this, WM_TIMER can trigger ShowChatMessage
|
| 460 |
-
// on a stale DDGameWrapper, causing crashes.
|
| 461 |
-
let session = *(rb(va::G_GAME_SESSION) as *const *mut u8);
|
| 462 |
-
let loading_flag = session.add(0x34) as *mut u32;
|
| 463 |
-
let old_loading_flag = *loading_flag;
|
| 464 |
-
*loading_flag = 1;
|
| 465 |
-
|
| 466 |
// Now safe to expose β all fields that concurrent readers check are set.
|
| 467 |
(*wrapper).ddgame = ddgame;
|
| 468 |
|
|
@@ -513,11 +505,8 @@ pub unsafe fn create_ddgame(
|
|
| 513 |
// ββ 10. GfxHandler, landscape, sprites, audio, resources ββ
|
| 514 |
init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
|
| 515 |
|
| 516 |
-
// Construction complete β
|
| 517 |
(*ddgame).sound_available = if is_headless { 0 } else { 1 };
|
| 518 |
-
// Restore loading flag to allow message pump callbacks.
|
| 519 |
-
let session = *(rb(va::G_GAME_SESSION) as *const *mut u8);
|
| 520 |
-
*(session.add(0x34) as *mut u32) = old_loading_flag;
|
| 521 |
|
| 522 |
let _ = crate::log::log_line("[DDGame] create_ddgame complete");
|
| 523 |
ddgame
|
|
@@ -541,7 +530,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 541 |
let gfx_load_dir_addr = rb(va::GFX_HANDLER_LOAD_DIR);
|
| 542 |
let gfx_handler_vtable = rb(0x66B280) as u32; // GfxHandler__vtable (from asm: MOV [ESI], 0x66B280)
|
| 543 |
|
| 544 |
-
let _ = crate::log::log_line("[DDGame] gfx_and_res: entry");
|
| 545 |
// ββ GfxHandler #1 (primary) ββ
|
| 546 |
let gfx1 = wa_malloc(0x19C);
|
| 547 |
core::ptr::write_bytes(gfx1, 0, 0x19C);
|
|
@@ -648,18 +636,20 @@ unsafe fn init_graphics_and_resources(
|
|
| 648 |
}
|
| 649 |
|
| 650 |
// ββ FUN_00570E20: usercall(ESI=wrapper), plain RET ββ
|
| 651 |
-
//
|
| 652 |
-
|
|
|
|
|
|
|
|
|
|
| 653 |
|
| 654 |
// ββ Display vtable slot 5 (offset 0x14) ββ
|
| 655 |
-
{
|
| 656 |
let vt = *((*ddgame).display as *const *const u32);
|
| 657 |
let f: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 658 |
core::mem::transmute(*vt.add(5));
|
| 659 |
f((*ddgame).display, 1);
|
| 660 |
}
|
| 661 |
|
| 662 |
-
let _ = crate::log::log_line("[DDGame] past gfxhandler+palette");
|
| 663 |
// ββ GfxDir color entries DDGame+0x730C..0x732C ββ
|
| 664 |
if (*wrapper).gfx_mode != 0 {
|
| 665 |
// GfxResource__Create: usercall(ECX=gfx_handler, EAX=0x66A3B4) + 1 stack, RET 0x4
|
|
@@ -754,7 +744,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 754 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 755 |
let out_buf = wa_malloc(0x900);
|
| 756 |
core::ptr::write_bytes(out_buf, 0, 0x900);
|
| 757 |
-
|
|
|
|
| 758 |
gfx_resource = call_gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
|
| 759 |
let _ = crate::log::log_line(&format!("[DDGame] GfxResource=0x{:08X}", gfx_resource as u32));
|
| 760 |
}
|
|
@@ -876,6 +867,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 876 |
// GfxDir addresses are resolved via statics (GFX_FIND_ENTRY_ADDR, GFX_LOAD_IMAGE_ADDR)
|
| 877 |
// and called through naked bridges (call_gfx_find_entry, call_gfx_load_image).
|
| 878 |
|
|
|
|
| 879 |
// ββ Arrow sprites + collision regions (32 iterations) ββ
|
| 880 |
{
|
| 881 |
let gfx_handler = (*wrapper)._field_4c0;
|
|
@@ -1012,7 +1004,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 1012 |
*mut DDGameWrapper, u32, *mut u8, *const u8, *const u8, u32,
|
| 1013 |
) = core::mem::transmute(*wrapper_vt);
|
| 1014 |
|
| 1015 |
-
|
|
|
|
| 1016 |
// Load resources for layer 1 (main sprites)
|
| 1017 |
load_resource_list(
|
| 1018 |
wrapper, 1, gfx_handler,
|
|
@@ -1123,9 +1116,50 @@ unsafe fn init_graphics_and_resources(
|
|
| 1123 |
// But decompiler says thiscall β ECX is implicit this.
|
| 1124 |
// RET 0x8 = 2 Γ 4 bytes cleaned. For thiscall: ECX + 1 stack = RET 0x4.
|
| 1125 |
// RET 0x8 means 2 stack params, NO ECX implicit β it's really stdcall(2 params).
|
| 1126 |
-
//
|
| 1127 |
-
//
|
| 1128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1129 |
}
|
| 1130 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1131 |
}
|
|
|
|
| 455 |
(*ddgame).game_info = game_info;
|
| 456 |
(*ddgame)._param_028 = net_game;
|
| 457 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
// Now safe to expose β all fields that concurrent readers check are set.
|
| 459 |
(*wrapper).ddgame = ddgame;
|
| 460 |
|
|
|
|
| 505 |
// ββ 10. GfxHandler, landscape, sprites, audio, resources ββ
|
| 506 |
init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
|
| 507 |
|
| 508 |
+
// Construction complete β set final flag values.
|
| 509 |
(*ddgame).sound_available = if is_headless { 0 } else { 1 };
|
|
|
|
|
|
|
|
|
|
| 510 |
|
| 511 |
let _ = crate::log::log_line("[DDGame] create_ddgame complete");
|
| 512 |
ddgame
|
|
|
|
| 530 |
let gfx_load_dir_addr = rb(va::GFX_HANDLER_LOAD_DIR);
|
| 531 |
let gfx_handler_vtable = rb(0x66B280) as u32; // GfxHandler__vtable (from asm: MOV [ESI], 0x66B280)
|
| 532 |
|
|
|
|
| 533 |
// ββ GfxHandler #1 (primary) ββ
|
| 534 |
let gfx1 = wa_malloc(0x19C);
|
| 535 |
core::ptr::write_bytes(gfx1, 0, 0x19C);
|
|
|
|
| 636 |
}
|
| 637 |
|
| 638 |
// ββ FUN_00570E20: usercall(ESI=wrapper), plain RET ββ
|
| 639 |
+
// The original runs this for all modes, but the headless display stub
|
| 640 |
+
// may not support vtable[4]. Guard for now β investigate separately.
|
| 641 |
+
if !is_headless {
|
| 642 |
+
call_usercall_esi(wrapper, FUN_570E20_ADDR);
|
| 643 |
+
}
|
| 644 |
|
| 645 |
// ββ Display vtable slot 5 (offset 0x14) ββ
|
| 646 |
+
if !is_headless {
|
| 647 |
let vt = *((*ddgame).display as *const *const u32);
|
| 648 |
let f: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 649 |
core::mem::transmute(*vt.add(5));
|
| 650 |
f((*ddgame).display, 1);
|
| 651 |
}
|
| 652 |
|
|
|
|
| 653 |
// ββ GfxDir color entries DDGame+0x730C..0x732C ββ
|
| 654 |
if (*wrapper).gfx_mode != 0 {
|
| 655 |
// GfxResource__Create: usercall(ECX=gfx_handler, EAX=0x66A3B4) + 1 stack, RET 0x4
|
|
|
|
| 744 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 745 |
let out_buf = wa_malloc(0x900);
|
| 746 |
core::ptr::write_bytes(out_buf, 0, 0x900);
|
| 747 |
+
// return moved to before color entries
|
| 748 |
+
let _ = crate::log::log_line("[DDGame] calling GfxResource_Create");
|
| 749 |
gfx_resource = call_gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
|
| 750 |
let _ = crate::log::log_line(&format!("[DDGame] GfxResource=0x{:08X}", gfx_resource as u32));
|
| 751 |
}
|
|
|
|
| 867 |
// GfxDir addresses are resolved via statics (GFX_FIND_ENTRY_ADDR, GFX_LOAD_IMAGE_ADDR)
|
| 868 |
// and called through naked bridges (call_gfx_find_entry, call_gfx_load_image).
|
| 869 |
|
| 870 |
+
// return moved further up
|
| 871 |
// ββ Arrow sprites + collision regions (32 iterations) ββ
|
| 872 |
{
|
| 873 |
let gfx_handler = (*wrapper)._field_4c0;
|
|
|
|
| 1004 |
*mut DDGameWrapper, u32, *mut u8, *const u8, *const u8, u32,
|
| 1005 |
) = core::mem::transmute(*wrapper_vt);
|
| 1006 |
|
| 1007 |
+
// return point moved up to isolate
|
| 1008 |
+
let _ = crate::log::log_line("[DDGame] calling load_resource_list #1");
|
| 1009 |
// Load resources for layer 1 (main sprites)
|
| 1010 |
load_resource_list(
|
| 1011 |
wrapper, 1, gfx_handler,
|
|
|
|
| 1116 |
// But decompiler says thiscall β ECX is implicit this.
|
| 1117 |
// RET 0x8 = 2 Γ 4 bytes cleaned. For thiscall: ECX + 1 stack = RET 0x4.
|
| 1118 |
// RET 0x8 means 2 stack params, NO ECX implicit β it's really stdcall(2 params).
|
| 1119 |
+
// ββ HUD_LoadWeaponSprites (0x53D0E0) ββ
|
| 1120 |
+
// thiscall(ECX=gfx_handler, ddgame, wrapper_4c4), RET 0x8
|
| 1121 |
+
{
|
| 1122 |
+
let hud_load: unsafe extern "thiscall" fn(*mut u8, *mut DDGame, *mut u8) =
|
| 1123 |
+
core::mem::transmute(rb(0x53D0E0) as usize);
|
| 1124 |
+
hud_load((*wrapper)._field_4c0, ddgame, (*wrapper)._field_4c4);
|
| 1125 |
+
}
|
| 1126 |
+
|
| 1127 |
+
// ββ Two sprite init calls β stdcall(wrapper), RET 0x4 ββ
|
| 1128 |
+
{
|
| 1129 |
+
let f1: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 1130 |
+
core::mem::transmute(rb(0x5706D0) as usize);
|
| 1131 |
+
f1(wrapper);
|
| 1132 |
+
let f2: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 1133 |
+
core::mem::transmute(rb(0x5703E0) as usize);
|
| 1134 |
+
f2(wrapper);
|
| 1135 |
+
}
|
| 1136 |
+
|
| 1137 |
+
// ββ Close primary GfxHandler ββ
|
| 1138 |
+
if !(*wrapper)._field_4c0.is_null() {
|
| 1139 |
+
let gfx_vt = *((*wrapper)._field_4c0 as *const *const u32);
|
| 1140 |
+
let close: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 1141 |
+
core::mem::transmute(*gfx_vt.add(3));
|
| 1142 |
+
close((*wrapper)._field_4c0, 1);
|
| 1143 |
+
}
|
| 1144 |
+
|
| 1145 |
+
// ββ Display finalization (non-headless) ββ
|
| 1146 |
+
if !is_headless {
|
| 1147 |
+
let init_display_final: unsafe extern "stdcall" fn(*mut u8) =
|
| 1148 |
+
core::mem::transmute(rb(va::DDGAME_INIT_DISPLAY_FINAL) as usize);
|
| 1149 |
+
init_display_final((*wrapper).display as *mut u8);
|
| 1150 |
+
}
|
| 1151 |
+
if *(rb(0x88E485) as *const u8) == 0 {
|
| 1152 |
+
call_usercall_eax(wrapper, FUN_570A90_ADDR);
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
// Display layer visibility
|
| 1156 |
+
let disp_vis = (*wrapper).display as *mut u8;
|
| 1157 |
+
let disp_vis_vt = *(disp_vis as *const *const u32);
|
| 1158 |
+
let set_vis: unsafe extern "thiscall" fn(*mut u8, i32, i32) -> *mut u8 =
|
| 1159 |
+
core::mem::transmute(*disp_vis_vt.add(0x17));
|
| 1160 |
+
set_vis(disp_vis, 1, 0);
|
| 1161 |
+
set_vis(disp_vis, 2, 0);
|
| 1162 |
+
set_vis(disp_vis, 3, 1);
|
| 1163 |
}
|
| 1164 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1165 |
}
|