feat: port SpawnEffect (0x547C30) to Rust and hook it
Browse filesPure Rust implementation of SpawnEffect_Maybe: builds a 0x408-byte
message buffer from params, looks up CTaskSpriteAnim via SharedData
(entity type 0x1A), and sends HandleMessage(0x56).
Hooked with a naked usercall trampoline that captures EAX (constant),
ECX (speed_x), ESI (worm) + 7 stack params. Serves all 17 WA callers.
Also called directly from weapon_release_impl.
This eliminates the last naked asm bridge from weapon_release.rs β
the module now has zero bridge functions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-wormkit/src/replacements/weapon_release.rs
CHANGED
|
@@ -10,9 +10,8 @@ use openwa_core::audio::{KnownSoundId, SoundId};
|
|
| 10 |
use openwa_core::fixed::Fixed;
|
| 11 |
use openwa_core::game::Weapon;
|
| 12 |
use openwa_core::log::log_line;
|
| 13 |
-
use openwa_core::rebase::rb;
|
| 14 |
use openwa_core::task::worm::{CTaskWorm, WormState};
|
| 15 |
-
use openwa_core::task::{CGameTask, Task};
|
| 16 |
|
| 17 |
use crate::hook::{self, usercall_trampoline};
|
| 18 |
use crate::replacements::{sound, weapon};
|
|
@@ -138,8 +137,8 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 138 |
}
|
| 139 |
|
| 140 |
// ββ 2. Populate context fields ββββββββββββββββββββββββββ
|
| 141 |
-
let speed_x = w.base.pos_x
|
| 142 |
-
let speed_y = w.base.pos_y
|
| 143 |
ctx.team_id = w.team_index;
|
| 144 |
ctx.worm_id = w.worm_index;
|
| 145 |
ctx.ammo_per_turn = w.weapon_param_1 as u32;
|
|
@@ -388,9 +387,9 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 388 |
let facing_flag: u32 = if facing < 1 { 0x40000 } else { 0 };
|
| 389 |
let state_flag = facing_flag + effect_state;
|
| 390 |
|
| 391 |
-
|
| 392 |
-
worm, speed_x, speed_y, rng_scaled, rng1_offset, palette, state_flag,
|
| 393 |
-
|
| 394 |
);
|
| 395 |
}
|
| 396 |
|
|
@@ -411,43 +410,94 @@ fn write_u32(buf: &mut [u8], offset: usize, value: u32) {
|
|
| 411 |
buf[offset..offset + 4].copy_from_slice(&value.to_ne_bytes());
|
| 412 |
}
|
| 413 |
|
| 414 |
-
// ββ
|
| 415 |
|
| 416 |
-
///
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
core::arch::naked_asm!(
|
| 431 |
-
|
| 432 |
"push ebx",
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
//
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
"push [esp+
|
| 440 |
-
"push [esp+
|
| 441 |
-
"push [esp+
|
| 442 |
-
"push [esp+
|
| 443 |
-
"push [esp+
|
| 444 |
-
"push [esp+
|
| 445 |
-
"push [esp+
|
| 446 |
-
"
|
| 447 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
"pop ebx",
|
| 449 |
-
"
|
| 450 |
-
|
| 451 |
);
|
| 452 |
}
|
| 453 |
|
|
@@ -455,6 +505,12 @@ unsafe extern "C" fn call_spawn_effect_full(
|
|
| 455 |
|
| 456 |
pub fn install() -> Result<(), String> {
|
| 457 |
unsafe {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
hook::install(
|
| 459 |
"WeaponRelease",
|
| 460 |
va::WEAPON_RELEASE,
|
|
|
|
| 10 |
use openwa_core::fixed::Fixed;
|
| 11 |
use openwa_core::game::Weapon;
|
| 12 |
use openwa_core::log::log_line;
|
|
|
|
| 13 |
use openwa_core::task::worm::{CTaskWorm, WormState};
|
| 14 |
+
use openwa_core::task::{CGameTask, SharedDataTable, Task};
|
| 15 |
|
| 16 |
use crate::hook::{self, usercall_trampoline};
|
| 17 |
use crate::replacements::{sound, weapon};
|
|
|
|
| 137 |
}
|
| 138 |
|
| 139 |
// ββ 2. Populate context fields ββββββββββββββββββββββββββ
|
| 140 |
+
let speed_x = w.base.pos_x;
|
| 141 |
+
let speed_y = w.base.pos_y;
|
| 142 |
ctx.team_id = w.team_index;
|
| 143 |
ctx.worm_id = w.worm_index;
|
| 144 |
ctx.ammo_per_turn = w.weapon_param_1 as u32;
|
|
|
|
| 387 |
let facing_flag: u32 = if facing < 1 { 0x40000 } else { 0 };
|
| 388 |
let state_flag = facing_flag + effect_state;
|
| 389 |
|
| 390 |
+
spawn_effect(
|
| 391 |
+
worm, 0x80000, speed_x, speed_y, rng_scaled, rng1_offset, palette, state_flag,
|
| 392 |
+
Fixed(0xA0000), Fixed(0x1999),
|
| 393 |
);
|
| 394 |
}
|
| 395 |
|
|
|
|
| 410 |
buf[offset..offset + 4].copy_from_slice(&value.to_ne_bytes());
|
| 411 |
}
|
| 412 |
|
| 413 |
+
// ββ SpawnEffect (0x547C30) β hooked, also called directly βββ
|
| 414 |
|
| 415 |
+
/// Spawn a visual effect on the sprite anim entity. Pure Rust port of FUN_00547C30.
|
| 416 |
+
///
|
| 417 |
+
/// Builds a 0x408-byte message buffer from the params, looks up CTaskSpriteAnim
|
| 418 |
+
/// via SharedData (entity type 0x1A), and sends HandleMessage(0x56).
|
| 419 |
+
///
|
| 420 |
+
/// Called directly from `weapon_release_impl` and via the hook trampoline
|
| 421 |
+
/// for all other WA callers (17 call sites).
|
| 422 |
+
pub(crate) unsafe fn spawn_effect(
|
| 423 |
+
worm: *mut CTaskWorm,
|
| 424 |
+
constant: u32,
|
| 425 |
+
speed_x: Fixed,
|
| 426 |
+
speed_y: Fixed,
|
| 427 |
+
rng_scaled: i32,
|
| 428 |
+
rng_offset: i32,
|
| 429 |
+
palette: u32,
|
| 430 |
+
state_flag: u32,
|
| 431 |
+
size: Fixed,
|
| 432 |
+
scale: Fixed,
|
| 433 |
) {
|
| 434 |
+
// Build the message buffer matching the original stack layout.
|
| 435 |
+
// The original pushes ESI (worm ptr) then uses LEA to include it at offset 0.
|
| 436 |
+
let mut buf = [0u8; 0x408];
|
| 437 |
+
write_u32(&mut buf, 0x00, worm as u32);
|
| 438 |
+
write_u32(&mut buf, 0x04, constant);
|
| 439 |
+
write_u32(&mut buf, 0x08, speed_x.0 as u32);
|
| 440 |
+
write_u32(&mut buf, 0x0C, speed_y.0 as u32);
|
| 441 |
+
write_u32(&mut buf, 0x10, rng_scaled as u32);
|
| 442 |
+
write_u32(&mut buf, 0x14, rng_offset as u32);
|
| 443 |
+
// [0x18] = 0 (already zeroed)
|
| 444 |
+
write_u32(&mut buf, 0x1C, palette);
|
| 445 |
+
write_u32(&mut buf, 0x20, state_flag);
|
| 446 |
+
// [0x24] = 0 (already zeroed)
|
| 447 |
+
write_u32(&mut buf, 0x28, size.0 as u32);
|
| 448 |
+
write_u32(&mut buf, 0x2C, scale.0 as u32);
|
| 449 |
+
|
| 450 |
+
// SharedData lookup for entity type 0x1A (CTaskSpriteAnim)
|
| 451 |
+
let table = SharedDataTable::from_task((*worm).as_task_ptr());
|
| 452 |
+
let entity = table.lookup(0, 0x1A);
|
| 453 |
+
if !entity.is_null() {
|
| 454 |
+
let vtable = *(entity as *const *const usize);
|
| 455 |
+
let handle_msg: unsafe extern "thiscall" fn(
|
| 456 |
+
*mut u8, *mut u8, u32, u32, *const u8,
|
| 457 |
+
) = core::mem::transmute(*vtable.add(2));
|
| 458 |
+
handle_msg(
|
| 459 |
+
entity as *mut u8,
|
| 460 |
+
worm as *mut u8,
|
| 461 |
+
0x56,
|
| 462 |
+
0x408,
|
| 463 |
+
buf.as_ptr(),
|
| 464 |
+
);
|
| 465 |
+
}
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
/// Naked trampoline for SpawnEffect (0x547C30).
|
| 469 |
+
///
|
| 470 |
+
/// Original convention: usercall(EAX=constant, ECX=speed_x, ESI=worm)
|
| 471 |
+
/// + 7 stack params (speed_y, rng_scaled, rng_offset, palette, state_flag, size, scale)
|
| 472 |
+
/// RET 0x1C.
|
| 473 |
+
#[unsafe(naked)]
|
| 474 |
+
unsafe extern "C" fn trampoline_spawn_effect() {
|
| 475 |
core::arch::naked_asm!(
|
| 476 |
+
// Save callee-saved registers
|
| 477 |
"push ebx",
|
| 478 |
+
"push ebp",
|
| 479 |
+
"push edi",
|
| 480 |
+
// ESI = worm, EAX = constant, ECX = speed_x
|
| 481 |
+
// Stack after 3 saves(12) + ret(4) = 16; original stack params start at +16
|
| 482 |
+
// Push cdecl args in reverse: scale, size, state_flag, palette, rng_offset,
|
| 483 |
+
// rng_scaled, speed_y, 0(rng_offset placeholder), speed_x(ECX), constant(EAX), worm(ESI)
|
| 484 |
+
"push [esp+40]", // scale (16+24=40)
|
| 485 |
+
"push [esp+40]", // size
|
| 486 |
+
"push [esp+40]", // state_flag
|
| 487 |
+
"push [esp+40]", // palette
|
| 488 |
+
"push [esp+40]", // rng_offset
|
| 489 |
+
"push [esp+40]", // rng_scaled
|
| 490 |
+
"push [esp+40]", // speed_y
|
| 491 |
+
"push ecx", // speed_x (register param)
|
| 492 |
+
"push eax", // constant (register param)
|
| 493 |
+
"push esi", // worm (register param)
|
| 494 |
+
"call {impl_fn}",
|
| 495 |
+
"add esp, 40", // clean 10 cdecl args
|
| 496 |
+
"pop edi",
|
| 497 |
+
"pop ebp",
|
| 498 |
"pop ebx",
|
| 499 |
+
"ret 0x1C", // clean 7 original stack params
|
| 500 |
+
impl_fn = sym spawn_effect,
|
| 501 |
);
|
| 502 |
}
|
| 503 |
|
|
|
|
| 505 |
|
| 506 |
pub fn install() -> Result<(), String> {
|
| 507 |
unsafe {
|
| 508 |
+
hook::install(
|
| 509 |
+
"SpawnEffect",
|
| 510 |
+
va::SPAWN_EFFECT,
|
| 511 |
+
trampoline_spawn_effect as *const (),
|
| 512 |
+
)?;
|
| 513 |
+
|
| 514 |
hook::install(
|
| 515 |
"WeaponRelease",
|
| 516 |
va::WEAPON_RELEASE,
|