refactor: propagate WeaponReleaseContext type through fire dispatch
Browse files- fire_weapon (renamed from fire_weapon_impl) now takes typed
*const WeaponReleaseContext instead of *const u8
- Convert from extern "cdecl" to plain unsafe fn β only called
from Rust (weapon_release_impl), no longer via naked trampoline
- Replace manual FireWeapon trampoline with install_trap! β the WA
function at 0x51EE60 is fully superseded by weapon_release
- All fire sub-function bridges use typed context pointer
- Remove ORIG_FIRE_WEAPON static (no longer needed)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-wormkit/src/replacements/weapon.rs
CHANGED
|
@@ -26,6 +26,7 @@ use openwa_core::task::Task;
|
|
| 26 |
use openwa_core::task::worm::{CTaskWorm, WormState};
|
| 27 |
|
| 28 |
use crate::hook::{self, usercall_trampoline};
|
|
|
|
| 29 |
|
| 30 |
// ============================================================
|
| 31 |
// AddAmmo replacement (0x522640)
|
|
@@ -138,66 +139,21 @@ usercall_trampoline!(fn trampoline_count_alive_worms; impl_fn = count_alive_worm
|
|
| 138 |
regs = [eax, ecx]);
|
| 139 |
|
| 140 |
// ============================================================
|
| 141 |
-
// FireWeapon
|
| 142 |
// ============================================================
|
| 143 |
-
// Convention: usercall(EAX=worm, ECX=local_struct) + 1 stack(worm), RET 0x4.
|
| 144 |
-
// Note: EAX = *(CTaskWorm+0x36C) = worm self-pointer, so EAX == stack param.
|
| 145 |
-
//
|
| 146 |
-
// Weapon launch data offsets (relative to worm/EAX):
|
| 147 |
-
// +0x30 = weapon type (1-4)
|
| 148 |
-
// +0x34 = subtype for types 3,4
|
| 149 |
-
// +0x38 = subtype for types 1,2
|
| 150 |
-
// +0x3C = params base
|
| 151 |
-
//
|
| 152 |
-
// worm = CTaskWorm pointer (ESI in original).
|
| 153 |
-
// local_struct = stack-local buffer from WeaponRelease (ECX at call site).
|
| 154 |
-
//
|
| 155 |
-
// Sub-functions are usercall: ESI=worm, ECX=local_struct (for some).
|
| 156 |
-
// We capture all three in our naked trampoline.
|
| 157 |
-
|
| 158 |
-
static ORIG_FIRE_WEAPON: AtomicU32 = AtomicU32::new(0);
|
| 159 |
-
|
| 160 |
-
/// Naked trampoline for FireWeapon.
|
| 161 |
-
/// Must save ALL callee-saved registers (ESI, EDI, EBX, EBP) because the
|
| 162 |
-
/// Rust cdecl impl may clobber them, and WeaponRelease (our caller) depends
|
| 163 |
-
/// on them being preserved.
|
| 164 |
-
#[unsafe(naked)]
|
| 165 |
-
unsafe extern "C" fn trampoline_fire_weapon() {
|
| 166 |
-
core::arch::naked_asm!(
|
| 167 |
-
// Save all callee-saved + EDX
|
| 168 |
-
"push ebx",
|
| 169 |
-
"push esi",
|
| 170 |
-
"push edi",
|
| 171 |
-
"push ebp",
|
| 172 |
-
"push edx",
|
| 173 |
-
// Push cdecl args: (weapon_ctx=EAX, local_struct=ECX, worm)
|
| 174 |
-
// Stack: 5 pushes (20) + ret (4) = 24 to stack param
|
| 175 |
-
"push [esp+24]", // worm
|
| 176 |
-
"push ecx", // local_struct
|
| 177 |
-
"push eax", // weapon_ctx
|
| 178 |
-
"call {impl_fn}",
|
| 179 |
-
"add esp, 12",
|
| 180 |
-
// Restore everything
|
| 181 |
-
"pop edx",
|
| 182 |
-
"pop ebp",
|
| 183 |
-
"pop edi",
|
| 184 |
-
"pop esi",
|
| 185 |
-
"pop ebx",
|
| 186 |
-
"ret 0x4",
|
| 187 |
-
impl_fn = sym fire_weapon_impl,
|
| 188 |
-
);
|
| 189 |
-
}
|
| 190 |
|
| 191 |
-
///
|
| 192 |
///
|
| 193 |
-
///
|
| 194 |
-
///
|
| 195 |
-
/// `worm`: stack param = CTaskWorm pointer (ESI in original).
|
| 196 |
///
|
| 197 |
-
///
|
| 198 |
-
///
|
| 199 |
-
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
| 201 |
) {
|
| 202 |
use openwa_core::rebase::rb;
|
| 203 |
|
|
@@ -216,25 +172,25 @@ pub(crate) unsafe extern "cdecl" fn fire_weapon_impl(
|
|
| 216 |
use openwa_core::game::weapon::{FireType, FireMethod};
|
| 217 |
match FireType::try_from(fire_type) {
|
| 218 |
Ok(FireType::Projectile) => match FireMethod::try_from(fire_method) {
|
| 219 |
-
Ok(FireMethod::PlacedExplosive) => call_fire_placed_explosive(worm, fire_params,
|
| 220 |
-
Ok(FireMethod::ProjectileFire) => call_fire_stdcall3(worm, fire_params,
|
| 221 |
-
Ok(FireMethod::CreateWeaponProjectile) => call_fire_thiscall2(worm, fire_params,
|
| 222 |
-
Ok(FireMethod::CreateArrow) => call_fire_thiscall2(worm, fire_params,
|
| 223 |
_ => {}
|
| 224 |
},
|
| 225 |
Ok(FireType::Rope) => match FireMethod::try_from(fire_method) {
|
| 226 |
-
Ok(FireMethod::PlacedExplosive) => call_fire_stdcall3(worm, fire_params,
|
| 227 |
-
Ok(FireMethod::ProjectileFire) => call_fire_thiscall2(worm, fire_params,
|
| 228 |
-
Ok(FireMethod::CreateWeaponProjectile) => call_fire_stdcall3(worm, fire_params,
|
| 229 |
_ => {}
|
| 230 |
},
|
| 231 |
Ok(FireType::Strike) => {
|
| 232 |
// StrikeFire takes a pointer to the subtype_34 field (reinterpreted as fire params)
|
| 233 |
let subtype_34_ptr = &raw const (*entry).special_subtype as *const WeaponFireParams;
|
| 234 |
-
call_fire_stdcall3(worm, subtype_34_ptr,
|
| 235 |
}
|
| 236 |
Ok(FireType::Special) => {
|
| 237 |
-
fire_weapon_special((*entry).special_subtype, entry, worm,
|
| 238 |
}
|
| 239 |
_ => {}
|
| 240 |
}
|
|
@@ -248,10 +204,10 @@ pub(crate) unsafe extern "cdecl" fn fire_weapon_impl(
|
|
| 248 |
// the usercall context that sub-functions expect.
|
| 249 |
|
| 250 |
/// Bridge: PlacedExplosive β usercall(ECX=local_struct, EDX=worm, [ESP+4]=fire_params), RET 0x4.
|
| 251 |
-
/// Args: (worm, fire_params,
|
| 252 |
#[unsafe(naked)]
|
| 253 |
unsafe extern "C" fn call_fire_placed_explosive(
|
| 254 |
-
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams,
|
| 255 |
) {
|
| 256 |
core::arch::naked_asm!(
|
| 257 |
"push ebx",
|
|
@@ -274,7 +230,7 @@ unsafe extern "C" fn call_fire_placed_explosive(
|
|
| 274 |
/// Bridge: Projectile/Rope/Grenade β stdcall(worm, fire_params, local_struct), RET 0xC.
|
| 275 |
#[unsafe(naked)]
|
| 276 |
unsafe extern "C" fn call_fire_stdcall3(
|
| 277 |
-
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams,
|
| 278 |
) {
|
| 279 |
core::arch::naked_asm!(
|
| 280 |
"push ebx",
|
|
@@ -297,7 +253,7 @@ unsafe extern "C" fn call_fire_stdcall3(
|
|
| 297 |
/// Bridge: CreateWeaponProjectile β thiscall(ECX=worm, fire_params, local_struct), RET 0x8.
|
| 298 |
#[unsafe(naked)]
|
| 299 |
unsafe extern "C" fn call_fire_thiscall2(
|
| 300 |
-
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams,
|
| 301 |
) {
|
| 302 |
core::arch::naked_asm!(
|
| 303 |
"push ebx",
|
|
@@ -322,7 +278,7 @@ unsafe extern "C" fn call_fire_thiscall2(
|
|
| 322 |
#[unsafe(naked)]
|
| 323 |
#[allow(dead_code)]
|
| 324 |
unsafe extern "C" fn call_fire_stdcall2(
|
| 325 |
-
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams,
|
| 326 |
) {
|
| 327 |
core::arch::naked_asm!(
|
| 328 |
"push ebx",
|
|
@@ -347,7 +303,7 @@ unsafe extern "C" fn call_fire_stdcall2(
|
|
| 347 |
/// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
|
| 348 |
/// Handlers without explicit MOV EAX inherit the entry pointer.
|
| 349 |
unsafe fn fire_weapon_special(
|
| 350 |
-
subtype: i32, entry: *const WeaponEntry, worm: *mut CTaskWorm,
|
| 351 |
) {
|
| 352 |
use openwa_core::game::weapon::SpecialFireSubtype as S;
|
| 353 |
use openwa_core::rebase::rb;
|
|
@@ -357,13 +313,13 @@ unsafe fn fire_weapon_special(
|
|
| 357 |
|
| 358 |
match S::try_from(subtype) {
|
| 359 |
Ok(S::Blowtorch) => CTaskWorm::set_state_raw(worm, WormState::Blowtorch),
|
| 360 |
-
Ok(S::PneumaticDrill) => fire_drill(worm,
|
| 361 |
-
Ok(S::Girder) => fire_girder(worm, params_38_ptr,
|
| 362 |
Ok(S::BaseballBat) => CTaskWorm::set_state_raw(worm, WormState::BaseballBat),
|
| 363 |
Ok(S::FirePunch) => CTaskWorm::set_state_raw(worm, WormState::FirePunch),
|
| 364 |
Ok(S::DragonBall) => CTaskWorm::set_state_raw(worm, WormState::DragonBall),
|
| 365 |
Ok(S::Kamikaze) => CTaskWorm::set_state_raw(worm, WormState::Kamikaze),
|
| 366 |
-
Ok(S::Prod) => fire_prod(worm,
|
| 367 |
Ok(S::AirStrike) => fire_air_strike(worm),
|
| 368 |
Ok(S::ScalesOfJustice) => CTaskWorm::set_state_raw(worm, WormState::ScalesOfJustice),
|
| 369 |
Ok(S::NapalmStrike) => fire_send_team_message(worm, 0x2B),
|
|
@@ -1488,8 +1444,7 @@ pub fn install() -> Result<(), String> {
|
|
| 1488 |
let _ = hook::install("GetAmmo", va::GET_AMMO, trampoline_get_ammo as *const ())?;
|
| 1489 |
let _ = hook::install("SubtractAmmo", va::SUBTRACT_AMMO, trampoline_subtract_ammo as *const ())?;
|
| 1490 |
let _ = hook::install("CountAliveWorms", va::COUNT_ALIVE_WORMS, trampoline_count_alive_worms as *const ())?;
|
| 1491 |
-
|
| 1492 |
-
ORIG_FIRE_WEAPON.store(trampoline as u32, Ordering::Relaxed);
|
| 1493 |
|
| 1494 |
// Passthrough hooks on fire sub-functions (log + call original)
|
| 1495 |
let t = hook::install("CreateWeaponProjectile", va::CREATE_WEAPON_PROJECTILE, trampoline_create_weapon_projectile as *const ())?;
|
|
|
|
| 26 |
use openwa_core::task::worm::{CTaskWorm, WormState};
|
| 27 |
|
| 28 |
use crate::hook::{self, usercall_trampoline};
|
| 29 |
+
use crate::replacements::weapon_release::WeaponReleaseContext;
|
| 30 |
|
| 31 |
// ============================================================
|
| 32 |
// AddAmmo replacement (0x522640)
|
|
|
|
| 139 |
regs = [eax, ecx]);
|
| 140 |
|
| 141 |
// ============================================================
|
| 142 |
+
// FireWeapon (0x51EE60) β trapped, called directly from weapon_release
|
| 143 |
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
+
/// FireWeapon dispatch β called directly by `weapon_release_impl`.
|
| 146 |
///
|
| 147 |
+
/// The WA function at 0x51EE60 is trapped via `install_trap!`; its only
|
| 148 |
+
/// caller (WeaponRelease) is ported and invokes this Rust function instead.
|
|
|
|
| 149 |
///
|
| 150 |
+
/// Dispatches to type-specific fire sub-functions based on `entry.fire_type`
|
| 151 |
+
/// and `entry.fire_method` / `entry.special_subtype`.
|
| 152 |
+
/// Sets the completion flag at worm+0x3C before and after dispatch.
|
| 153 |
+
pub(crate) unsafe fn fire_weapon(
|
| 154 |
+
entry: *const WeaponEntry,
|
| 155 |
+
ctx: *const WeaponReleaseContext,
|
| 156 |
+
worm: *mut CTaskWorm,
|
| 157 |
) {
|
| 158 |
use openwa_core::rebase::rb;
|
| 159 |
|
|
|
|
| 172 |
use openwa_core::game::weapon::{FireType, FireMethod};
|
| 173 |
match FireType::try_from(fire_type) {
|
| 174 |
Ok(FireType::Projectile) => match FireMethod::try_from(fire_method) {
|
| 175 |
+
Ok(FireMethod::PlacedExplosive) => call_fire_placed_explosive(worm, fire_params, ctx, rb(0x51EC80)),
|
| 176 |
+
Ok(FireMethod::ProjectileFire) => call_fire_stdcall3(worm, fire_params, ctx, rb(0x51DFB0)),
|
| 177 |
+
Ok(FireMethod::CreateWeaponProjectile) => call_fire_thiscall2(worm, fire_params, ctx, rb(0x51E0F0)),
|
| 178 |
+
Ok(FireMethod::CreateArrow) => call_fire_thiscall2(worm, fire_params, ctx, rb(0x51ED90)),
|
| 179 |
_ => {}
|
| 180 |
},
|
| 181 |
Ok(FireType::Rope) => match FireMethod::try_from(fire_method) {
|
| 182 |
+
Ok(FireMethod::PlacedExplosive) => call_fire_stdcall3(worm, fire_params, ctx, rb(0x51E1C0)), // RopeType1
|
| 183 |
+
Ok(FireMethod::ProjectileFire) => call_fire_thiscall2(worm, fire_params, ctx, rb(0x51E0F0)),
|
| 184 |
+
Ok(FireMethod::CreateWeaponProjectile) => call_fire_stdcall3(worm, fire_params, ctx, rb(0x51E240)), // RopeType3
|
| 185 |
_ => {}
|
| 186 |
},
|
| 187 |
Ok(FireType::Strike) => {
|
| 188 |
// StrikeFire takes a pointer to the subtype_34 field (reinterpreted as fire params)
|
| 189 |
let subtype_34_ptr = &raw const (*entry).special_subtype as *const WeaponFireParams;
|
| 190 |
+
call_fire_stdcall3(worm, subtype_34_ptr, ctx, rb(0x51E2C0));
|
| 191 |
}
|
| 192 |
Ok(FireType::Special) => {
|
| 193 |
+
fire_weapon_special((*entry).special_subtype, entry, worm, ctx);
|
| 194 |
}
|
| 195 |
_ => {}
|
| 196 |
}
|
|
|
|
| 204 |
// the usercall context that sub-functions expect.
|
| 205 |
|
| 206 |
/// Bridge: PlacedExplosive β usercall(ECX=local_struct, EDX=worm, [ESP+4]=fire_params), RET 0x4.
|
| 207 |
+
/// Args: (worm, fire_params, ctx, addr).
|
| 208 |
#[unsafe(naked)]
|
| 209 |
unsafe extern "C" fn call_fire_placed_explosive(
|
| 210 |
+
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _ctx: *const WeaponReleaseContext, _addr: u32,
|
| 211 |
) {
|
| 212 |
core::arch::naked_asm!(
|
| 213 |
"push ebx",
|
|
|
|
| 230 |
/// Bridge: Projectile/Rope/Grenade β stdcall(worm, fire_params, local_struct), RET 0xC.
|
| 231 |
#[unsafe(naked)]
|
| 232 |
unsafe extern "C" fn call_fire_stdcall3(
|
| 233 |
+
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _ctx: *const WeaponReleaseContext, _addr: u32,
|
| 234 |
) {
|
| 235 |
core::arch::naked_asm!(
|
| 236 |
"push ebx",
|
|
|
|
| 253 |
/// Bridge: CreateWeaponProjectile β thiscall(ECX=worm, fire_params, local_struct), RET 0x8.
|
| 254 |
#[unsafe(naked)]
|
| 255 |
unsafe extern "C" fn call_fire_thiscall2(
|
| 256 |
+
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _ctx: *const WeaponReleaseContext, _addr: u32,
|
| 257 |
) {
|
| 258 |
core::arch::naked_asm!(
|
| 259 |
"push ebx",
|
|
|
|
| 278 |
#[unsafe(naked)]
|
| 279 |
#[allow(dead_code)]
|
| 280 |
unsafe extern "C" fn call_fire_stdcall2(
|
| 281 |
+
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _ctx: *const WeaponReleaseContext, _addr: u32,
|
| 282 |
) {
|
| 283 |
core::arch::naked_asm!(
|
| 284 |
"push ebx",
|
|
|
|
| 303 |
/// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
|
| 304 |
/// Handlers without explicit MOV EAX inherit the entry pointer.
|
| 305 |
unsafe fn fire_weapon_special(
|
| 306 |
+
subtype: i32, entry: *const WeaponEntry, worm: *mut CTaskWorm, ctx: *const WeaponReleaseContext,
|
| 307 |
) {
|
| 308 |
use openwa_core::game::weapon::SpecialFireSubtype as S;
|
| 309 |
use openwa_core::rebase::rb;
|
|
|
|
| 313 |
|
| 314 |
match S::try_from(subtype) {
|
| 315 |
Ok(S::Blowtorch) => CTaskWorm::set_state_raw(worm, WormState::Blowtorch),
|
| 316 |
+
Ok(S::PneumaticDrill) => fire_drill(worm, ctx as *const u8),
|
| 317 |
+
Ok(S::Girder) => fire_girder(worm, params_38_ptr, ctx as *const u8),
|
| 318 |
Ok(S::BaseballBat) => CTaskWorm::set_state_raw(worm, WormState::BaseballBat),
|
| 319 |
Ok(S::FirePunch) => CTaskWorm::set_state_raw(worm, WormState::FirePunch),
|
| 320 |
Ok(S::DragonBall) => CTaskWorm::set_state_raw(worm, WormState::DragonBall),
|
| 321 |
Ok(S::Kamikaze) => CTaskWorm::set_state_raw(worm, WormState::Kamikaze),
|
| 322 |
+
Ok(S::Prod) => fire_prod(worm, ctx as *const u8),
|
| 323 |
Ok(S::AirStrike) => fire_air_strike(worm),
|
| 324 |
Ok(S::ScalesOfJustice) => CTaskWorm::set_state_raw(worm, WormState::ScalesOfJustice),
|
| 325 |
Ok(S::NapalmStrike) => fire_send_team_message(worm, 0x2B),
|
|
|
|
| 1444 |
let _ = hook::install("GetAmmo", va::GET_AMMO, trampoline_get_ammo as *const ())?;
|
| 1445 |
let _ = hook::install("SubtractAmmo", va::SUBTRACT_AMMO, trampoline_subtract_ammo as *const ())?;
|
| 1446 |
let _ = hook::install("CountAliveWorms", va::COUNT_ALIVE_WORMS, trampoline_count_alive_worms as *const ())?;
|
| 1447 |
+
hook::install_trap!("FireWeapon", va::FIRE_WEAPON);
|
|
|
|
| 1448 |
|
| 1449 |
// Passthrough hooks on fire sub-functions (log + call original)
|
| 1450 |
let t = hook::install("CreateWeaponProjectile", va::CREATE_WEAPON_PROJECTILE, trampoline_create_weapon_projectile as *const ())?;
|
crates/openwa-wormkit/src/replacements/weapon_release.rs
CHANGED
|
@@ -28,7 +28,7 @@ usercall_trampoline!(fn trampoline_weapon_release; impl_fn = weapon_release_impl
|
|
| 28 |
/// FireWeapon as the `local_struct` (ECX) parameter.
|
| 29 |
#[repr(C)]
|
| 30 |
#[derive(Clone, Copy)]
|
| 31 |
-
struct WeaponReleaseContext {
|
| 32 |
team_id: u32,
|
| 33 |
worm_id: u32,
|
| 34 |
spawn_x: u32,
|
|
@@ -399,8 +399,7 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 399 |
|
| 400 |
// ββ 11. Call FireWeapon ββββββββββββββββββββββββββββββββββ
|
| 401 |
let entry = (*worm).active_weapon_entry;
|
| 402 |
-
|
| 403 |
-
weapon::fire_weapon_impl(entry, ctx_ptr, worm);
|
| 404 |
|
| 405 |
let _ = log_line(&format!(
|
| 406 |
"[WeaponRelease] worm=0x{:08X} weapon={:?} type={} sub34={} sub38={}",
|
|
|
|
| 28 |
/// FireWeapon as the `local_struct` (ECX) parameter.
|
| 29 |
#[repr(C)]
|
| 30 |
#[derive(Clone, Copy)]
|
| 31 |
+
pub(crate) struct WeaponReleaseContext {
|
| 32 |
team_id: u32,
|
| 33 |
worm_id: u32,
|
| 34 |
spawn_x: u32,
|
|
|
|
| 399 |
|
| 400 |
// ββ 11. Call FireWeapon ββββββββββββββββββββββββββββββββββ
|
| 401 |
let entry = (*worm).active_weapon_entry;
|
| 402 |
+
weapon::fire_weapon(entry, &ctx, worm);
|
|
|
|
| 403 |
|
| 404 |
let _ = log_line(&format!(
|
| 405 |
"[WeaponRelease] worm=0x{:08X} weapon={:?} type={} sub34={} sub38={}",
|