paavohtl Claude Opus 4.6 (1M context) commited on
Commit
e7f6be4
·
1 Parent(s): 6820eef

feat: enable USE_RUST_CTOR — Rust DDGame constructor now functional

Browse files

Enable 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>

crates/openwa-core/src/address.rs CHANGED
@@ -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
- /// DDGame__LoadWeaponSprites_Maybe (0x5717A0): loads weapon sprite data.
349
- pub const DDGAME_LOAD_WEAPON_SPRITES: u32 = 0x0057_17A0;
 
 
 
 
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.
crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -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
- LOAD_WEAPON_SPRITES_ADDR = rb(va::DDGAME_LOAD_WEAPON_SPRITES);
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, LOAD_WEAPON_SPRITES_ADDR);
1268
- // call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
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 &regions {
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, LOAD_WEAPON_SPRITES_ADDR);
1526
- // call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
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
- // ── HUD_LoadWeaponSprites (0x53D0E0) ──
1642
- // thiscall(ECX=ddgame, stack=wrapper_4c4), RET 0x8 (cleans 2: this implicit + 1 stack)
1643
- // Wait — RET 0x8 means 2 stack params cleaned. For thiscall with ECX, that's 2 stack params.
1644
- // The asm: PUSH ECX(=wrapper_4c4), PUSH EDX(=ddgame), CALL.
1645
- // So it's stdcall(ddgame, wrapper_4c4) actually — ECX is NOT the this pointer!
1646
- // The decompiler labeled it thiscall but the push sequence shows two stack params.
1647
- // Let me pass wrapper as thiscall ECX and wrapper_4c4 as stack:
1648
- // Actually: PUSH ECX([EBP+0x4C4]), PUSH EDX([EBP+0x488]=ddgame).
1649
- // CALL 0x53D0E0. So ECX = gfx_handler_4c4, pushed FIRST. EDX = ddgame, pushed SECOND.
1650
- // For stdcall: param_1=EDX(ddgame, pushed last), param_2=ECX(wrapper_4c4, pushed first).
1651
- // But decompiler says thiscall → ECX is implicit this.
1652
- // RET 0x8 = 2 × 4 bytes cleaned. For thiscall: ECX + 1 stack = RET 0x4.
1653
- // RET 0x8 means 2 stack params, NO ECX implicit → it's really stdcall(2 params).
1654
- // End sections: disabled for headless testing
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 LOAD_WEAPON_SPRITES_ADDR: u32 = 0;
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 &regions {
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")]
crates/openwa-wormkit/src/replacements/game_session.rs CHANGED
@@ -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 = false;
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,