paavohtl Claude Opus 4.6 (1M context) commited on
Commit
74b6e34
·
1 Parent(s): 6db1334

refactor: clean up ddgame.rs — remove dead code, fix bug, reduce logging

Browse files

Bug fix:
- ActiveSoundTable memset was AFTER field writes, clobbering ddgame
and counter. Moved memset before field writes.

Dead code removed (-247 lines):
- 4 dead FFI bridge functions (call_init_fields, call_tsm_init,
call_gfx_resource_create, call_gfx_find_entry) — superseded by
pure Rust implementations
- 5 dead statics (INIT_FIELDS_ADDR, FUN_570E20_ADDR, TSM_INIT_ADDR,
GFX_RESOURCE_CREATE_ADDR, GFX_FIND_ENTRY_ADDR)
- 5 dead init_constructor_addrs entries
- Unused import (crate::log::log_line)
- ~43 debug log_line calls (kept only 2 milestone markers)

Code quality:
- Renamed cryptic variables: f914→headless, f374→display_flags,
d778→game_version
- Prefixed unused params with _
- Removed stale development comments
- Fixed 3 memory leaks (landscape_coords_buf, landscape_byte_buf,
stack_local_8 now freed after use)

Zero warnings from openwa-core. Headless replay PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -8,7 +8,6 @@ use crate::display::palette::Palette;
8
  use crate::engine::ddgame_wrapper::DDGameWrapper;
9
  use crate::engine::game_info::GameInfo;
10
  use crate::input::keyboard::DDKeyboard;
11
- use crate::log::log_line;
12
  use crate::rebase::rb;
13
  use crate::render::landscape::PCLandscape;
14
  use crate::render::queue::RenderQueue;
