fix: correct Shotgun/Longbow calling convention (stdcall -> thiscall)
Browse filesFireWeapon 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 =>
|
| 220 |
_ => {}
|
| 221 |
},
|
| 222 |
2 => match subtype_38 {
|
|
@@ -320,8 +336,10 @@ unsafe extern "C" fn call_fire_thiscall2(
|
|
| 320 |
);
|
| 321 |
}
|
| 322 |
|
| 323 |
-
/// Bridge:
|
|
|
|
| 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.
|
| 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);
|