feat: replace FireWeapon dispatch with Rust implementation
Browse filesFull Rust replacement for FireWeapon (0x51EE60) — the weapon firing
dispatcher that routes 4 weapon types × subtypes to 20+ handlers.
Key discovery: EAX at entry = *(CTaskWorm+0x36C) = worm self-pointer,
so weapon_ctx == worm for all dispatch paths. All sub-functions use
EAX=ESI=EDI=worm as their implicit register context.
Implementation:
- Naked trampoline captures EAX/ECX/stack, saves callee-saved regs
- Type 1: PlacedExplosive, Projectile, CreateWeaponProjectile, Shotgun
- Type 2: RopeType1, CreateWeaponProjectile, RopeType3
- Type 3: GrenadeMortar
- Type 4: 24 special weapon subtypes (vtable calls + usercall bridges)
- All bridges save/restore ESI/EDI/EBX and set ESI=EDI=worm
Verified: headless PASS (byte-identical), headful 133+3 / 0 FAIL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -128,57 +128,327 @@ usercall_trampoline!(fn trampoline_count_alive_worms; impl_fn = count_alive_worm
|
|
| 128 |
regs = [eax, ecx]);
|
| 129 |
|
| 130 |
// ============================================================
|
| 131 |
-
// FireWeapon
|
| 132 |
// ============================================================
|
| 133 |
-
// usercall(EAX=
|
| 134 |
-
//
|
| 135 |
-
//
|
| 136 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
static ORIG_FIRE_WEAPON: AtomicU32 = AtomicU32::new(0);
|
| 139 |
|
| 140 |
-
///
|
| 141 |
-
///
|
| 142 |
-
///
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
//
|
| 150 |
-
let weapon_id = *(
|
| 151 |
let weapon_name = Weapon::try_from(weapon_id)
|
| 152 |
.map(|w| format!("{:?}", w))
|
| 153 |
.unwrap_or_else(|id| format!("Unknown({})", id));
|
| 154 |
-
|
| 155 |
let _ = log_line(&format!(
|
| 156 |
"[Weapon] FireWeapon: {} (id={}) type={} sub34={} sub38={}",
|
| 157 |
weapon_name, weapon_id, weapon_type, subtype_34, subtype_38
|
| 158 |
));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
}
|
| 160 |
|
| 161 |
-
//
|
| 162 |
-
//
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
#[unsafe(naked)]
|
| 165 |
-
unsafe extern "C" fn
|
| 166 |
core::arch::naked_asm!(
|
| 167 |
-
"push
|
| 168 |
-
"push
|
| 169 |
-
"push
|
| 170 |
-
//
|
| 171 |
-
|
| 172 |
-
"
|
| 173 |
-
"push
|
| 174 |
-
"call
|
| 175 |
-
"
|
| 176 |
-
"pop
|
| 177 |
-
"pop
|
| 178 |
-
"
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
);
|
| 183 |
}
|
| 184 |
|
|
|
|
| 128 |
regs = [eax, ecx]);
|
| 129 |
|
| 130 |
// ============================================================
|
| 131 |
+
// FireWeapon replacement (0x51EE60)
|
| 132 |
// ============================================================
|
| 133 |
+
// Convention: usercall(EAX=worm, ECX=local_struct) + 1 stack(worm), RET 0x4.
|
| 134 |
+
// Note: EAX = *(CTaskWorm+0x36C) = worm self-pointer, so EAX == stack param.
|
| 135 |
+
//
|
| 136 |
+
// Weapon launch data offsets (relative to worm/EAX):
|
| 137 |
+
// +0x30 = weapon type (1-4)
|
| 138 |
+
// +0x34 = subtype for types 3,4
|
| 139 |
+
// +0x38 = subtype for types 1,2
|
| 140 |
+
// +0x3C = params base
|
| 141 |
+
//
|
| 142 |
+
// worm = CTaskWorm pointer (ESI in original).
|
| 143 |
+
// local_struct = stack-local buffer from WeaponRelease (ECX at call site).
|
| 144 |
+
//
|
| 145 |
+
// Sub-functions are usercall: ESI=worm, ECX=local_struct (for some).
|
| 146 |
+
// We capture all three in our naked trampoline.
|
| 147 |
|
| 148 |
static ORIG_FIRE_WEAPON: AtomicU32 = AtomicU32::new(0);
|
| 149 |
|
| 150 |
+
/// Naked trampoline for FireWeapon.
|
| 151 |
+
/// Must save ALL callee-saved registers (ESI, EDI, EBX, EBP) because the
|
| 152 |
+
/// Rust cdecl impl may clobber them, and WeaponRelease (our caller) depends
|
| 153 |
+
/// on them being preserved.
|
| 154 |
+
#[unsafe(naked)]
|
| 155 |
+
unsafe extern "C" fn trampoline_fire_weapon() {
|
| 156 |
+
core::arch::naked_asm!(
|
| 157 |
+
// Save all callee-saved + EDX
|
| 158 |
+
"push ebx",
|
| 159 |
+
"push esi",
|
| 160 |
+
"push edi",
|
| 161 |
+
"push ebp",
|
| 162 |
+
"push edx",
|
| 163 |
+
// Push cdecl args: (weapon_ctx=EAX, local_struct=ECX, worm)
|
| 164 |
+
// Stack: 5 pushes (20) + ret (4) = 24 to stack param
|
| 165 |
+
"push [esp+24]", // worm
|
| 166 |
+
"push ecx", // local_struct
|
| 167 |
+
"push eax", // weapon_ctx
|
| 168 |
+
"call {impl_fn}",
|
| 169 |
+
"add esp, 12",
|
| 170 |
+
// Restore everything
|
| 171 |
+
"pop edx",
|
| 172 |
+
"pop ebp",
|
| 173 |
+
"pop edi",
|
| 174 |
+
"pop esi",
|
| 175 |
+
"pop ebx",
|
| 176 |
+
"ret 0x4",
|
| 177 |
+
impl_fn = sym fire_weapon_impl,
|
| 178 |
+
);
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
/// Rust implementation of FireWeapon dispatch.
|
| 182 |
+
unsafe extern "cdecl" fn fire_weapon_impl(weapon_ctx: u32, local_struct: u32, worm: u32) {
|
| 183 |
let ctx = weapon_ctx as *const u8;
|
| 184 |
let weapon_type = *(ctx.add(0x30) as *const i32);
|
| 185 |
let subtype_34 = *(ctx.add(0x34) as *const i32);
|
| 186 |
let subtype_38 = *(ctx.add(0x38) as *const i32);
|
| 187 |
+
let params = weapon_ctx.wrapping_add(0x3C);
|
| 188 |
+
let worm_ptr = worm as *mut u8;
|
| 189 |
|
| 190 |
+
// Log weapon fire
|
| 191 |
+
let weapon_id = *(worm_ptr.add(0x170) as *const u32);
|
| 192 |
let weapon_name = Weapon::try_from(weapon_id)
|
| 193 |
.map(|w| format!("{:?}", w))
|
| 194 |
.unwrap_or_else(|id| format!("Unknown({})", id));
|
|
|
|
| 195 |
let _ = log_line(&format!(
|
| 196 |
"[Weapon] FireWeapon: {} (id={}) type={} sub34={} sub38={}",
|
| 197 |
weapon_name, weapon_id, weapon_type, subtype_34, subtype_38
|
| 198 |
));
|
| 199 |
+
|
| 200 |
+
// worm+0x3C = completion flag
|
| 201 |
+
*(worm_ptr.add(0x3C) as *mut i32) = 0;
|
| 202 |
+
|
| 203 |
+
use openwa_core::rebase::rb;
|
| 204 |
+
|
| 205 |
+
match weapon_type {
|
| 206 |
+
1 => match subtype_38 {
|
| 207 |
+
1 => call_fire_stdcall1(worm, params, rb(0x51EC80)), // PlacedExplosive
|
| 208 |
+
2 => call_fire_stdcall3(worm, params, local_struct, rb(0x51DFB0)), // Projectile
|
| 209 |
+
3 => call_fire_thiscall2(worm, params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
|
| 210 |
+
4 => call_fire_stdcall2(worm, params, local_struct, rb(0x51ED90)), // Shotgun
|
| 211 |
+
_ => {}
|
| 212 |
+
},
|
| 213 |
+
2 => match subtype_38 {
|
| 214 |
+
1 => call_fire_stdcall3(worm, params, local_struct, rb(0x51E1C0)), // RopeType1
|
| 215 |
+
2 => call_fire_thiscall2(worm, params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
|
| 216 |
+
3 => call_fire_stdcall3(worm, params, local_struct, rb(0x51E240)), // RopeType3
|
| 217 |
+
_ => {}
|
| 218 |
+
},
|
| 219 |
+
3 => {
|
| 220 |
+
let params_34 = weapon_ctx.wrapping_add(0x34);
|
| 221 |
+
call_fire_stdcall3(worm, params_34, local_struct, rb(0x51E2C0)); // GrenadeMortar
|
| 222 |
+
}
|
| 223 |
+
4 => {
|
| 224 |
+
let params_38 = weapon_ctx.wrapping_add(0x38);
|
| 225 |
+
fire_weapon_special(subtype_34, params_38, worm, local_struct);
|
| 226 |
+
}
|
| 227 |
+
_ => {}
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
*(worm_ptr.add(0x3C) as *mut i32) = 1;
|
| 231 |
}
|
| 232 |
|
| 233 |
+
// ── Sub-function bridges ────────────────────────────────────
|
| 234 |
+
// All preserve ESI=worm for usercall sub-functions.
|
| 235 |
+
|
| 236 |
+
// ── Sub-function bridges ────────────────────────────────────
|
| 237 |
+
// All bridges set ESI=worm AND EDI=worm before calling, since
|
| 238 |
+
// sub-functions are usercall and read both registers implicitly.
|
| 239 |
+
// Stack offsets are carefully calculated for each bridge.
|
| 240 |
+
|
| 241 |
+
// ── Sub-function bridges ────────────────────────────────────
|
| 242 |
+
// All bridges save/restore ESI+EDI, set ESI=EDI=worm, then call.
|
| 243 |
+
// This preserves LLVM's callee-saved registers while providing
|
| 244 |
+
// the usercall context that sub-functions expect.
|
| 245 |
+
|
| 246 |
+
/// Bridge: PlacedExplosive — stdcall(params), RET 0x4.
|
| 247 |
+
/// Our args: (worm, params, addr)
|
| 248 |
#[unsafe(naked)]
|
| 249 |
+
unsafe extern "C" fn call_fire_stdcall1(_worm: u32, _params: u32, _addr: u32) {
|
| 250 |
core::arch::naked_asm!(
|
| 251 |
+
"push ebx",
|
| 252 |
+
"push esi",
|
| 253 |
+
"push edi",
|
| 254 |
+
"mov esi, [esp+16]", // worm (3 saves=12 + ret=4 = 16)
|
| 255 |
+
"mov edi, [esp+16]",
|
| 256 |
+
"mov ebx, [esp+24]", // addr
|
| 257 |
+
"push [esp+20]", // params
|
| 258 |
+
"call ebx",
|
| 259 |
+
"pop edi",
|
| 260 |
+
"pop esi",
|
| 261 |
+
"pop ebx",
|
| 262 |
+
"ret",
|
| 263 |
+
);
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
/// Bridge: Projectile/Rope/Grenade — stdcall(worm, params, local_struct), RET 0xC.
|
| 267 |
+
/// Our args: (worm, params, local_struct, addr)
|
| 268 |
+
#[unsafe(naked)]
|
| 269 |
+
unsafe extern "C" fn call_fire_stdcall3(_worm: u32, _params: u32, _local: u32, _addr: u32) {
|
| 270 |
+
core::arch::naked_asm!(
|
| 271 |
+
"push ebx",
|
| 272 |
+
"push esi",
|
| 273 |
+
"push edi",
|
| 274 |
+
"mov esi, [esp+16]", // worm (3 saves=12 + ret=4 = 16)
|
| 275 |
+
"mov edi, [esp+16]",
|
| 276 |
+
"mov ebx, [esp+28]", // addr (saves=12 + ret=4 + 3 args=12 = 28)
|
| 277 |
+
"push [esp+24]", // local_struct
|
| 278 |
+
"push [esp+24]", // params (shifted +4)
|
| 279 |
+
"push [esp+24]", // worm (shifted +8)
|
| 280 |
+
"call ebx",
|
| 281 |
+
"pop edi",
|
| 282 |
+
"pop esi",
|
| 283 |
+
"pop ebx",
|
| 284 |
+
"ret",
|
| 285 |
+
);
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
/// Bridge: CreateWeaponProjectile — thiscall(ECX=worm, params, local_struct), RET 0x8.
|
| 289 |
+
/// Our args: (worm, params, local_struct, addr)
|
| 290 |
+
#[unsafe(naked)]
|
| 291 |
+
unsafe extern "C" fn call_fire_thiscall2(_worm: u32, _params: u32, _local: u32, _addr: u32) {
|
| 292 |
+
core::arch::naked_asm!(
|
| 293 |
+
"push ebx",
|
| 294 |
+
"push esi",
|
| 295 |
+
"push edi",
|
| 296 |
+
"mov esi, [esp+16]", // worm
|
| 297 |
+
"mov edi, [esp+16]",
|
| 298 |
+
"mov ecx, [esp+16]", // ECX = worm (this)
|
| 299 |
+
"mov ebx, [esp+28]", // addr
|
| 300 |
+
"push [esp+24]", // local_struct
|
| 301 |
+
"push [esp+24]", // params (shifted +4)
|
| 302 |
+
"call ebx",
|
| 303 |
+
"pop edi",
|
| 304 |
+
"pop esi",
|
| 305 |
+
"pop ebx",
|
| 306 |
+
"ret",
|
| 307 |
+
);
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
/// Bridge: Shotgun — stdcall(params, local_struct), RET 0x8.
|
| 311 |
+
/// Our args: (worm, params, local_struct, addr)
|
| 312 |
+
#[unsafe(naked)]
|
| 313 |
+
unsafe extern "C" fn call_fire_stdcall2(_worm: u32, _params: u32, _local: u32, _addr: u32) {
|
| 314 |
+
core::arch::naked_asm!(
|
| 315 |
+
"push ebx",
|
| 316 |
+
"push esi",
|
| 317 |
+
"push edi",
|
| 318 |
+
"mov esi, [esp+16]", // worm
|
| 319 |
+
"mov edi, [esp+16]",
|
| 320 |
+
"mov ebx, [esp+28]", // addr
|
| 321 |
+
"push [esp+24]", // local_struct
|
| 322 |
+
"push [esp+24]", // params (shifted +4)
|
| 323 |
+
"call ebx",
|
| 324 |
+
"pop edi",
|
| 325 |
+
"pop esi",
|
| 326 |
+
"pop ebx",
|
| 327 |
+
"ret",
|
| 328 |
+
);
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
/// Type 4 (special) weapon dispatch.
|
| 332 |
+
/// Type 4 (special) weapon dispatch. All sub-functions use EAX=ESI=EDI=worm.
|
| 333 |
+
unsafe fn fire_weapon_special(subtype: i32, params_38: u32, worm: u32, local_struct: u32) {
|
| 334 |
+
use openwa_core::rebase::rb;
|
| 335 |
+
|
| 336 |
+
// All handlers use EAX=worm (self-pointer at CTaskWorm+0x36C).
|
| 337 |
+
match subtype {
|
| 338 |
+
1 => fire_worm_vtable_0xe(worm, 0x6C), // Blowtorch
|
| 339 |
+
2 => call_fire_usercall(worm, worm, rb(0x51E3E0)), // Pneumatic Drill
|
| 340 |
+
3 => call_fire_stdcall3(worm, params_38, local_struct, rb(0x51E350)), // Girder
|
| 341 |
+
4 => fire_worm_vtable_0xe(worm, 0x6D), // Baseball Bat
|
| 342 |
+
5 => fire_worm_vtable_0xe(worm, 0x75), // Fire Punch
|
| 343 |
+
6 => fire_worm_vtable_0xe(worm, 0x70), // Dragon Ball
|
| 344 |
+
8 => fire_worm_vtable_0xe(worm, 0x6E), // Kamikaze
|
| 345 |
+
9 => call_fire_usercall_stdcall1(worm, worm, local_struct, rb(0x51E480)), // Prod
|
| 346 |
+
10 => call_fire_usercall(worm, worm, rb(0x51E710)), // Air Strike (EAX=worm)
|
| 347 |
+
11 => fire_worm_vtable_0xe(worm, 0x71), // Scales of Justice
|
| 348 |
+
13 => call_fire_usercall(worm, worm, rb(0x51E5C0)), // Napalm Strike
|
| 349 |
+
14 => call_fire_usercall(worm, worm, rb(0x51E670)), // Mail/Mine/Mole (EAX=worm!)
|
| 350 |
+
16 => {
|
| 351 |
+
// Teleport: check condition, then fire or vtable fallback
|
| 352 |
+
let result = call_fire_usercall_ret(worm, worm, rb(0x516930));
|
| 353 |
+
if result != 0 {
|
| 354 |
+
call_fire_usercall(worm, worm, rb(0x51EB00));
|
| 355 |
+
} else {
|
| 356 |
+
fire_worm_vtable_0xe(worm, 0x74);
|
| 357 |
+
}
|
| 358 |
+
}
|
| 359 |
+
17 => call_fire_usercall(worm, worm, rb(0x51E920)), // Freeze
|
| 360 |
+
18 => fire_worm_vtable_0xe(worm, 0x72), // Suicide Bomber
|
| 361 |
+
19 => call_fire_usercall(worm, worm, rb(0x51E8C0)), // Skip Go
|
| 362 |
+
20 => call_fire_usercall(worm, worm, rb(0x51E600)), // Surrender
|
| 363 |
+
21 => call_fire_usercall(worm, worm, rb(0x51EBE0)), // Select Worm
|
| 364 |
+
22 => call_fire_usercall(worm, worm, rb(0x51EC30)), // Jet Pack
|
| 365 |
+
23 => fire_worm_vtable_0xe(worm, 0x78), // Magic Bullet
|
| 366 |
+
24 => call_fire_usercall(worm, worm, rb(0x51EA60)), // Low Gravity / Fast Walk
|
| 367 |
+
_ => {}
|
| 368 |
+
}
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET.
|
| 372 |
+
/// Args: (eax_val, worm, addr). Saves/restores ESI+EDI. Uses EBX to hold addr.
|
| 373 |
+
#[unsafe(naked)]
|
| 374 |
+
unsafe extern "C" fn call_fire_usercall(_eax: u32, _worm: u32, _addr: u32) {
|
| 375 |
+
core::arch::naked_asm!(
|
| 376 |
+
"push ebx",
|
| 377 |
+
"push esi",
|
| 378 |
+
"push edi",
|
| 379 |
+
"mov eax, [esp+16]", // eax_val (3 saves=12 + ret=4 = 16)
|
| 380 |
+
"mov esi, [esp+20]", // worm
|
| 381 |
+
"mov edi, [esp+20]",
|
| 382 |
+
"mov ebx, [esp+24]", // addr
|
| 383 |
+
"call ebx",
|
| 384 |
+
"pop edi",
|
| 385 |
+
"pop esi",
|
| 386 |
+
"pop ebx",
|
| 387 |
+
"ret",
|
| 388 |
+
);
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET, returns EAX.
|
| 392 |
+
#[unsafe(naked)]
|
| 393 |
+
unsafe extern "C" fn call_fire_usercall_ret(_eax: u32, _worm: u32, _addr: u32) -> i32 {
|
| 394 |
+
core::arch::naked_asm!(
|
| 395 |
+
"push ebx",
|
| 396 |
+
"push esi",
|
| 397 |
+
"push edi",
|
| 398 |
+
"mov eax, [esp+16]",
|
| 399 |
+
"mov esi, [esp+20]",
|
| 400 |
+
"mov edi, [esp+20]",
|
| 401 |
+
"mov ebx, [esp+24]",
|
| 402 |
+
"call ebx",
|
| 403 |
+
"pop edi",
|
| 404 |
+
"pop esi",
|
| 405 |
+
"pop ebx",
|
| 406 |
+
"ret",
|
| 407 |
+
);
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm) + stdcall(1 param), RET 0x4.
|
| 411 |
+
#[unsafe(naked)]
|
| 412 |
+
unsafe extern "C" fn call_fire_usercall_stdcall1(
|
| 413 |
+
_eax: u32, _worm: u32, _param: u32, _addr: u32,
|
| 414 |
+
) {
|
| 415 |
+
core::arch::naked_asm!(
|
| 416 |
+
"push ebx",
|
| 417 |
+
"push esi",
|
| 418 |
+
"push edi",
|
| 419 |
+
"mov eax, [esp+16]", // eax_val
|
| 420 |
+
"mov esi, [esp+20]", // worm
|
| 421 |
+
"mov edi, [esp+20]",
|
| 422 |
+
"mov ebx, [esp+28]", // addr (3 saves=12 + ret=4 + 3 args=12 = 28)
|
| 423 |
+
"push [esp+24]", // param
|
| 424 |
+
"call ebx",
|
| 425 |
+
"pop edi",
|
| 426 |
+
"pop esi",
|
| 427 |
+
"pop ebx",
|
| 428 |
+
"ret",
|
| 429 |
+
);
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
/// Call worm->vtable[0xE](msg_id) — thiscall(ECX=worm, stack=msg_id), RET 0x4.
|
| 433 |
+
/// Original: `(**(code **)(*worm + 0x38))(msg_id)`.
|
| 434 |
+
/// Uses naked bridge to avoid LLVM stack tracking issues with push/RET mismatch.
|
| 435 |
+
#[unsafe(naked)]
|
| 436 |
+
unsafe extern "C" fn fire_worm_vtable_0xe(_worm: u32, _msg_id: u32) {
|
| 437 |
+
core::arch::naked_asm!(
|
| 438 |
+
"push ebx",
|
| 439 |
+
"push esi",
|
| 440 |
+
"push edi",
|
| 441 |
+
"mov ecx, [esp+16]", // worm (3 saves=12 + ret=4 = 16)
|
| 442 |
+
"mov esi, [esp+16]", // ESI = worm
|
| 443 |
+
"mov edi, [esp+16]", // EDI = worm
|
| 444 |
+
"mov ebx, [ecx]", // vtable
|
| 445 |
+
"mov ebx, [ebx+0x38]",// vtable[0xE]
|
| 446 |
+
"push [esp+20]", // msg_id
|
| 447 |
+
"call ebx", // thiscall: ECX=worm, RET 0x4 cleans msg_id
|
| 448 |
+
"pop edi",
|
| 449 |
+
"pop esi",
|
| 450 |
+
"pop ebx",
|
| 451 |
+
"ret",
|
| 452 |
);
|
| 453 |
}
|
| 454 |
|