paavohtl Claude Opus 4.6 (1M context) commited on
Commit
d7dcf03
·
1 Parent(s): 106f839

fix: correct Shotgun/Longbow calling convention (stdcall -> thiscall)

Browse files

FireWeapon subtype_38=4 handler (0x51ED90) is thiscall (ECX=worm),
not stdcall. The wrong convention passed garbage in ECX, causing a
NULL dereference at DDGame+0x72A4 when firing Longbow or Shotgun.

Confirmed via crash analysis: EAX=0 at MOV ECX,[EAX+0x72A4] in
FireWeapon__Shotgun, and Ghidra disassembly showing MOV ESI,ECX
as the first instruction (thiscall pattern).

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

crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -180,6 +180,22 @@ unsafe extern "C" fn trampoline_fire_weapon() {
180
  );
181
  }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  /// Rust implementation of FireWeapon dispatch.
184
  ///
185
  /// `entry`: EAX = active WeaponEntry pointer (from CTaskWorm+0x36C).
@@ -216,7 +232,7 @@ unsafe extern "cdecl" fn fire_weapon_impl(
216
  1 => call_fire_stdcall1(w, fire_params, rb(0x51EC80)), // PlacedExplosive
217
  2 => call_fire_stdcall3(w, fire_params, local_struct, rb(0x51DFB0)), // Projectile
218
  3 => call_fire_thiscall2(w, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
219
- 4 => call_fire_stdcall2(w, fire_params, local_struct, rb(0x51ED90)), // Shotgun
220
  _ => {}
221
  },
222
  2 => match subtype_38 {
@@ -320,8 +336,10 @@ unsafe extern "C" fn call_fire_thiscall2(
320
  );
321
  }
322
 
323
- /// Bridge: Shotgun — stdcall(fire_params, local_struct), RET 0x8.
 
324
  #[unsafe(naked)]
 
325
  unsafe extern "C" fn call_fire_stdcall2(
326
  _worm: u32, _fire_params: *const WeaponFireParams, _local: u32, _addr: u32,
327
  ) {
@@ -462,7 +480,7 @@ unsafe fn fire_skip_go(worm: *const CTaskWorm, entry: *const WeaponEntry) {
462
  let game_version = (*(*ddgame).game_info).game_version;
463
  let team_index = (*worm).team_index as usize;
464
 
465
- let bit_index = (*entry).fire_params._data[0] & 0x1F;
466
  let bit = 1u32 << bit_index;
467
 
468
  let arena = TeamArenaRef::from_ptr(&raw mut (*ddgame).team_arena);
 
180
  );
181
  }
182
 
183
+ /// Call the original FireWeapon (trampoline) for debugging.
184
+ /// Restores EAX=entry, ECX=local_struct, stack=worm.
185
+ #[unsafe(naked)]
186
+ unsafe extern "C" fn call_original_fire_weapon(
187
+ _entry: *const WeaponEntry, _local_struct: u32, _worm: *mut CTaskWorm,
188
+ ) {
189
+ core::arch::naked_asm!(
190
+ "mov eax, [esp+4]", // entry
191
+ "mov ecx, [esp+8]", // local_struct
192
+ "push [esp+12]", // worm
193
+ "call [{orig}]",
194
+ "ret",
195
+ orig = sym ORIG_FIRE_WEAPON,
196
+ );
197
+ }
198
+
199
  /// Rust implementation of FireWeapon dispatch.
200
  ///
201
  /// `entry`: EAX = active WeaponEntry pointer (from CTaskWorm+0x36C).
 
232
  1 => call_fire_stdcall1(w, fire_params, rb(0x51EC80)), // PlacedExplosive
233
  2 => call_fire_stdcall3(w, fire_params, local_struct, rb(0x51DFB0)), // Projectile
234
  3 => call_fire_thiscall2(w, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
235
+ 4 => call_fire_thiscall2(w, fire_params, local_struct, rb(0x51ED90)), // Shotgun/Longbow (thiscall)
236
  _ => {}
237
  },
238
  2 => match subtype_38 {
 
336
  );
337
  }
338
 
339
+ /// Bridge: stdcall(fire_params, local_struct), RET 0x8.
340
+ /// Currently unused — was incorrectly used for Shotgun/Longbow (which are thiscall).
341
  #[unsafe(naked)]
342
+ #[allow(dead_code)]
343
  unsafe extern "C" fn call_fire_stdcall2(
344
  _worm: u32, _fire_params: *const WeaponFireParams, _local: u32, _addr: u32,
345
  ) {
 
480
  let game_version = (*(*ddgame).game_info).game_version;
481
  let team_index = (*worm).team_index as usize;
482
 
483
+ let bit_index = ((*entry).fire_params.shot_count & 0x1F) as u32;
484
  let bit = 1u32 << bit_index;
485
 
486
  let arena = TeamArenaRef::from_ptr(&raw mut (*ddgame).team_arena);