debug: add milestone logging, identify LoadWeaponSprites message pump
Browse filesFound root cause of intermittent crashes: DDGame__LoadWeaponSprites
(0x5717A0) is a loading tick function that pumps the Windows message
loop (uses QueryPerformanceCounter + message dispatch). During this
pump, WM_TIMER/WM_PAINT handlers run game task code that reads DDGame
fields β if any field is still null, it crashes.
The original C++ constructor sets fields in an order compatible with
whatever the message pump reads. Our Rust version must match that
exact order, OR defer the loading tick calls until all critical fields
are initialized.
This is also why DSSound_LoadEffectWAVs (which calls LoadWeaponSprites
between each WAV) must have wrapper passed correctly β it too pumps
messages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -488,6 +488,11 @@ pub unsafe fn create_ddgame(
|
|
| 488 |
// ββ 10. GfxHandler, landscape, sprites, audio, resources ββ
|
| 489 |
init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
|
| 490 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
ddgame
|
| 492 |
}
|
| 493 |
|
|
@@ -575,6 +580,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 575 |
}
|
| 576 |
}
|
| 577 |
|
|
|
|
| 578 |
// ββ Display palette setup (non-headless) ββ
|
| 579 |
if !is_headless {
|
| 580 |
if *(rb(0x88E485) as *const u8) == 0 {
|
|
@@ -610,6 +616,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 610 |
}
|
| 611 |
}
|
| 612 |
|
|
|
|
| 613 |
// ββ FUN_00570E20: usercall(ESI=wrapper), plain RET ββ
|
| 614 |
call_usercall_esi(wrapper, FUN_570E20_ADDR);
|
| 615 |
|
|
@@ -673,6 +680,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 673 |
(*wrapper)._field_4e0 = 0xFFFFFF9C; // -100
|
| 674 |
(*wrapper).speech_name_count = 0;
|
| 675 |
|
|
|
|
| 676 |
// ββ Audio init (non-headless + sound available) ββ
|
| 677 |
if !is_headless {
|
| 678 |
// FUN_570F30: usercall(ESI=wrapper)
|
|
@@ -924,6 +932,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 924 |
// ββ Weapon sprites (FUN_005717A0 Γ2): usercall(ECX=wrapper) ββ
|
| 925 |
call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
| 926 |
call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
|
|
|
| 927 |
|
| 928 |
// ββ Sprite resource loading via DDGameWrapper vtable[0] ββ
|
| 929 |
// DDNetGameWrapper__LoadResourceList: thiscall(ECX=wrapper) +
|
|
@@ -940,12 +949,14 @@ unsafe fn init_graphics_and_resources(
|
|
| 940 |
) = core::mem::transmute(*wrapper_vt);
|
| 941 |
|
| 942 |
// Load resources for layer 1 (main sprites)
|
|
|
|
| 943 |
load_resource_list(
|
| 944 |
wrapper, 1, gfx_handler,
|
| 945 |
rb(0x643F2B) as *const u8, // base path
|
| 946 |
rb(0x6AD2C0) as *const u8, // resource table
|
| 947 |
0x1D88, // table size
|
| 948 |
);
|
|
|
|
| 949 |
|
| 950 |
// Set global flag based on game version
|
| 951 |
let d778 = *((*ddgame).game_info as *const u8).add(0xD778).cast::<i32>();
|
|
|
|
| 488 |
// ββ 10. GfxHandler, landscape, sprites, audio, resources ββ
|
| 489 |
init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
|
| 490 |
|
| 491 |
+
let _ = log_line(&format!(
|
| 492 |
+
"[DDGame] create_ddgame done: landscape=0x{:08X}, display_gfx=0x{:08X}, coord_list=0x{:08X}",
|
| 493 |
+
(*ddgame).landscape as u32, (*ddgame).display_gfx as u32, (*ddgame).coord_list as u32,
|
| 494 |
+
));
|
| 495 |
+
|
| 496 |
ddgame
|
| 497 |
}
|
| 498 |
|
|
|
|
| 580 |
}
|
| 581 |
}
|
| 582 |
|
| 583 |
+
let _ = log_line("[DDGame] past gfxhandlers");
|
| 584 |
// ββ Display palette setup (non-headless) ββ
|
| 585 |
if !is_headless {
|
| 586 |
if *(rb(0x88E485) as *const u8) == 0 {
|
|
|
|
| 616 |
}
|
| 617 |
}
|
| 618 |
|
| 619 |
+
let _ = log_line("[DDGame] past palette");
|
| 620 |
// ββ FUN_00570E20: usercall(ESI=wrapper), plain RET ββ
|
| 621 |
call_usercall_esi(wrapper, FUN_570E20_ADDR);
|
| 622 |
|
|
|
|
| 680 |
(*wrapper)._field_4e0 = 0xFFFFFF9C; // -100
|
| 681 |
(*wrapper).speech_name_count = 0;
|
| 682 |
|
| 683 |
+
let _ = log_line("[DDGame] past 570e20+display+colors+gfxdir+fields");
|
| 684 |
// ββ Audio init (non-headless + sound available) ββ
|
| 685 |
if !is_headless {
|
| 686 |
// FUN_570F30: usercall(ESI=wrapper)
|
|
|
|
| 932 |
// ββ Weapon sprites (FUN_005717A0 Γ2): usercall(ECX=wrapper) ββ
|
| 933 |
call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
| 934 |
call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
| 935 |
+
let _ = log_line("[DDGame] past audio init");
|
| 936 |
|
| 937 |
// ββ Sprite resource loading via DDGameWrapper vtable[0] ββ
|
| 938 |
// DDNetGameWrapper__LoadResourceList: thiscall(ECX=wrapper) +
|
|
|
|
| 949 |
) = core::mem::transmute(*wrapper_vt);
|
| 950 |
|
| 951 |
// Load resources for layer 1 (main sprites)
|
| 952 |
+
let _ = log_line("[DDGame] M2: load_resource_list #1");
|
| 953 |
load_resource_list(
|
| 954 |
wrapper, 1, gfx_handler,
|
| 955 |
rb(0x643F2B) as *const u8, // base path
|
| 956 |
rb(0x6AD2C0) as *const u8, // resource table
|
| 957 |
0x1D88, // table size
|
| 958 |
);
|
| 959 |
+
let _ = log_line("[DDGame] M3: load_resource_list #1 done");
|
| 960 |
|
| 961 |
// Set global flag based on game version
|
| 962 |
let d778 = *((*ddgame).game_info as *const u8).add(0xD778).cast::<i32>();
|