@@ -499,21 +498,11 @@ pub unsafe fn task_state_machine_init(object: *mut u8, param1: u32, width: u32,
499
  let alloc_size = ((total_size + 3) & !3) + 0x20;
500
  let buffer = wa_malloc(alloc_size);
501
 
502
- let _ = crate::log::log_line(&format!(
503
- "[TSM] init: obj=0x{:08X} p1={} w={} h={} stride={} total={} alloc={} buf=0x{:08X}",
504
- object as u32, param1, width, height, row_stride, total_size, alloc_size, buffer as u32,
505
- ));
506
-
507
  if buffer.is_null() {
508
- let _ = crate::log::log_line("[TSM] WARN: wa_malloc returned null, skipping memset");
509
  return;
510
  }
511
  // Guard against integer overflow producing tiny alloc_size with huge total_size
512
  if total_size as usize > alloc_size as usize {
513
- let _ = crate::log::log_line(&format!(
514
- "[TSM] OVERFLOW: total_size {} > alloc_size {}, skipping memset",
515
- total_size, alloc_size,
516
- ));
517
  return;
518
  }
519
 
@@ -853,22 +842,15 @@ pub unsafe fn gfx_resource_create(
853
  name: *const u8,
854
  output: *mut u8,
855
  ) -> *mut u8 {
856
- let _ = crate::log::log_line(&format!(
857
- "[GfxRes] create: handler=0x{:08X} name=0x{:08X} out=0x{:08X}",
858
- gfx_handler as u32, name as u32, output as u32,
859
- ));
860
  // 1. Try FindEntry → cached load → DisplayGfx wrap
861
  let entry = gfx_dir_find_entry(name, gfx_handler);
862
- let _ = crate::log::log_line(&format!("[GfxRes] entry=0x{:08X}", entry as u32));
863
  if !entry.is_null() {
864
  // gfx_handler->vtable[2](entry->field_4) — cached load
865
  let vt = *(gfx_handler as *const *const u32);
866
- let _ = crate::log::log_line(&format!("[GfxRes] vt=0x{:08X}", vt as u32));
867
  let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
868
  core::mem::transmute(*vt.add(2));
869
  let entry_val = *(entry.add(4) as *const u32);
870
  let cached = load_cached(gfx_handler, entry_val);
871
- let _ = crate::log::log_line(&format!("[GfxRes] cached=0x{:08X}", cached as u32));
872
  if !cached.is_null() {
873
  // DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
874
  let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
@@ -878,65 +860,33 @@ pub unsafe fn gfx_resource_create(
878
  }
879
 
880
  // 2. Fallback: LoadImage → IMG_Decode
881
- let _ = crate::log::log_line("[GfxRes] fallback: LoadImage");
882
  let raw_image = call_gfx_load_image(gfx_handler, name);
883
- let _ = crate::log::log_line(&format!("[GfxRes] raw_image=0x{:08X}", raw_image as u32));
884
  if raw_image.is_null() {
885
  return core::ptr::null_mut();
886
  }
887
 
888
  // IMG_Decode: stdcall(output, raw_image, 1), RET 0xC
889
- let _ = crate::log::log_line("[GfxRes] calling IMG_Decode");
890
  let decode: unsafe extern "stdcall" fn(*mut u8, *mut u8, i32) -> *mut u8 =
891
  core::mem::transmute(rb(va::IMG_DECODE) as usize);
892
  let result = decode(output, raw_image, 1);
893
- let _ = crate::log::log_line(&format!(
894
- "[GfxRes] IMG_Decode result=0x{:08X}",
895
- result as u32
896
- ));
897
 
898
  // Release raw image: raw_image->vtable[0](1)
899
  let img_vt = *(raw_image as *const *const u32);
900
  let fn_ptr = *img_vt;
901
- let slot_idx = *(raw_image.add(4) as *const u32);
902
- let stored_handler = *(raw_image.add(8) as *const u32);
903
- let _ = crate::log::log_line(&format!(
904
- "[GfxRes] release: raw=0x{:08X} vt=0x{:08X} fn=0x{:08X} slot={} handler=0x{:08X}",
905
- raw_image as u32, img_vt as u32, fn_ptr as u32, slot_idx, stored_handler,
906
- ));
907
- let _ = crate::log::log_line(&format!(
908
- "[GfxRes] handler_usedcnt={} handler_freecnt={}",
909
- *(gfx_handler.add(0x190) as *const u32),
910
- *(gfx_handler.add(0x18C) as *const u32),
911
- ));
912
  let release: unsafe extern "thiscall" fn(*mut u8, i32) = core::mem::transmute(fn_ptr);
913
- let _ = crate::log::log_line("[GfxRes] calling release...");
914
  release(raw_image, 1);
915
- let _ = crate::log::log_line("[GfxRes] release done");
916
- let _ = crate::log::log_line(&format!(
917
- "[GfxRes] heap-ok raw=0x{:08X} result=0x{:08X}",
918
- raw_image as u32, result as u32
919
- ));
920
- let _ = crate::log::log_line("[GfxRes] before return");
921
 
922
  result
923
  }
924
 
925
- /// Runtime address of DDGame__InitFields (set by init_constructor_addrs()).
926
- static mut INIT_FIELDS_ADDR: u32 = 0;
927
  static mut GFX_LOAD_DIR_ADDR: u32 = 0;
928
 
929
  /// Initialize runtime addresses for the constructor bridges.
930
  /// Must be called once at DLL startup (from lib.rs or similar).
931
  pub fn init_constructor_addrs() {
932
  unsafe {
933
- INIT_FIELDS_ADDR = rb(va::DDGAME_INIT_FIELDS);
934
  SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
935
- FUN_570E20_ADDR = rb(va::FUN_570E20);
936
  FUN_570A90_ADDR = rb(va::FUN_570A90);
937
- GFX_RESOURCE_CREATE_ADDR = rb(va::GFX_RESOURCE_CREATE);
938
- TSM_INIT_ADDR = rb(va::TASK_STATE_MACHINE_INIT);
939
- GFX_FIND_ENTRY_ADDR = rb(va::GFX_DIR_FIND_ENTRY);
940
  GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
941
  GFX_LOAD_DIR_ADDR = rb(va::GFX_HANDLER_LOAD_DIR);
942
  FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
@@ -945,22 +895,6 @@ pub fn init_constructor_addrs() {
945
  }
946
  }
947
 
948
- /// Bridge to DDGame__InitFields (0x526120).
949
- /// Convention: usercall(EDI=ddgame), plain RET.
950
- #[cfg(target_arch = "x86")]
951
- #[unsafe(naked)]
952
- unsafe extern "C" fn call_init_fields(_ddgame: *mut DDGame) {
953
- core::arch::naked_asm!(
954
- "pushl %edi",
955
- "movl 8(%esp), %edi",
956
- "calll *({addr})",
957
- "popl %edi",
958
- "retl",
959
- addr = sym INIT_FIELDS_ADDR,
960
- options(att_syntax),
961
- );
962
- }
963
-
964
  /// Bridge to DDGame__InitVersionFlags (0x525BE0).
965
  /// Convention: stdcall(ddgame_wrapper).
966
  #[cfg(target_arch = "x86")]
@@ -1019,12 +953,6 @@ pub unsafe fn create_ddgame(
1019
  // Now safe to expose — all fields that concurrent readers check are set.
1020
  (*wrapper).ddgame = ddgame;
1021
 
1022
- // Verify game_info is set
1023
- let _ = crate::log::log_line(&format!(
1024
- "[DDGame] exposed: ddgame=0x{:08X}, game_info=0x{:08X}, net_game=0x{:08X}",
1025
- ddgame as u32, game_info as u32, net_game as u32,
1026
- ));
1027
-
1028
  // ── 5. Set g_GameInfo global ──
1029
  *(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
1030
 
@@ -1054,9 +982,7 @@ pub unsafe fn create_ddgame(
1054
  }
1055
 
1056
  // ── 9. InitVersionFlags — sets DDGame+0x7E2E/0x7E2F/0x7E3F ──
1057
- let _ = crate::log::log_line("[DDGame] calling InitVersionFlags");
1058
  call_init_version_flags(wrapper);
1059
- let _ = crate::log::log_line("[DDGame] calling init_graphics_and_resources");
1060
 
1061
  // ── 10. GfxHandler, landscape, sprites, audio, resources ──
1062
  init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
@@ -1073,14 +999,13 @@ pub unsafe fn create_ddgame(
1073
  unsafe fn init_graphics_and_resources(
1074
  wrapper: *mut DDGameWrapper,
1075
  game_info: *mut GameInfo,
1076
- net_game: *mut u8,
1077
- display: *mut DDDisplay,
1078
  is_headless: bool,
1079
  ) {
1080
  let ddgame = (*wrapper).ddgame;
1081
  let fopen: unsafe extern "cdecl" fn(*const u8, *const u8) -> *mut u8 =
1082
  core::mem::transmute(rb(va::WA_FOPEN) as usize);
1083
- // gfx_load_dir_addr removed — using direct Rust gfx_handler_load_dir() now
1084
  let gfx_handler_vtable = rb(0x66B280) as u32; // GfxHandler__vtable (from asm: MOV [ESI], 0x66B280)
1085
 
1086
  // ── GfxHandler #1 (primary) ──
@@ -1090,11 +1015,11 @@ unsafe fn init_graphics_and_resources(
1090
  (*wrapper)._field_4c0 = gfx1;
1091
  (*wrapper)._field_4c4 = core::ptr::null_mut();
1092
 
1093
- // Build path list (order depends on headless + GameInfo+0xF374)
1094
- let f914 = (*game_info).headless_mode as i32;
1095
- let f374 = (*game_info).display_flags as i32;
1096
- let paths: [&[u8]; 3] = if f914 != 0 {
1097
- if f374 == 0 {
1098
  [
1099
  b"data\\Gfx\\Gfx.dir\0",
1100
  b"data\\Gfx\\Gfx0.dir\0",
@@ -1107,7 +1032,7 @@ unsafe fn init_graphics_and_resources(
1107
  b"data\\Gfx\\Gfx0.dir\0",
1108
  ]
1109
  }
1110
- } else if f374 == 0 {
1111
  [
1112
  b"data\\Gfx\\Gfx0.dir\0",
1113
  b"data\\Gfx\\Gfx1.dir\0",
@@ -1134,7 +1059,7 @@ unsafe fn init_graphics_and_resources(
1134
  }
1135
  }
1136
 
1137
- let headless_offset = if f914 != 0 { 1u32 } else { 0u32 };
1138
  (*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 {
1139
  1
1140
  } else {
@@ -1142,11 +1067,11 @@ unsafe fn init_graphics_and_resources(
1142
  };
1143
 
1144
  // ── GfxHandler #2 (conditional) ──
1145
- let d778 = (*game_info).game_version;
1146
  // Original: (-(gfx_mode != 0) & 0x23) - 2 → gfx_mode ? 33 : -2
1147
  let threshold = if (*wrapper).gfx_mode != 0 { 33 } else { -2i32 };
1148
- if d778 < threshold {
1149
- let c_digit = if d778 > -3 { b'2' } else { b'1' };
1150
  let mut gfx_c_path = *b"data\\Gfx\\GfxC_3_0.dir\0";
1151
  gfx_c_path[14] = c_digit;
1152
 
@@ -1219,7 +1144,6 @@ unsafe fn init_graphics_and_resources(
1219
  f((*ddgame).display, 1)
1220
  };
1221
 
1222
- let _ = crate::log::log_line(&format!("[DDGame] gfx_mode={}", (*wrapper).gfx_mode));
1223
  // ── GfxDir color entries DDGame+0x730C..0x732C ──
1224
  if (*wrapper).gfx_mode != 0 {
1225
  // Original: PUSH ESI (=layer_ctx from vtable[5] call), MOV EAX=0x66A3B4, CALL 0x4F6300
@@ -1227,12 +1151,6 @@ unsafe fn init_graphics_and_resources(
1227
  let res = gfx_resource_create((*wrapper)._field_4c0, rb(0x66A3B4) as *const u8, layer_ctx);
1228
  if !res.is_null() {
1229
  let rvt = *(res as *const *const u32);
1230
- let _ = crate::log::log_line(&format!(
1231
- "[DDGame] color entries: res=0x{:08X} vt=0x{:08X} vt[4]=0x{:08X}",
1232
- res as u32,
1233
- rvt as u32,
1234
- *rvt.add(4),
1235
- ));
1236
  // vtable[4] = get_pixel: thiscall(this, x, y) -> color, RET 0x8.
1237
  let get_color: unsafe extern "thiscall" fn(*mut u8, u32, u32) -> u32 =
1238
  core::mem::transmute(*rvt.add(4));
@@ -1241,9 +1159,6 @@ unsafe fn init_graphics_and_resources(
1241
  while off < 0x732Du32 {
1242
  let c = get_color(res, idx, 0);
1243
  *((ddgame as *mut u8).add(off as usize) as *mut u32) = c;
1244
- if idx == 0 {
1245
- let _ = crate::log::log_line(&format!("[DDGame] color[0] = 0x{:08X}", c,));
1246
- }
1247
  off += 4;
1248
  idx += 1;
1249
  }
@@ -1254,15 +1169,9 @@ unsafe fn init_graphics_and_resources(
1254
  release(res, 1);
1255
  }
1256
  } else {
1257
- // GFX_HANDLER_LOAD_SPRITES: SKIP for testing (gfx_mode==0 path)
1258
  }
1259
 
1260
- let _ = crate::log::log_line("[DDGame] past color entries");
1261
- let _ = crate::log::log_line(&format!(
1262
- "[DDGame] 4c0=0x{:08X} 4c4=0x{:08X}",
1263
- (*wrapper)._field_4c0 as u32,
1264
- (*wrapper)._field_4c4 as u32
1265
- ));
1266
  // ── Secondary GfxDir object (DDGame+0x2C, conditional) ──
1267
  if !(*wrapper)._field_4c4.is_null() {
1268
  let gfxdir2 = wa_malloc(0x70C);
@@ -1300,12 +1209,9 @@ unsafe fn init_graphics_and_resources(
1300
  (*wrapper).speech_name_count = 0;
1301
 
1302
  // ── Audio init (non-headless + sound available) ──
1303
- let _ = crate::log::log_line("[DDGame] audio: start");
1304
  if !is_headless {
1305
  // FUN_570F30: usercall(ESI=wrapper)
1306
  call_usercall_esi(wrapper, FUN_570F30_ADDR);
1307
- let _ = crate::log::log_line("[DDGame] audio: past 570F30");
1308
-
1309
  if !(*ddgame).sound.is_null() {
1310
  // DSSound_LoadEffectWAVs: stdcall(wrapper)
1311
  let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
@@ -1314,32 +1220,22 @@ unsafe fn init_graphics_and_resources(
1314
  // DSSound_LoadAllSpeechBanks: the original is hooked to our Rust
1315
  // replacement (speech.rs), so the usercall bridge calls our code.
1316
  call_usercall_esi(wrapper, LOAD_SPEECH_BANKS_ADDR);
1317
- let _ = crate::log::log_line("[DDGame] audio: done");
1318
  // Allocate ActiveSoundTable (0x608 bytes)
1319
  let ast = wa_malloc(0x608) as *mut ActiveSoundTable;
 
1320
  (*ast).ddgame = ddgame;
1321
  (*ast).counter = 0;
1322
- core::ptr::write_bytes(ast as *mut u8, 0, 0x600);
1323
  (*ddgame).active_sounds = ast;
1324
  }
1325
  }
1326
 
1327
- let _ = crate::log::log_line("[DDGame] past ActiveSoundTable");
1328
-
1329
  // ── GfxResource: thiscall(ECX=gfx_handler) + EAX=name + 1 stack(output), RET 0x4 ──
1330
- // Test: call GfxResource with real params
1331
  let gfx_resource: *mut u8;
1332
  {
1333
- {
1334
- let gfx_handler = (*wrapper)._field_4c0;
1335
- let out_buf = wa_malloc(0x900);
1336
- core::ptr::write_bytes(out_buf, 0, 0x900);
1337
- // return moved to before color entries
1338
- let _ = crate::log::log_line("[DDGame] calling GfxResource_Create");
1339
- gfx_resource = gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
1340
- let _ = crate::log::log_line("[DDGame] after gfx_resource_create");
1341
- let _ = crate::log::log_line("[DDGame] GfxResource created (value elided)");
1342
- }
1343
  }
1344
 
1345
  // ── PCLandscape (alloc 0xB44, stdcall 11 params) ──
@@ -1382,37 +1278,6 @@ unsafe fn init_graphics_and_resources(
1382
  (ddgame as *mut u8).add(0x777C), // param 10
1383
  (ddgame as *mut u8).add(0x7780), // param 11
1384
  );
1385
- let _ = crate::log::log_line(&format!(
1386
- "[DDGame] PCLandscape returned: 0x{:08X}, 777C=0x{:08X} 7780=0x{:08X}",
1387
- result as u32,
1388
- *((ddgame as *mut u8).add(0x777C) as *const u32),
1389
- *((ddgame as *mut u8).add(0x7780) as *const u32),
1390
- ));
1391
- // Dump landscape_coords_buf to find coordinate count and data format
1392
- let coords = landscape_coords_buf as *const u32;
1393
- let mut coord_count = 0u32;
1394
- // Count non-zero pairs (each coord is 2 u32s)
1395
- for j in 0..285 {
1396
- if *coords.add(j * 2) != 0 || *coords.add(j * 2 + 1) != 0 {
1397
- coord_count = j as u32 + 1;
1398
- }
1399
- }
1400
- let _ = crate::log::log_line(&format!(
1401
- "[DDGame] coords: first=[0x{:08X},0x{:08X}] count_est={} byte_buf[0]=0x{:02X}",
1402
- *coords,
1403
- *coords.add(1),
1404
- coord_count,
1405
- *landscape_byte_buf,
1406
- ));
1407
- // Also check stack_local_8 for count
1408
- let sl8 = stack_local_8 as *const u32;
1409
- let _ = crate::log::log_line(&format!(
1410
- "[DDGame] stack8: [0]=0x{:08X} [1]=0x{:08X} [2]=0x{:08X} [3]=0x{:08X}",
1411
- *sl8,
1412
- *sl8.add(1),
1413
- *sl8.add(2),
1414
- *sl8.add(3),
1415
- ));
1416
  // Store landscape pointer
1417
  (*wrapper).landscape = result as *mut PCLandscape;
1418
  (*ddgame).landscape = result as *mut PCLandscape;
@@ -1494,20 +1359,8 @@ unsafe fn init_graphics_and_resources(
1494
  let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
1495
  core::mem::transmute(*disp_vt.add(5));
1496
  let layer_ctx = set_layer((*ddgame).display, 1);
1497
- if i == 0 {
1498
- let _ = crate::log::log_line(&format!(
1499
- "[DDGame] arrow[0]: layer_ctx=0x{:08X}, gfx=0x{:08X}",
1500
- layer_ctx as u32, gfx_handler as u32
1501
- ));
1502
- }
1503
 
1504
  let entry = gfx_dir_find_entry(name_buf.as_ptr(), gfx_handler);
1505
- if i == 0 {
1506
- let _ = crate::log::log_line(&format!(
1507
- "[DDGame] arrow[0]: entry=0x{:08X}",
1508
- entry as u32
1509
- ));
1510
- }
1511
 
1512
  let sprite: *mut u8;
1513
  if !entry.is_null() {
@@ -1517,12 +1370,6 @@ unsafe fn init_graphics_and_resources(
1517
  core::mem::transmute(*gfx_vt.add(2));
1518
  let entry_val = *(entry.add(4) as *const u32);
1519
  let cached = load_cached(gfx_handler, entry_val);
1520
- if i == 0 {
1521
- let _ = crate::log::log_line(&format!(
1522
- "[DDGame] arrow[0]: cached=0x{:08X}",
1523
- cached as u32
1524
- ));
1525
- }
1526
  if !cached.is_null() {
1527
  // Wrap with DisplayGfx constructor
1528
  let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
@@ -1536,12 +1383,6 @@ unsafe fn init_graphics_and_resources(
1536
  // Entry not found — try direct file load
1537
  sprite = call_gfx_load_and_wrap(gfx_handler, name_buf.as_ptr(), layer_ctx);
1538
  }
1539
- if i == 0 {
1540
- let _ = crate::log::log_line(&format!(
1541
- "[DDGame] arrow[0]: sprite=0x{:08X}",
1542
- sprite as u32
1543
- ));
1544
- }
1545
 
1546
  // Store arrow sprite at DDGame+0x38+i*4
1547
  (*ddgame).arrow_sprites[i as usize] = sprite;
@@ -1586,8 +1427,6 @@ unsafe fn init_graphics_and_resources(
1586
  }
1587
  }
1588
 
1589
- let _ = crate::log::log_line("[DDGame] arrow loop done");
1590
-
1591
  // Release gfx_resource AFTER arrow loop (arrows need it for SpriteRegions)
1592
  // vtable[3] = DisplayGfx__vmethod_3: thiscall(this, byte param_2), RET 4.
1593
  if !gfx_resource.is_null() {
@@ -1650,12 +1489,13 @@ unsafe fn init_graphics_and_resources(
1650
  *cl = (cur_count + 1) as u32;
1651
  }
1652
  }
1653
- let _ = crate::log::log_line(&format!(
1654
- "[DDGame] coord_list populated: {} entries from {} landscape coords",
1655
- *cl, coord_count,
1656
- ));
1657
  }
1658
 
 
 
 
 
 
1659
  // ── Loading progress ticks (2 of 4 — before load_resource_list) ──
1660
  call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
1661
  call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
@@ -1670,10 +1510,6 @@ unsafe fn init_graphics_and_resources(
1670
  let gfx_handler = (*wrapper)._field_4c0;
1671
 
1672
  let wrapper_vt = *(wrapper as *const *const u32);
1673
- let _ = crate::log::log_line(&format!(
1674
- "[DDGame] sprite resource loading: wrapper_vt=0x{:08X}, vt[0]=0x{:08X}, landscape=0x{:08X}, water=0x{:08X}, land=0x{:08X}",
1675
- wrapper_vt as u32, *wrapper_vt, landscape_ptr as u32, water_layer as u32, land_layer as u32,
1676
- ));
1677
  let load_resource_list: unsafe extern "thiscall" fn(
1678
  *mut DDGameWrapper,
1679
  u32,
@@ -1683,7 +1519,6 @@ unsafe fn init_graphics_and_resources(
1683
  u32,
1684
  ) = core::mem::transmute(*wrapper_vt);
1685
  // Load resources for layer 1 (main sprites)
1686
- let _ = crate::log::log_line("[DDGame] load_resource_list #1 (main sprites)");
1687
  load_resource_list(
1688
  wrapper,
1689
  1,
@@ -1692,17 +1527,11 @@ unsafe fn init_graphics_and_resources(
1692
  rb(0x6AD2C0) as *const u8, // resource table
1693
  0x1D88, // table size
1694
  );
1695
- let _ = crate::log::log_line(&format!(
1696
- "[DDGame] after #1: sprite_cache[144]=0x{:08X}",
1697
- (*ddgame).sprite_cache[144] as u32,
1698
- ));
1699
-
1700
  // Set global flag based on game version
1701
  let gv = (*(*ddgame).game_info).game_version;
1702
  *(rb(0x6AF050) as *mut u32) = if gv < 8 { 0 } else { 0x10 };
1703
 
1704
  // Load resources for layer 1 with different table
1705
- let _ = crate::log::log_line("[DDGame] load_resource_list #2");
1706
  load_resource_list(
1707
  wrapper,
1708
  1,
@@ -1713,7 +1542,6 @@ unsafe fn init_graphics_and_resources(
1713
  );
1714
 
1715
  // Load resources for layer 2 (water)
1716
- let _ = crate::log::log_line("[DDGame] load_resource_list #3 (water)");
1717
  load_resource_list(
1718
  wrapper,
1719
  2,
@@ -1790,10 +1618,6 @@ unsafe fn init_graphics_and_resources(
1790
  // shows was set from piVar3 (water_layer from landscape+0xB38).
1791
  let fill_sprite =
1792
  call_gfx_find_and_load(water_layer, b"fill.img\0".as_ptr(), layer2_ctx);
1793
- let _ = crate::log::log_line(&format!(
1794
- "[DDGame] fill.img: water=0x{:08X} sprite=0x{:08X}",
1795
- water_layer as u32, fill_sprite as u32,
1796
- ));
1797
  if !fill_sprite.is_null() {
1798
  // Get pixel value: fill_sprite->vtable[4](0, 0)
1799
  let fill_vt = *(fill_sprite as *const *const u32);
@@ -1902,7 +1726,6 @@ unsafe fn call_gfx_find_and_load(
1902
  }
1903
 
1904
  // Statics for usercall bridge addresses
1905
- static mut FUN_570E20_ADDR: u32 = 0;
1906
  static mut FUN_570A90_ADDR: u32 = 0;
1907
  static mut FUN_570F30_ADDR: u32 = 0;
1908
  static mut LOAD_SPEECH_BANKS_ADDR: u32 = 0;
@@ -1943,76 +1766,6 @@ unsafe fn call_usercall_ecx(wrapper: *mut DDGameWrapper, addr: u32) {
1943
  f(wrapper);
1944
  }
1945
 
1946
- /// Runtime address of TaskStateMachine__Init_Maybe.
1947
- static mut TSM_INIT_ADDR: u32 = 0;
1948
-
1949
- /// Bridge to TaskStateMachine__Init_Maybe (0x4F6370).
1950
- /// Convention: usercall(ESI=object, ECX=1, EDI=height) + 1 stack(width), RET 0x4.
1951
- #[cfg(target_arch = "x86")]
1952
- #[unsafe(naked)]
1953
- unsafe extern "C" fn call_tsm_init(_object: *mut u8, _width: u32, _height: u32) {
1954
- core::arch::naked_asm!(
1955
- "pushl %esi",
1956
- "pushl %edi",
1957
- "movl 12(%esp), %esi", // ESI = object (1st param, shifted by 2 pushes)
1958
- "movl 20(%esp), %edi", // EDI = height (3rd param, shifted by 2 pushes)
1959
- "pushl 16(%esp)", // push width (2nd param, shifted by 2 pushes + 1 push)
1960
- "movl $1, %ecx", // ECX = 1
1961
- "calll *({addr})", // RET 0x4 cleans the pushed width
1962
- "popl %edi",
1963
- "popl %esi",
1964
- "retl",
1965
- addr = sym TSM_INIT_ADDR,
1966
- options(att_syntax),
1967
- );
1968
- }
1969
-
1970
- /// Runtime address of GfxResource__Create_Maybe (set by init_constructor_addrs()).
1971
- static mut GFX_RESOURCE_CREATE_ADDR: u32 = 0;
1972
-
1973
- /// Bridge to GfxResource__Create_Maybe (0x4F6300).
1974
- /// Convention: usercall(ECX=gfx_handler, EAX=data_ptr) + 1 stack(output), RET 0x4.
1975
- #[cfg(target_arch = "x86")]
1976
- #[unsafe(naked)]
1977
- unsafe extern "C" fn call_gfx_resource_create(
1978
- _gfx_handler: *mut u8,
1979
- _data_ptr: *const u8,
1980
- _output: *mut u8,
1981
- ) -> *mut u8 {
1982
- core::arch::naked_asm!(
1983
- "pushl %ebp",
1984
- "pushl %ebx",
1985
- "movl 12(%esp), %ecx", // ECX = gfx_handler (0=ebx,4=ebp,8=ret,12=handler)
1986
- "movl 16(%esp), %eax", // EAX = data_ptr (16=data)
1987
- "pushl 20(%esp)", // push output (20=output, but shifts after push!)
1988
- "calll *({addr})", // RET 0x4 cleans output
1989
- "popl %ebx",
1990
- "popl %ebp",
1991
- "retl",
1992
- addr = sym GFX_RESOURCE_CREATE_ADDR,
1993
- options(att_syntax),
1994
- );
1995
- }
1996
-
1997
- /// Runtime address of GfxDir__FindEntry (set by init_constructor_addrs()).
1998
- static mut GFX_FIND_ENTRY_ADDR: u32 = 0;
1999
-
2000
- /// Bridge to GfxDir__FindEntry (0x566520).
2001
- /// Convention: usercall(EAX=name) + 1 stack(gfx_handler), RET 0x4.
2002
- /// Returns entry pointer or null.
2003
- #[cfg(target_arch = "x86")]
2004
- #[unsafe(naked)]
2005
- unsafe extern "C" fn call_gfx_find_entry(_name: *const u8, _gfx_handler: *mut u8) -> *mut u8 {
2006
- core::arch::naked_asm!(
2007
- "movl 4(%esp), %eax", // EAX = name
2008
- "pushl 8(%esp)", // push gfx_handler (callee cleans via RET 0x4)
2009
- "calll *({addr})",
2010
- "retl", // caller cleans our 2 cdecl params
2011
- addr = sym GFX_FIND_ENTRY_ADDR,
2012
- options(att_syntax),
2013
- );
2014
- }
2015
-
2016
  /// Bridge to GfxHandler__LoadDir (0x5663E0).
2017
  /// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
2018
  #[cfg(target_arch = "x86")]
 
8
  use crate::engine::ddgame_wrapper::DDGameWrapper;
9
  use crate::engine::game_info::GameInfo;
10
  use crate::input::keyboard::DDKeyboard;
 
11
  use crate::rebase::rb;
12
  use crate::render::landscape::PCLandscape;
13
  use crate::render::queue::RenderQueue;
 
498
  let alloc_size = ((total_size + 3) & !3) + 0x20;
499
  let buffer = wa_malloc(alloc_size);
500
 
 
 
 
 
 
501
  if buffer.is_null() {
 
502
  return;
503
  }
504
  // Guard against integer overflow producing tiny alloc_size with huge total_size
505
  if total_size as usize > alloc_size as usize {
 
 
 
 
506
  return;
507
  }
508
 
 
842
  name: *const u8,
843
  output: *mut u8,
844
  ) -> *mut u8 {
 
 
 
 
845
  // 1. Try FindEntry → cached load → DisplayGfx wrap
846
  let entry = gfx_dir_find_entry(name, gfx_handler);
 
847
  if !entry.is_null() {
848
  // gfx_handler->vtable[2](entry->field_4) — cached load
849
  let vt = *(gfx_handler as *const *const u32);
 
850
  let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
851
  core::mem::transmute(*vt.add(2));
852
  let entry_val = *(entry.add(4) as *const u32);
853
  let cached = load_cached(gfx_handler, entry_val);
 
854
  if !cached.is_null() {
855
  // DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
856
  let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
 
860
  }
861
 
862
  // 2. Fallback: LoadImage → IMG_Decode
 
863
  let raw_image = call_gfx_load_image(gfx_handler, name);
 
864
  if raw_image.is_null() {
865
  return core::ptr::null_mut();
866
  }
867
 
868
  // IMG_Decode: stdcall(output, raw_image, 1), RET 0xC
 
869
  let decode: unsafe extern "stdcall" fn(*mut u8, *mut u8, i32) -> *mut u8 =
870
  core::mem::transmute(rb(va::IMG_DECODE) as usize);
871
  let result = decode(output, raw_image, 1);
 
 
 
 
872
 
873
  // Release raw image: raw_image->vtable[0](1)
874
  let img_vt = *(raw_image as *const *const u32);
875
  let fn_ptr = *img_vt;
 
 
 
 
 
 
 
 
 
 
 
876
  let release: unsafe extern "thiscall" fn(*mut u8, i32) = core::mem::transmute(fn_ptr);
 
877
  release(raw_image, 1);
 
 
 
 
 
 
878
 
879
  result
880
  }
881
 
 
 
882
  static mut GFX_LOAD_DIR_ADDR: u32 = 0;
883
 
884
  /// Initialize runtime addresses for the constructor bridges.
885
  /// Must be called once at DLL startup (from lib.rs or similar).
886
  pub fn init_constructor_addrs() {
887
  unsafe {
 
888
  SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
 
889
  FUN_570A90_ADDR = rb(va::FUN_570A90);
 
 
 
890
  GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
891
  GFX_LOAD_DIR_ADDR = rb(va::GFX_HANDLER_LOAD_DIR);
892
  FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
 
895
  }
896
  }
897
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
898
  /// Bridge to DDGame__InitVersionFlags (0x525BE0).
899
  /// Convention: stdcall(ddgame_wrapper).
900
  #[cfg(target_arch = "x86")]
 
953
  // Now safe to expose — all fields that concurrent readers check are set.
954
  (*wrapper).ddgame = ddgame;
955
 
 
 
 
 
 
 
956
  // ── 5. Set g_GameInfo global ──
957
  *(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
958
 
 
982
  }
983
 
984
  // ── 9. InitVersionFlags — sets DDGame+0x7E2E/0x7E2F/0x7E3F ──
 
985
  call_init_version_flags(wrapper);
 
986
 
987
  // ── 10. GfxHandler, landscape, sprites, audio, resources ──
988
  init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
 
999
  unsafe fn init_graphics_and_resources(
1000
  wrapper: *mut DDGameWrapper,
1001
  game_info: *mut GameInfo,
1002
+ _net_game: *mut u8,
1003
+ _display: *mut DDDisplay,
1004
  is_headless: bool,
1005
  ) {
1006
  let ddgame = (*wrapper).ddgame;
1007
  let fopen: unsafe extern "cdecl" fn(*const u8, *const u8) -> *mut u8 =
1008
  core::mem::transmute(rb(va::WA_FOPEN) as usize);
 
1009
  let gfx_handler_vtable = rb(0x66B280) as u32; // GfxHandler__vtable (from asm: MOV [ESI], 0x66B280)
1010
 
1011
  // ── GfxHandler #1 (primary) ──
 
1015
  (*wrapper)._field_4c0 = gfx1;
1016
  (*wrapper)._field_4c4 = core::ptr::null_mut();
1017
 
1018
+ // Build path list (order depends on headless + display_flags)
1019
+ let headless = (*game_info).headless_mode as i32;
1020
+ let display_flags = (*game_info).display_flags as i32;
1021
+ let paths: [&[u8]; 3] = if headless != 0 {
1022
+ if display_flags == 0 {
1023
  [
1024
  b"data\\Gfx\\Gfx.dir\0",
1025
  b"data\\Gfx\\Gfx0.dir\0",
 
1032
  b"data\\Gfx\\Gfx0.dir\0",
1033
  ]
1034
  }
1035
+ } else if display_flags == 0 {
1036
  [
1037
  b"data\\Gfx\\Gfx0.dir\0",
1038
  b"data\\Gfx\\Gfx1.dir\0",
 
1059
  }
1060
  }
1061
 
1062
+ let headless_offset = if headless != 0 { 1u32 } else { 0u32 };
1063
  (*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 {
1064
  1
1065
  } else {
 
1067
  };
1068
 
1069
  // ── GfxHandler #2 (conditional) ──
1070
+ let game_version = (*game_info).game_version;
1071
  // Original: (-(gfx_mode != 0) & 0x23) - 2 → gfx_mode ? 33 : -2
1072
  let threshold = if (*wrapper).gfx_mode != 0 { 33 } else { -2i32 };
1073
+ if game_version < threshold {
1074
+ let c_digit = if game_version > -3 { b'2' } else { b'1' };
1075
  let mut gfx_c_path = *b"data\\Gfx\\GfxC_3_0.dir\0";
1076
  gfx_c_path[14] = c_digit;
1077
 
 
1144
  f((*ddgame).display, 1)
1145
  };
1146
 
 
1147
  // ── GfxDir color entries DDGame+0x730C..0x732C ──
1148
  if (*wrapper).gfx_mode != 0 {
1149
  // Original: PUSH ESI (=layer_ctx from vtable[5] call), MOV EAX=0x66A3B4, CALL 0x4F6300
 
1151
  let res = gfx_resource_create((*wrapper)._field_4c0, rb(0x66A3B4) as *const u8, layer_ctx);
1152
  if !res.is_null() {
1153
  let rvt = *(res as *const *const u32);
 
 
 
 
 
 
1154
  // vtable[4] = get_pixel: thiscall(this, x, y) -> color, RET 0x8.
1155
  let get_color: unsafe extern "thiscall" fn(*mut u8, u32, u32) -> u32 =
1156
  core::mem::transmute(*rvt.add(4));
 
1159
  while off < 0x732Du32 {
1160
  let c = get_color(res, idx, 0);
1161
  *((ddgame as *mut u8).add(off as usize) as *mut u32) = c;
 
 
 
1162
  off += 4;
1163
  idx += 1;
1164
  }
 
1169
  release(res, 1);
1170
  }
1171
  } else {
1172
+ // gfx_mode == 0: no color entries to load
1173
  }
1174
 
 
 
 
 
 
 
1175
  // ── Secondary GfxDir object (DDGame+0x2C, conditional) ──
1176
  if !(*wrapper)._field_4c4.is_null() {
1177
  let gfxdir2 = wa_malloc(0x70C);
 
1209
  (*wrapper).speech_name_count = 0;
1210
 
1211
  // ── Audio init (non-headless + sound available) ──
 
1212
  if !is_headless {
1213
  // FUN_570F30: usercall(ESI=wrapper)
1214
  call_usercall_esi(wrapper, FUN_570F30_ADDR);
 
 
1215
  if !(*ddgame).sound.is_null() {
1216
  // DSSound_LoadEffectWAVs: stdcall(wrapper)
1217
  let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
 
1220
  // DSSound_LoadAllSpeechBanks: the original is hooked to our Rust
1221
  // replacement (speech.rs), so the usercall bridge calls our code.
1222
  call_usercall_esi(wrapper, LOAD_SPEECH_BANKS_ADDR);
 
1223
  // Allocate ActiveSoundTable (0x608 bytes)
1224
  let ast = wa_malloc(0x608) as *mut ActiveSoundTable;
1225
+ core::ptr::write_bytes(ast as *mut u8, 0, 0x600);
1226
  (*ast).ddgame = ddgame;
1227
  (*ast).counter = 0;
 
1228
  (*ddgame).active_sounds = ast;
1229
  }
1230
  }
1231
 
 
 
1232
  // ── GfxResource: thiscall(ECX=gfx_handler) + EAX=name + 1 stack(output), RET 0x4 ──
 
1233
  let gfx_resource: *mut u8;
1234
  {
1235
+ let gfx_handler = (*wrapper)._field_4c0;
1236
+ let out_buf = wa_malloc(0x900);
1237
+ core::ptr::write_bytes(out_buf, 0, 0x900);
1238
+ gfx_resource = gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
 
 
 
 
 
 
1239
  }
1240
 
1241
  // ── PCLandscape (alloc 0xB44, stdcall 11 params) ──
 
1278
  (ddgame as *mut u8).add(0x777C), // param 10
1279
  (ddgame as *mut u8).add(0x7780), // param 11
1280
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1281
  // Store landscape pointer
1282
  (*wrapper).landscape = result as *mut PCLandscape;
1283
  (*ddgame).landscape = result as *mut PCLandscape;
 
1359
  let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
1360
  core::mem::transmute(*disp_vt.add(5));
1361
  let layer_ctx = set_layer((*ddgame).display, 1);
 
 
 
 
 
 
1362
 
1363
  let entry = gfx_dir_find_entry(name_buf.as_ptr(), gfx_handler);
 
 
 
 
 
 
1364
 
1365
  let sprite: *mut u8;
1366
  if !entry.is_null() {
 
1370
  core::mem::transmute(*gfx_vt.add(2));
1371
  let entry_val = *(entry.add(4) as *const u32);
1372
  let cached = load_cached(gfx_handler, entry_val);
 
 
 
 
 
 
1373
  if !cached.is_null() {
1374
  // Wrap with DisplayGfx constructor
1375
  let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
 
1383
  // Entry not found — try direct file load
1384
  sprite = call_gfx_load_and_wrap(gfx_handler, name_buf.as_ptr(), layer_ctx);
1385
  }
 
 
 
 
 
 
1386
 
1387
  // Store arrow sprite at DDGame+0x38+i*4
1388
  (*ddgame).arrow_sprites[i as usize] = sprite;
 
1427
  }
1428
  }
1429
 
 
 
1430
  // Release gfx_resource AFTER arrow loop (arrows need it for SpriteRegions)
1431
  // vtable[3] = DisplayGfx__vmethod_3: thiscall(this, byte param_2), RET 4.
1432
  if !gfx_resource.is_null() {
 
1489
  *cl = (cur_count + 1) as u32;
1490
  }
1491
  }
 
 
 
 
1492
  }
1493
 
1494
+ // Free temporary landscape buffers (no longer needed after coord_list population)
1495
+ crate::wa_alloc::wa_free(landscape_coords_buf);
1496
+ crate::wa_alloc::wa_free(landscape_byte_buf);
1497
+ crate::wa_alloc::wa_free(stack_local_8);
1498
+
1499
  // ── Loading progress ticks (2 of 4 — before load_resource_list) ──
1500
  call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
1501
  call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
 
1510
  let gfx_handler = (*wrapper)._field_4c0;
1511
 
1512
  let wrapper_vt = *(wrapper as *const *const u32);
 
 
 
 
1513
  let load_resource_list: unsafe extern "thiscall" fn(
1514
  *mut DDGameWrapper,
1515
  u32,
 
1519
  u32,
1520
  ) = core::mem::transmute(*wrapper_vt);
1521
  // Load resources for layer 1 (main sprites)
 
1522
  load_resource_list(
1523
  wrapper,
1524
  1,
 
1527
  rb(0x6AD2C0) as *const u8, // resource table
1528
  0x1D88, // table size
1529
  );
 
 
 
 
 
1530
  // Set global flag based on game version
1531
  let gv = (*(*ddgame).game_info).game_version;
1532
  *(rb(0x6AF050) as *mut u32) = if gv < 8 { 0 } else { 0x10 };
1533
 
1534
  // Load resources for layer 1 with different table
 
1535
  load_resource_list(
1536
  wrapper,
1537
  1,
 
1542
  );
1543
 
1544
  // Load resources for layer 2 (water)
 
1545
  load_resource_list(
1546
  wrapper,
1547
  2,
 
1618
  // shows was set from piVar3 (water_layer from landscape+0xB38).
1619
  let fill_sprite =
1620
  call_gfx_find_and_load(water_layer, b"fill.img\0".as_ptr(), layer2_ctx);
 
 
 
 
1621
  if !fill_sprite.is_null() {
1622
  // Get pixel value: fill_sprite->vtable[4](0, 0)
1623
  let fill_vt = *(fill_sprite as *const *const u32);
 
1726
  }
1727
 
1728
  // Statics for usercall bridge addresses
 
1729
  static mut FUN_570A90_ADDR: u32 = 0;
1730
  static mut FUN_570F30_ADDR: u32 = 0;
1731
  static mut LOAD_SPEECH_BANKS_ADDR: u32 = 0;
 
1766
  f(wrapper);
1767
  }
1768
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1769
  /// Bridge to GfxHandler__LoadDir (0x5663E0).
1770
  /// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
1771
  #[cfg(target_arch = "x86")]