feat: add FireWeapon passthrough hook with weapon type logging
Browse filesHook FireWeapon (0x51EE60) as naked passthrough: save regs → call
logger → restore → jmp original. Logs weapon type (1-4) and subtypes
for each weapon fired during gameplay.
Verified: headless PASS (byte-identical), headful 133+3 / 0 FAIL.
58 weapon fires logged in test replay (mostly type=1 homing/bazooka).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-wormkit/src/replacements/weapon.rs
CHANGED
|
@@ -1,14 +1,18 @@
|
|
| 1 |
-
//! Weapon
|
| 2 |
//!
|
| 3 |
//! Replaces WA.exe functions that manage weapon ammo in the TeamArenaState area (DDGame + 0x4628):
|
| 4 |
//! - GetAmmo (0x5225E0): query ammo count with delay/phase checks
|
| 5 |
//! - AddAmmo (0x522640): add ammo to a weapon slot
|
| 6 |
//! - SubtractAmmo (0x522680): decrement ammo count
|
| 7 |
//! - CountAliveWorms (0x5225A0): check if >1 worm alive on team
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
use openwa_core::address::va;
|
| 10 |
use openwa_core::engine::ddgame::{self, TeamArenaRef};
|
| 11 |
use openwa_core::game::Weapon;
|
|
|
|
| 12 |
|
| 13 |
use crate::hook::{self, usercall_trampoline};
|
| 14 |
|
|
@@ -123,6 +127,51 @@ unsafe extern "cdecl" fn count_alive_worms_impl(team_index: u32, arena: TeamAren
|
|
| 123 |
usercall_trampoline!(fn trampoline_count_alive_worms; impl_fn = count_alive_worms_impl;
|
| 124 |
regs = [eax, ecx]);
|
| 125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
// ============================================================
|
| 127 |
// Hook installation
|
| 128 |
// ============================================================
|
|
@@ -144,6 +193,13 @@ pub fn install() -> Result<(), String> {
|
|
| 144 |
va::COUNT_ALIVE_WORMS,
|
| 145 |
trampoline_count_alive_worms as *const (),
|
| 146 |
)?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
}
|
| 148 |
|
| 149 |
Ok(())
|
|
|
|
| 1 |
+
//! Weapon hooks.
|
| 2 |
//!
|
| 3 |
//! Replaces WA.exe functions that manage weapon ammo in the TeamArenaState area (DDGame + 0x4628):
|
| 4 |
//! - GetAmmo (0x5225E0): query ammo count with delay/phase checks
|
| 5 |
//! - AddAmmo (0x522640): add ammo to a weapon slot
|
| 6 |
//! - SubtractAmmo (0x522680): decrement ammo count
|
| 7 |
//! - CountAliveWorms (0x5225A0): check if >1 worm alive on team
|
| 8 |
+
//! - FireWeapon (0x51EE60): passthrough with weapon type logging
|
| 9 |
+
|
| 10 |
+
use core::sync::atomic::{AtomicU32, Ordering};
|
| 11 |
|
| 12 |
use openwa_core::address::va;
|
| 13 |
use openwa_core::engine::ddgame::{self, TeamArenaRef};
|
| 14 |
use openwa_core::game::Weapon;
|
| 15 |
+
use openwa_core::log::log_line;
|
| 16 |
|
| 17 |
use crate::hook::{self, usercall_trampoline};
|
| 18 |
|
|
|
|
| 127 |
usercall_trampoline!(fn trampoline_count_alive_worms; impl_fn = count_alive_worms_impl;
|
| 128 |
regs = [eax, ecx]);
|
| 129 |
|
| 130 |
+
// ============================================================
|
| 131 |
+
// FireWeapon passthrough (0x51EE60)
|
| 132 |
+
// ============================================================
|
| 133 |
+
// usercall(EAX=weapon_ctx) + 1 stack param (wrapper), RET 0x4.
|
| 134 |
+
// weapon_ctx+0x30 = weapon type (1-4)
|
| 135 |
+
// weapon_ctx+0x34 = subtype for types 3,4
|
| 136 |
+
// weapon_ctx+0x38 = subtype for types 1,2
|
| 137 |
+
|
| 138 |
+
static ORIG_FIRE_WEAPON: AtomicU32 = AtomicU32::new(0);
|
| 139 |
+
|
| 140 |
+
/// Logger called from the naked passthrough. EAX = weapon launch context.
|
| 141 |
+
/// Context+0x30 = weapon type (1-4), +0x34 = subtype for types 3/4,
|
| 142 |
+
/// +0x38 = subtype for types 1/2, +0x3C = params base.
|
| 143 |
+
unsafe extern "cdecl" fn fire_weapon_log(weapon_ctx: u32) {
|
| 144 |
+
let ctx = weapon_ctx as *const u8;
|
| 145 |
+
let weapon_type = *(ctx.add(0x30) as *const i32);
|
| 146 |
+
let subtype_34 = *(ctx.add(0x34) as *const i32);
|
| 147 |
+
let subtype_38 = *(ctx.add(0x38) as *const i32);
|
| 148 |
+
|
| 149 |
+
let _ = log_line(&format!(
|
| 150 |
+
"[Weapon] FireWeapon: type={} sub34={} sub38={}",
|
| 151 |
+
weapon_type, subtype_34, subtype_38
|
| 152 |
+
));
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
/// Naked passthrough: save regs → call logger → restore → jmp original.
|
| 156 |
+
#[unsafe(naked)]
|
| 157 |
+
unsafe extern "C" fn trampoline_fire_weapon() {
|
| 158 |
+
core::arch::naked_asm!(
|
| 159 |
+
"push eax",
|
| 160 |
+
"push ecx",
|
| 161 |
+
"push edx",
|
| 162 |
+
// call logger with EAX (weapon_ctx) as cdecl arg
|
| 163 |
+
"push eax",
|
| 164 |
+
"call {log_fn}",
|
| 165 |
+
"add esp, 4",
|
| 166 |
+
"pop edx",
|
| 167 |
+
"pop ecx",
|
| 168 |
+
"pop eax",
|
| 169 |
+
"jmp [{orig}]",
|
| 170 |
+
log_fn = sym fire_weapon_log,
|
| 171 |
+
orig = sym ORIG_FIRE_WEAPON,
|
| 172 |
+
);
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
// ============================================================
|
| 176 |
// Hook installation
|
| 177 |
// ============================================================
|
|
|
|
| 193 |
va::COUNT_ALIVE_WORMS,
|
| 194 |
trampoline_count_alive_worms as *const (),
|
| 195 |
)?;
|
| 196 |
+
|
| 197 |
+
let trampoline = hook::install(
|
| 198 |
+
"FireWeapon",
|
| 199 |
+
va::FIRE_WEAPON,
|
| 200 |
+
trampoline_fire_weapon as *const (),
|
| 201 |
+
)?;
|
| 202 |
+
ORIG_FIRE_WEAPON.store(trampoline as u32, Ordering::Relaxed);
|
| 203 |
}
|
| 204 |
|
| 205 |
Ok(())
|