feat: complete remaining DDGame constructor sections
Browse filesPort the final ~40% of DDGame__Constructor:
- Sprite resource loading via DDGameWrapper vtable[0] (3 calls)
- back.spr, debris.spr, layer.spr loading via display vtable
- Gradient image loading (simple path; complex gradient TODO)
- Fill image → fill_pixel at DDGame+0x7338
- HUD weapon sprites
- GfxHandler cleanup
- Display finalization and layer visibility
Also add pre-InitGameState field dump to shadow comparison for
precise verification of constructor output (396 vs 2794 DWORDs).
Fix GfxDir address variables to be in shared scope for all sections.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -823,13 +823,14 @@ unsafe fn init_graphics_and_resources(
|
|
| 823 |
|
| 824 |
let _ = log_line("[DDGame] SpriteRegions + TSM done");
|
| 825 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 826 |
// ── Arrow sprites + collision regions (32 iterations) ──
|
| 827 |
{
|
| 828 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 829 |
-
let gfx_find_entry = rb(va::GFX_DIR_FIND_ENTRY) as u32;
|
| 830 |
-
let gfx_load_image = rb(va::GFX_DIR_LOAD_IMAGE) as u32;
|
| 831 |
-
let fun_4f5f80 = rb(0x4F5F80) as u32;
|
| 832 |
-
let _sprintf = rb(0x5D125A) as u32;
|
| 833 |
|
| 834 |
for i in 0u32..32 {
|
| 835 |
// Format "arrow%02u.img\0" into stack buffer
|
|
@@ -948,19 +949,222 @@ unsafe fn init_graphics_and_resources(
|
|
| 948 |
|
| 949 |
let _ = log_line("[DDGame] weapon sprites done");
|
| 950 |
|
| 951 |
-
// ──
|
| 952 |
-
//
|
| 953 |
-
//
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
|
| 962 |
-
|
| 963 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 964 |
}
|
| 965 |
|
| 966 |
/// Bridge to GfxHandler__LoadDir (0x5663E0).
|
|
@@ -985,7 +1189,7 @@ unsafe fn call_gfx_load_dir(handler: *mut u8, addr: u32) -> i32 {
|
|
| 985 |
unsafe fn call_gfx_load_and_wrap(
|
| 986 |
name: *const u8,
|
| 987 |
gfx_load_image_addr: u32,
|
| 988 |
-
|
| 989 |
) -> *mut u8 {
|
| 990 |
// GfxDir__LoadImage: usercall(EAX=name?), stdcall(1 stack param), RET 0x4
|
| 991 |
let image: *mut u8;
|
|
@@ -1012,7 +1216,7 @@ unsafe fn call_gfx_load_and_wrap(
|
|
| 1012 |
"add esp, 12",
|
| 1013 |
img = in(reg) image,
|
| 1014 |
ctx = in(reg) 0u32, // display_gfx context, often 0
|
| 1015 |
-
wrap = in(reg)
|
| 1016 |
out("eax") result,
|
| 1017 |
out("ecx") _,
|
| 1018 |
out("edx") _,
|
|
|
|
| 823 |
|
| 824 |
let _ = log_line("[DDGame] SpriteRegions + TSM done");
|
| 825 |
|
| 826 |
+
// Resolved addresses for GfxDir operations (used by arrows, gradients, fill)
|
| 827 |
+
let gfx_find_entry = rb(va::GFX_DIR_FIND_ENTRY) as u32;
|
| 828 |
+
let gfx_load_image = rb(va::GFX_DIR_LOAD_IMAGE) as u32;
|
| 829 |
+
let fun_4f5f80 = rb(0x4F5F80) as u32;
|
| 830 |
+
|
| 831 |
// ── Arrow sprites + collision regions (32 iterations) ──
|
| 832 |
{
|
| 833 |
let gfx_handler = (*wrapper)._field_4c0;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 834 |
|
| 835 |
for i in 0u32..32 {
|
| 836 |
// Format "arrow%02u.img\0" into stack buffer
|
|
|
|
| 949 |
|
| 950 |
let _ = log_line("[DDGame] weapon sprites done");
|
| 951 |
|
| 952 |
+
// ── Sprite resource loading via DDGameWrapper vtable[0] ──
|
| 953 |
+
// DDNetGameWrapper__LoadResourceList: thiscall(ECX=wrapper) +
|
| 954 |
+
// 5 stack params (layer, gfx_handler, base_path, data_table, table_size)
|
| 955 |
+
{
|
| 956 |
+
let landscape_ptr = (*wrapper).landscape as *const u8;
|
| 957 |
+
let water_layer = *(landscape_ptr.add(0xB38) as *const *mut u8);
|
| 958 |
+
let land_layer = *(landscape_ptr.add(0xB34) as *const *mut u8);
|
| 959 |
+
let gfx_handler = (*wrapper)._field_4c0;
|
| 960 |
+
|
| 961 |
+
let wrapper_vt = *(wrapper as *const *const u32);
|
| 962 |
+
let load_resource_list: unsafe extern "thiscall" fn(
|
| 963 |
+
*mut DDGameWrapper, u32, *mut u8, *const u8, *const u8, u32,
|
| 964 |
+
) = core::mem::transmute(*wrapper_vt);
|
| 965 |
+
|
| 966 |
+
// Load resources for layer 1 (main sprites)
|
| 967 |
+
load_resource_list(
|
| 968 |
+
wrapper, 1, gfx_handler,
|
| 969 |
+
rb(0x643F2B) as *const u8, // base path
|
| 970 |
+
rb(0x6AD2C0) as *const u8, // resource table
|
| 971 |
+
0x1D88, // table size
|
| 972 |
+
);
|
| 973 |
+
|
| 974 |
+
// Set global flag based on game version
|
| 975 |
+
let d778 = *((*ddgame).game_info as *const u8).add(0xD778).cast::<i32>();
|
| 976 |
+
*(rb(0x6AF050) as *mut u32) = if d778 < 8 { 0 } else { 0x10 };
|
| 977 |
+
|
| 978 |
+
// Load resources for layer 1 with different table
|
| 979 |
+
load_resource_list(
|
| 980 |
+
wrapper, 1, gfx_handler,
|
| 981 |
+
rb(0x643F2B) as *const u8,
|
| 982 |
+
rb(0x6AF048) as *const u8,
|
| 983 |
+
0x18,
|
| 984 |
+
);
|
| 985 |
+
|
| 986 |
+
// Load resources for layer 2 (water)
|
| 987 |
+
load_resource_list(
|
| 988 |
+
wrapper, 2, water_layer,
|
| 989 |
+
rb(0x643F2B) as *const u8,
|
| 990 |
+
rb(0x6AF060) as *const u8,
|
| 991 |
+
0x2F4,
|
| 992 |
+
);
|
| 993 |
+
|
| 994 |
+
// Display vtable[5](3) — set active layer 3, returns context ptr
|
| 995 |
+
let disp_vt = *((*ddgame).display as *const *const u32);
|
| 996 |
+
let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 997 |
+
core::mem::transmute(*disp_vt.add(5));
|
| 998 |
+
let _layer3_ctx = set_layer((*ddgame).display, 3);
|
| 999 |
+
|
| 1000 |
+
// Load back.spr and debris.spr (conditional)
|
| 1001 |
+
let disp_obj = *(wrapper as *const u8).add(0x4D0) as *mut u8;
|
| 1002 |
+
let disp_obj_vt = *(disp_obj as *const *const u32);
|
| 1003 |
+
// Check if gfx_mode high byte is set (original: uStack_123c._3_1_ != '\0')
|
| 1004 |
+
if (*wrapper).gfx_mode != 0 {
|
| 1005 |
+
let load_spr_94: unsafe extern "thiscall" fn(*mut u8, u32, u32, *mut u8, *const u8) =
|
| 1006 |
+
core::mem::transmute(*disp_obj_vt.add(0x94 / 4));
|
| 1007 |
+
load_spr_94(disp_obj, 3, 0x26D, land_layer, b"back.spr\0".as_ptr());
|
| 1008 |
+
|
| 1009 |
+
let load_spr_7c: unsafe extern "thiscall" fn(*mut u8, u32, u32, u32, *mut u8, *const u8) =
|
| 1010 |
+
core::mem::transmute(*disp_obj_vt.add(0x7C / 4));
|
| 1011 |
+
load_spr_7c(disp_obj, 3, 0x26E, 0, land_layer, b"debris.spr\0".as_ptr());
|
| 1012 |
+
}
|
| 1013 |
+
|
| 1014 |
+
// Load layer.spr into layer 2
|
| 1015 |
+
let load_spr_94: unsafe extern "thiscall" fn(*mut u8, u32, u32, *mut u8, *const u8) =
|
| 1016 |
+
core::mem::transmute(*disp_obj_vt.add(0x94 / 4));
|
| 1017 |
+
load_spr_94(disp_obj, 2, 0x26C, water_layer,
|
| 1018 |
+
b"layer\\layer.spr|layer.spr\0".as_ptr());
|
| 1019 |
+
|
| 1020 |
+
// gradient_image_2 = 0
|
| 1021 |
+
(*ddgame).gradient_image_2 = core::ptr::null_mut();
|
| 1022 |
+
|
| 1023 |
+
let _ = log_line("[DDGame] sprite resources loaded");
|
| 1024 |
+
|
| 1025 |
+
// ── Gradient image (0x030) ──
|
| 1026 |
+
// The gradient is loaded from "gradient.img" via GfxDir.
|
| 1027 |
+
// Simple path: height <= 0x60 AND level_height == 0x2B8
|
| 1028 |
+
let level_height = (*ddgame).level_height as i32;
|
| 1029 |
+
// Read sVar1 from display layer 3 context
|
| 1030 |
+
let layer3_ctx = set_layer((*ddgame).display, 3);
|
| 1031 |
+
let s_var1 = *(layer3_ctx.add(0x606) as *const i16);
|
| 1032 |
+
|
| 1033 |
+
if s_var1 < 0x61 && level_height == 0x2B8 {
|
| 1034 |
+
// Simple gradient: load gradient.img directly
|
| 1035 |
+
let gradient = call_gfx_find_and_load(
|
| 1036 |
+
land_layer, b"gradient.img\0".as_ptr(),
|
| 1037 |
+
gfx_find_entry, gfx_load_image, fun_4f5f80,
|
| 1038 |
+
layer3_ctx,
|
| 1039 |
+
);
|
| 1040 |
+
(*ddgame).gradient_image = gradient;
|
| 1041 |
+
} else {
|
| 1042 |
+
// Complex gradient computation — ~200 lines of color interpolation.
|
| 1043 |
+
// For now, skip this path (gradient_image remains null/0).
|
| 1044 |
+
// This affects the sky background appearance but not gameplay.
|
| 1045 |
+
let _ = log_line("[DDGame] WARNING: complex gradient path not ported, skipping");
|
| 1046 |
+
// Set gradient_image to null (already zero from memset)
|
| 1047 |
+
}
|
| 1048 |
+
|
| 1049 |
+
// ── Fill image → fill_pixel (0x7338) ──
|
| 1050 |
+
{
|
| 1051 |
+
let layer2_ctx = set_layer((*ddgame).display, 2);
|
| 1052 |
+
let fill_sprite = call_gfx_find_and_load(
|
| 1053 |
+
land_layer, b"fill.img\0".as_ptr(),
|
| 1054 |
+
gfx_find_entry, gfx_load_image, fun_4f5f80,
|
| 1055 |
+
layer2_ctx,
|
| 1056 |
+
);
|
| 1057 |
+
if !fill_sprite.is_null() {
|
| 1058 |
+
// Get pixel value: fill_sprite->vtable[4](0, 0)
|
| 1059 |
+
let fill_vt = *(fill_sprite as *const *const u32);
|
| 1060 |
+
let get_pixel: unsafe extern "thiscall" fn(*mut u8, i32, i32) -> u32 =
|
| 1061 |
+
core::mem::transmute(*fill_vt.add(4));
|
| 1062 |
+
(*ddgame).fill_pixel = get_pixel(fill_sprite, 0, 0);
|
| 1063 |
+
// Release fill sprite
|
| 1064 |
+
let release: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 1065 |
+
core::mem::transmute(*fill_vt);
|
| 1066 |
+
release(fill_sprite, 1);
|
| 1067 |
+
}
|
| 1068 |
+
}
|
| 1069 |
+
|
| 1070 |
+
let _ = log_line(&format!("[DDGame] fill_pixel=0x{:08X}", (*ddgame).fill_pixel));
|
| 1071 |
+
|
| 1072 |
+
// ── HUD weapon sprites ──
|
| 1073 |
+
{
|
| 1074 |
+
let hud_load: unsafe extern "stdcall" fn(u32, u32) =
|
| 1075 |
+
core::mem::transmute(rb(0x524070) as usize); // HUD_LoadWeaponSprites_Maybe address — verify
|
| 1076 |
+
hud_load(ddgame as u32, (*wrapper)._field_4c4 as u32);
|
| 1077 |
+
}
|
| 1078 |
+
|
| 1079 |
+
// ── Two DisplayGfx__Constructor_Maybe calls ──
|
| 1080 |
+
{
|
| 1081 |
+
let display_gfx_ctor: unsafe extern "thiscall" fn(*mut DDGameWrapper) =
|
| 1082 |
+
core::mem::transmute(rb(va::DISPLAYGFX_CTOR) as usize);
|
| 1083 |
+
display_gfx_ctor(wrapper);
|
| 1084 |
+
display_gfx_ctor(wrapper);
|
| 1085 |
+
}
|
| 1086 |
+
|
| 1087 |
+
// ── Close primary GfxHandler ──
|
| 1088 |
+
if !(*wrapper)._field_4c0.is_null() {
|
| 1089 |
+
let gfx_vt = *((*wrapper)._field_4c0 as *const *const u32);
|
| 1090 |
+
let close: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 1091 |
+
core::mem::transmute(*gfx_vt.add(3));
|
| 1092 |
+
close((*wrapper)._field_4c0, 1);
|
| 1093 |
+
}
|
| 1094 |
+
|
| 1095 |
+
// ── Display finalization (non-headless) ──
|
| 1096 |
+
if !is_headless {
|
| 1097 |
+
let init_display_final: unsafe extern "stdcall" fn(u32) =
|
| 1098 |
+
core::mem::transmute(rb(va::DDGAME_INIT_DISPLAY_FINAL) as usize);
|
| 1099 |
+
init_display_final(disp_obj as u32);
|
| 1100 |
+
}
|
| 1101 |
+
if *(rb(0x88E485) as *const u8) == 0 {
|
| 1102 |
+
let fun_570a90: unsafe extern "C" fn() =
|
| 1103 |
+
core::mem::transmute(rb(va::FUN_570A90) as usize);
|
| 1104 |
+
fun_570a90();
|
| 1105 |
+
}
|
| 1106 |
+
|
| 1107 |
+
// Display layer visibility: vt[0x5C](1,0), vt[0x5C](2,0), vt[0x5C](3,1)
|
| 1108 |
+
let disp_obj_vt = *(disp_obj as *const *const u32);
|
| 1109 |
+
let set_vis: unsafe extern "thiscall" fn(*mut u8, i32, i32) -> *mut u8 =
|
| 1110 |
+
core::mem::transmute(*disp_obj_vt.add(0x17));
|
| 1111 |
+
set_vis(disp_obj, 1, 0);
|
| 1112 |
+
set_vis(disp_obj, 2, 0);
|
| 1113 |
+
set_vis(disp_obj, 3, 1);
|
| 1114 |
+
}
|
| 1115 |
+
|
| 1116 |
+
let _ = log_line("[DDGame] create_ddgame complete");
|
| 1117 |
+
}
|
| 1118 |
+
|
| 1119 |
+
/// Helper: find entry in GfxDir and load image, or load directly.
|
| 1120 |
+
/// Returns a DisplayGfx/sprite pointer or null.
|
| 1121 |
+
#[cfg(target_arch = "x86")]
|
| 1122 |
+
unsafe fn call_gfx_find_and_load(
|
| 1123 |
+
gfx_dir: *mut u8,
|
| 1124 |
+
name: *const u8,
|
| 1125 |
+
find_entry_addr: u32,
|
| 1126 |
+
load_image_addr: u32,
|
| 1127 |
+
wrap_addr: u32,
|
| 1128 |
+
display_ctx: *mut u8,
|
| 1129 |
+
) -> *mut u8 {
|
| 1130 |
+
// GfxDir__FindEntry: usercall(EAX=name) + stdcall(gfx_dir), RET 0x4
|
| 1131 |
+
let entry: *mut u8;
|
| 1132 |
+
core::arch::asm!(
|
| 1133 |
+
"push {dir}",
|
| 1134 |
+
"call {find}",
|
| 1135 |
+
dir = in(reg) gfx_dir,
|
| 1136 |
+
find = in(reg) find_entry_addr,
|
| 1137 |
+
inlateout("eax") name => entry,
|
| 1138 |
+
out("ecx") _,
|
| 1139 |
+
out("edx") _,
|
| 1140 |
+
clobber_abi("C"),
|
| 1141 |
+
);
|
| 1142 |
+
|
| 1143 |
+
if !entry.is_null() {
|
| 1144 |
+
// Try cached load: gfx_dir->vtable[2](entry->field_4)
|
| 1145 |
+
let dir_vt = *(gfx_dir as *const *const u32);
|
| 1146 |
+
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 1147 |
+
core::mem::transmute(*dir_vt.add(2));
|
| 1148 |
+
let cached = load_cached(gfx_dir, *(entry.add(4) as *const u32));
|
| 1149 |
+
if !cached.is_null() {
|
| 1150 |
+
// Wrap with DisplayGfx constructor
|
| 1151 |
+
let result: *mut u8;
|
| 1152 |
+
core::arch::asm!(
|
| 1153 |
+
"push {cached}",
|
| 1154 |
+
"call {ctor}",
|
| 1155 |
+
cached = in(reg) cached,
|
| 1156 |
+
ctor = in(reg) rb(0x4F5E80) as u32, // DisplayGfx__Constructor_Maybe
|
| 1157 |
+
out("eax") result,
|
| 1158 |
+
out("ecx") _,
|
| 1159 |
+
out("edx") _,
|
| 1160 |
+
clobber_abi("C"),
|
| 1161 |
+
);
|
| 1162 |
+
return result;
|
| 1163 |
+
}
|
| 1164 |
+
}
|
| 1165 |
+
|
| 1166 |
+
// Fallback: load image directly
|
| 1167 |
+
call_gfx_load_and_wrap(name, load_image_addr, wrap_addr)
|
| 1168 |
}
|
| 1169 |
|
| 1170 |
/// Bridge to GfxHandler__LoadDir (0x5663E0).
|
|
|
|
| 1189 |
unsafe fn call_gfx_load_and_wrap(
|
| 1190 |
name: *const u8,
|
| 1191 |
gfx_load_image_addr: u32,
|
| 1192 |
+
fun_4f5f80: u32,
|
| 1193 |
) -> *mut u8 {
|
| 1194 |
// GfxDir__LoadImage: usercall(EAX=name?), stdcall(1 stack param), RET 0x4
|
| 1195 |
let image: *mut u8;
|
|
|
|
| 1216 |
"add esp, 12",
|
| 1217 |
img = in(reg) image,
|
| 1218 |
ctx = in(reg) 0u32, // display_gfx context, often 0
|
| 1219 |
+
wrap = in(reg) fun_4f5f80,
|
| 1220 |
out("eax") result,
|
| 1221 |
out("ecx") _,
|
| 1222 |
out("edx") _,
|
crates/openwa-wormkit/src/replacements/game_session.rs
CHANGED
|
@@ -117,6 +117,29 @@ pub(crate) unsafe fn construct_ddgame_wrapper(
|
|
| 117 |
timer_obj, net_game, game_info,
|
| 118 |
);
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
// Initialize DDGame's game-state fields.
|
| 121 |
let init_state: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 122 |
core::mem::transmute(rb(va::DDGAME_INIT_GAME_STATE) as usize);
|
|
@@ -216,27 +239,47 @@ unsafe fn shadow_compare_ddgame(
|
|
| 216 |
"[Shadow] Param check: {} OK, {} FAIL", ok, fail,
|
| 217 |
));
|
| 218 |
|
| 219 |
-
// Dump
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
(
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
}
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
wa_free(shadow_wrapper);
|
| 242 |
}
|
|
|
|
| 117 |
timer_obj, net_game, game_info,
|
| 118 |
);
|
| 119 |
|
| 120 |
+
// Dump DDGame state BEFORE InitGameState (constructor output only)
|
| 121 |
+
if std::env::var("OPENWA_VALIDATE").is_ok() {
|
| 122 |
+
let ddgame_pre = (*this).ddgame;
|
| 123 |
+
let real_dwords = ddgame_pre as *const u32;
|
| 124 |
+
let dword_count = 0x98B8 / 4;
|
| 125 |
+
let mut nonzero = 0u32;
|
| 126 |
+
let _ = log_line("[Shadow] === DDGame after constructor (before InitGameState) ===");
|
| 127 |
+
for i in 0..dword_count {
|
| 128 |
+
let val = *real_dwords.add(i);
|
| 129 |
+
if val != 0 {
|
| 130 |
+
nonzero += 1;
|
| 131 |
+
if nonzero <= 300 {
|
| 132 |
+
let _ = log_line(&format!(
|
| 133 |
+
"[Shadow:Pre] +0x{:04X} = 0x{:08X}", i * 4, val,
|
| 134 |
+
));
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
let _ = log_line(&format!(
|
| 139 |
+
"[Shadow:Pre] Total non-zero: {} / {}", nonzero, dword_count,
|
| 140 |
+
));
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
// Initialize DDGame's game-state fields.
|
| 144 |
let init_state: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 145 |
core::mem::transmute(rb(va::DDGAME_INIT_GAME_STATE) as usize);
|
|
|
|
| 239 |
"[Shadow] Param check: {} OK, {} FAIL", ok, fail,
|
| 240 |
));
|
| 241 |
|
| 242 |
+
// Dump ALL non-zero DWORDs from the real DDGame.
|
| 243 |
+
// This gives us the exact map of what the constructor initializes.
|
| 244 |
+
let real_dwords = real_ddgame as *const u32;
|
| 245 |
+
let dword_count = 0x98B8 / 4;
|
| 246 |
+
let mut nonzero = 0u32;
|
| 247 |
+
for i in 0..dword_count {
|
| 248 |
+
let val = *real_dwords.add(i);
|
| 249 |
+
if val != 0 {
|
| 250 |
+
nonzero += 1;
|
| 251 |
+
// Log up to 200 non-zero fields
|
| 252 |
+
if nonzero <= 200 {
|
| 253 |
+
let _ = log_line(&format!(
|
| 254 |
+
"[Shadow] DDGame+0x{:04X} = 0x{:08X}",
|
| 255 |
+
i * 4, val,
|
| 256 |
+
));
|
| 257 |
+
}
|
| 258 |
+
}
|
| 259 |
+
}
|
| 260 |
+
let _ = log_line(&format!(
|
| 261 |
+
"[Shadow] Total non-zero DWORDs: {} / {}", nonzero, dword_count,
|
| 262 |
+
));
|
| 263 |
+
|
| 264 |
+
// Also dump non-zero wrapper fields
|
| 265 |
+
let wrapper_dwords = core::mem::size_of::<DDGameWrapper>() / 4;
|
| 266 |
+
let real_w = wrapper as *const u32;
|
| 267 |
+
let mut w_nonzero = 0u32;
|
| 268 |
+
for i in 0..wrapper_dwords {
|
| 269 |
+
let val = *real_w.add(i);
|
| 270 |
+
if val != 0 {
|
| 271 |
+
w_nonzero += 1;
|
| 272 |
+
if w_nonzero <= 50 {
|
| 273 |
+
let _ = log_line(&format!(
|
| 274 |
+
"[Shadow] Wrapper+0x{:04X} = 0x{:08X}",
|
| 275 |
+
i * 4, val,
|
| 276 |
+
));
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
}
|
| 280 |
+
let _ = log_line(&format!(
|
| 281 |
+
"[Shadow] Wrapper non-zero DWORDs: {} / {}", w_nonzero, wrapper_dwords,
|
| 282 |
+
));
|
| 283 |
|
| 284 |
wa_free(shadow_wrapper);
|
| 285 |
}
|