refactor: eliminate pointer arithmetic in weapon.rs — typed params throughout
Browse files- Rename DDGame.hurry_flag → version_flag_4 (confirmed via Ghidra as game
logic version byte, not a hurry flag)
- Replace raw fire_params pointer math with struct field access
(collision_radius.0, _fp_04) with polymorphism comments
- Type local_struct as *const WeaponSpawnData in projectile_fire_impl,
use named fields (initial_speed_x/y) instead of array indexing
- Type all naked asm bridge params: *mut CTaskWorm, *const WeaponFireParams,
*const WeaponSpawnData, *const u8, *const () instead of u32
- Simplify fire_weapon_special: remove params_38 u32 arg, compute
params_38_ptr inside from entry pointer
- Type CTaskArrow/CTaskMissile ctor calls with proper pointer params
- Remove duplicate section header comments
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -313,8 +313,9 @@ pub struct DDGame {
|
|
| 313 |
/// 0x7E3F: Version flag byte 3 (set by InitVersionFlags).
|
| 314 |
/// Passed to is_super_weapon as the version/mode parameter.
|
| 315 |
pub version_flag_3: u8,
|
| 316 |
-
/// 0x7E40:
|
| 317 |
-
|
|
|
|
| 318 |
/// 0x7E41-0x7E9F: Unknown
|
| 319 |
pub _unknown_7e41: [u8; 0x7EA0 - 0x7E41],
|
| 320 |
|
|
|
|
| 313 |
/// 0x7E3F: Version flag byte 3 (set by InitVersionFlags).
|
| 314 |
/// Passed to is_super_weapon as the version/mode parameter.
|
| 315 |
pub version_flag_3: u8,
|
| 316 |
+
/// 0x7E40: Game logic version byte 4 (set by InitVersionFlags).
|
| 317 |
+
/// Used in FireWeapon__MailMineMole to gate vtable call behavior by version.
|
| 318 |
+
pub version_flag_4: u8,
|
| 319 |
/// 0x7E41-0x7E9F: Unknown
|
| 320 |
pub _unknown_7e41: [u8; 0x7EA0 - 0x7E41],
|
| 321 |
|
|
@@ -17,7 +17,8 @@ use core::sync::atomic::{AtomicU32, Ordering};
|
|
| 17 |
|
| 18 |
use openwa_core::address::va;
|
| 19 |
use openwa_core::engine::ddgame::{self, TeamArenaRef};
|
| 20 |
-
use openwa_core::
|
|
|
|
| 21 |
use openwa_core::game::Weapon;
|
| 22 |
use openwa_core::log::log_line;
|
| 23 |
use openwa_core::task::Task;
|
|
@@ -195,7 +196,7 @@ unsafe extern "C" fn trampoline_fire_weapon() {
|
|
| 195 |
/// Completion flag at worm+0x3C (CGameTask.subclass_data[12]).
|
| 196 |
/// Params pointer at entry+0x3C (WeaponEntry.fire_complete) — different object, same offset.
|
| 197 |
unsafe extern "cdecl" fn fire_weapon_impl(
|
| 198 |
-
entry: *const WeaponEntry, local_struct:
|
| 199 |
) {
|
| 200 |
use openwa_core::rebase::rb;
|
| 201 |
|
|
@@ -203,8 +204,6 @@ unsafe extern "cdecl" fn fire_weapon_impl(
|
|
| 203 |
let subtype_34 = (*entry).fire_subtype_34;
|
| 204 |
let subtype_38 = (*entry).fire_subtype_38;
|
| 205 |
let fire_params = &raw const (*entry).fire_params;
|
| 206 |
-
let w = worm as u32;
|
| 207 |
-
|
| 208 |
// Log weapon fire
|
| 209 |
let weapon_id = (*worm).selected_weapon;
|
| 210 |
let weapon_name = Weapon::try_from(weapon_id)
|
|
@@ -219,25 +218,25 @@ unsafe extern "cdecl" fn fire_weapon_impl(
|
|
| 219 |
|
| 220 |
match weapon_type {
|
| 221 |
1 => match subtype_38 {
|
| 222 |
-
1 => call_fire_placed_explosive(
|
| 223 |
-
2 => call_fire_stdcall3(
|
| 224 |
-
3 => call_fire_thiscall2(
|
| 225 |
-
4 => call_fire_thiscall2(
|
| 226 |
_ => {}
|
| 227 |
},
|
| 228 |
2 => match subtype_38 {
|
| 229 |
-
1 => call_fire_stdcall3(
|
| 230 |
-
2 => call_fire_thiscall2(
|
| 231 |
-
3 => call_fire_stdcall3(
|
| 232 |
_ => {}
|
| 233 |
},
|
| 234 |
3 => {
|
|
|
|
| 235 |
let subtype_34_ptr = &raw const (*entry).fire_subtype_34 as *const WeaponFireParams;
|
| 236 |
-
call_fire_stdcall3(
|
| 237 |
}
|
| 238 |
4 => {
|
| 239 |
-
|
| 240 |
-
fire_weapon_special(subtype_34, subtype_38_ptr, worm, local_struct, entry);
|
| 241 |
}
|
| 242 |
_ => {}
|
| 243 |
}
|
|
@@ -245,14 +244,6 @@ unsafe extern "cdecl" fn fire_weapon_impl(
|
|
| 245 |
(*worm).set_fire_complete(1);
|
| 246 |
}
|
| 247 |
|
| 248 |
-
// ── Sub-function bridges ────────────────────────────────────
|
| 249 |
-
// All preserve ESI=worm for usercall sub-functions.
|
| 250 |
-
|
| 251 |
-
// ── Sub-function bridges ────────────────────────────────────
|
| 252 |
-
// All bridges set ESI=worm AND EDI=worm before calling, since
|
| 253 |
-
// sub-functions are usercall and read both registers implicitly.
|
| 254 |
-
// Stack offsets are carefully calculated for each bridge.
|
| 255 |
-
|
| 256 |
// ── Sub-function bridges ────────────────────────────────────
|
| 257 |
// All bridges save/restore ESI+EDI, set ESI=EDI=worm, then call.
|
| 258 |
// This preserves LLVM's callee-saved registers while providing
|
|
@@ -262,7 +253,7 @@ unsafe extern "cdecl" fn fire_weapon_impl(
|
|
| 262 |
/// Args: (worm, fire_params, local_struct, addr).
|
| 263 |
#[unsafe(naked)]
|
| 264 |
unsafe extern "C" fn call_fire_placed_explosive(
|
| 265 |
-
_worm:
|
| 266 |
) {
|
| 267 |
core::arch::naked_asm!(
|
| 268 |
"push ebx",
|
|
@@ -285,7 +276,7 @@ unsafe extern "C" fn call_fire_placed_explosive(
|
|
| 285 |
/// Bridge: Projectile/Rope/Grenade — stdcall(worm, fire_params, local_struct), RET 0xC.
|
| 286 |
#[unsafe(naked)]
|
| 287 |
unsafe extern "C" fn call_fire_stdcall3(
|
| 288 |
-
_worm:
|
| 289 |
) {
|
| 290 |
core::arch::naked_asm!(
|
| 291 |
"push ebx",
|
|
@@ -308,7 +299,7 @@ unsafe extern "C" fn call_fire_stdcall3(
|
|
| 308 |
/// Bridge: CreateWeaponProjectile — thiscall(ECX=worm, fire_params, local_struct), RET 0x8.
|
| 309 |
#[unsafe(naked)]
|
| 310 |
unsafe extern "C" fn call_fire_thiscall2(
|
| 311 |
-
_worm:
|
| 312 |
) {
|
| 313 |
core::arch::naked_asm!(
|
| 314 |
"push ebx",
|
|
@@ -333,7 +324,7 @@ unsafe extern "C" fn call_fire_thiscall2(
|
|
| 333 |
#[unsafe(naked)]
|
| 334 |
#[allow(dead_code)]
|
| 335 |
unsafe extern "C" fn call_fire_stdcall2(
|
| 336 |
-
_worm:
|
| 337 |
) {
|
| 338 |
core::arch::naked_asm!(
|
| 339 |
"push ebx",
|
|
@@ -352,51 +343,50 @@ unsafe extern "C" fn call_fire_stdcall2(
|
|
| 352 |
);
|
| 353 |
}
|
| 354 |
|
| 355 |
-
/// Type 4 (special) weapon dispatch.
|
| 356 |
/// Type 4 (special) weapon dispatch.
|
| 357 |
///
|
| 358 |
/// EAX at entry = weapon entry pointer (unchanged from type-4 switch).
|
| 359 |
/// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
|
| 360 |
/// Handlers without explicit MOV EAX inherit the entry pointer.
|
| 361 |
unsafe fn fire_weapon_special(
|
| 362 |
-
subtype: i32,
|
| 363 |
-
entry: *const WeaponEntry,
|
| 364 |
) {
|
| 365 |
use openwa_core::rebase::rb;
|
| 366 |
-
|
| 367 |
-
|
|
|
|
| 368 |
|
| 369 |
match subtype {
|
| 370 |
-
1 => fire_worm_vtable_0xe(
|
| 371 |
-
2 => call_fire_usercall(
|
| 372 |
-
3 => call_fire_stdcall3(
|
| 373 |
-
4 => fire_worm_vtable_0xe(
|
| 374 |
-
5 => fire_worm_vtable_0xe(
|
| 375 |
-
6 => fire_worm_vtable_0xe(
|
| 376 |
-
8 => fire_worm_vtable_0xe(
|
| 377 |
-
9 => call_fire_usercall_stdcall1(
|
| 378 |
-
10 => call_fire_usercall(
|
| 379 |
-
11 => fire_worm_vtable_0xe(
|
| 380 |
-
13 => fire_send_team_message(worm, 0x2B),
|
| 381 |
-
14 => fire_mail_mine_mole(worm),
|
| 382 |
16 => {
|
| 383 |
// Teleport: MOV EAX,[ESI+0x44] (worm state) before check
|
| 384 |
let worm_state = (*worm).state();
|
| 385 |
-
let result = call_fire_usercall_ret(worm_state,
|
| 386 |
if result != 0 {
|
| 387 |
-
call_fire_usercall(result as
|
| 388 |
} else {
|
| 389 |
-
fire_worm_vtable_0xe(
|
| 390 |
}
|
| 391 |
}
|
| 392 |
-
17 => call_fire_usercall(
|
| 393 |
-
18 => fire_worm_vtable_0xe(
|
| 394 |
-
19 => fire_skip_go(worm, entry),
|
| 395 |
-
20 => fire_surrender(worm),
|
| 396 |
-
21 => fire_select_worm(worm),
|
| 397 |
-
22 => call_fire_usercall(
|
| 398 |
-
23 => fire_worm_vtable_0xe(
|
| 399 |
-
24 => call_fire_usercall(
|
| 400 |
_ => {}
|
| 401 |
}
|
| 402 |
}
|
|
@@ -517,7 +507,7 @@ unsafe fn fire_surrender(worm: *mut CTaskWorm) {
|
|
| 517 |
unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
|
| 518 |
use openwa_core::engine::ddgame::TeamArenaRef;
|
| 519 |
let ddgame = (*worm).ddgame();
|
| 520 |
-
let version =
|
| 521 |
let worm_state = (*worm).state();
|
| 522 |
|
| 523 |
let should_call_vtable = version < 2
|
|
@@ -525,7 +515,7 @@ unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
|
|
| 525 |
&& (worm_state == 0x7D || (worm_state == 0x78 && version < 8)));
|
| 526 |
|
| 527 |
if should_call_vtable {
|
| 528 |
-
fire_worm_vtable_0xe(worm
|
| 529 |
}
|
| 530 |
|
| 531 |
fire_send_team_message(worm, 0x28);
|
|
@@ -543,7 +533,7 @@ unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
|
|
| 543 |
/// Args: (spawn_data, worm, fire_params, addr).
|
| 544 |
#[unsafe(naked)]
|
| 545 |
unsafe extern "C" fn call_projectile_fire_single(
|
| 546 |
-
_spawn_data:
|
| 547 |
) {
|
| 548 |
core::arch::naked_asm!(
|
| 549 |
"push ebx",
|
|
@@ -566,7 +556,7 @@ unsafe extern "C" fn call_projectile_fire_single(
|
|
| 566 |
/// Args: (this, parent, fire_params, spawn_data, ctor_addr).
|
| 567 |
#[unsafe(naked)]
|
| 568 |
unsafe extern "C" fn call_missile_ctor(
|
| 569 |
-
_this:
|
| 570 |
) {
|
| 571 |
core::arch::naked_asm!(
|
| 572 |
"push ebx",
|
|
@@ -589,7 +579,7 @@ unsafe extern "C" fn call_missile_ctor(
|
|
| 589 |
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET.
|
| 590 |
/// Args: (eax_val, worm, addr). Saves/restores ESI+EDI. Uses EBX to hold addr.
|
| 591 |
#[unsafe(naked)]
|
| 592 |
-
unsafe extern "C" fn call_fire_usercall(_eax:
|
| 593 |
core::arch::naked_asm!(
|
| 594 |
"push ebx",
|
| 595 |
"push esi",
|
|
@@ -608,7 +598,7 @@ unsafe extern "C" fn call_fire_usercall(_eax: u32, _worm: u32, _addr: u32) {
|
|
| 608 |
|
| 609 |
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET, returns EAX.
|
| 610 |
#[unsafe(naked)]
|
| 611 |
-
unsafe extern "C" fn call_fire_usercall_ret(_eax:
|
| 612 |
core::arch::naked_asm!(
|
| 613 |
"push ebx",
|
| 614 |
"push esi",
|
|
@@ -628,7 +618,7 @@ unsafe extern "C" fn call_fire_usercall_ret(_eax: u32, _worm: u32, _addr: u32) -
|
|
| 628 |
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm) + stdcall(1 param), RET 0x4.
|
| 629 |
#[unsafe(naked)]
|
| 630 |
unsafe extern "C" fn call_fire_usercall_stdcall1(
|
| 631 |
-
_eax:
|
| 632 |
) {
|
| 633 |
core::arch::naked_asm!(
|
| 634 |
"push ebx",
|
|
@@ -651,7 +641,7 @@ unsafe extern "C" fn call_fire_usercall_stdcall1(
|
|
| 651 |
/// Original: `(**(code **)(*worm + 0x38))(msg_id)`.
|
| 652 |
/// Uses naked bridge to avoid LLVM stack tracking issues with push/RET mismatch.
|
| 653 |
#[unsafe(naked)]
|
| 654 |
-
unsafe extern "C" fn fire_worm_vtable_0xe(_worm:
|
| 655 |
core::arch::naked_asm!(
|
| 656 |
"push ebx",
|
| 657 |
"push esi",
|
|
@@ -706,7 +696,7 @@ unsafe extern "C" fn trampoline_create_weapon_projectile() {
|
|
| 706 |
/// Original: 0x51E0F0. Allocates CTaskMissile (0x40C bytes), looks up
|
| 707 |
/// parent CTaskTurnGame via SharedData, calls the original constructor.
|
| 708 |
unsafe extern "cdecl" fn create_weapon_projectile_impl(
|
| 709 |
-
worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct:
|
| 710 |
) {
|
| 711 |
use openwa_core::rebase::rb;
|
| 712 |
use openwa_core::task::{SharedDataTable, Task};
|
|
@@ -736,9 +726,9 @@ unsafe extern "cdecl" fn create_weapon_projectile_impl(
|
|
| 736 |
// Call original CTaskMissile::Constructor
|
| 737 |
// thiscall(ECX=buffer, parent, fire_params, local_struct), RET 0xC
|
| 738 |
call_missile_ctor(
|
| 739 |
-
buffer
|
| 740 |
-
parent as
|
| 741 |
-
fire_params
|
| 742 |
local_struct,
|
| 743 |
rb(va::CTASK_MISSILE_CTOR),
|
| 744 |
);
|
|
@@ -780,23 +770,23 @@ unsafe extern "C" fn trampoline_projectile_fire() {
|
|
| 780 |
/// e. Write rotated velocity into local spawn data
|
| 781 |
/// f. Call ProjectileFire_Single(loop_counter, fire_params) with EDI=spawn_data
|
| 782 |
unsafe extern "cdecl" fn projectile_fire_impl(
|
| 783 |
-
worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const
|
| 784 |
) {
|
| 785 |
use openwa_core::rebase::rb;
|
| 786 |
|
| 787 |
-
let
|
| 788 |
-
|
|
|
|
| 789 |
if shot_count <= 0 {
|
| 790 |
return;
|
| 791 |
}
|
| 792 |
|
| 793 |
-
// Copy
|
| 794 |
-
let mut spawn_data =
|
| 795 |
-
core::ptr::copy_nonoverlapping(local_struct, spawn_data.as_mut_ptr(), 11);
|
| 796 |
|
| 797 |
-
// Read template velocity
|
| 798 |
-
let template_speed_x =
|
| 799 |
-
let template_speed_y =
|
| 800 |
|
| 801 |
// Trig table: sin at SIN_TABLE, cos at SIN_TABLE + 256*4
|
| 802 |
let sin_table = rb(va::SIN_TABLE) as *const i32;
|
|
@@ -809,8 +799,9 @@ unsafe extern "cdecl" fn projectile_fire_impl(
|
|
| 809 |
let rng = ddgame.advance_rng();
|
| 810 |
|
| 811 |
// Compute spread angle: ((rng_low16 - 0x8000) * spread_param) / 360
|
|
|
|
| 812 |
let rng_centered = (rng & 0xFFFF) as i32 - 0x8000;
|
| 813 |
-
let spread_param =
|
| 814 |
let angle = (rng_centered * spread_param) / 360;
|
| 815 |
|
| 816 |
// Cos/sin table lookup with linear interpolation
|
|
@@ -836,14 +827,14 @@ unsafe extern "cdecl" fn projectile_fire_impl(
|
|
| 836 |
+ fixed_mul(cos_val, template_speed_y);
|
| 837 |
|
| 838 |
// Write rotated velocity into spawn data
|
| 839 |
-
spawn_data
|
| 840 |
-
spawn_data
|
| 841 |
|
| 842 |
// Call ProjectileFire_Single(worm, fire_params) with EDI=&spawn_data
|
| 843 |
call_projectile_fire_single(
|
| 844 |
-
|
| 845 |
-
worm
|
| 846 |
-
fire_params
|
| 847 |
rb(va::PROJECTILE_FIRE_SINGLE),
|
| 848 |
);
|
| 849 |
}
|
|
@@ -879,7 +870,7 @@ unsafe extern "C" fn trampoline_create_arrow() {
|
|
| 879 |
}
|
| 880 |
|
| 881 |
unsafe extern "cdecl" fn create_arrow_impl(
|
| 882 |
-
worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct:
|
| 883 |
) {
|
| 884 |
use openwa_core::rebase::rb;
|
| 885 |
use openwa_core::task::{SharedDataTable, Task};
|
|
@@ -905,9 +896,9 @@ unsafe extern "cdecl" fn create_arrow_impl(
|
|
| 905 |
core::ptr::write_bytes(buffer, 0, 0x148);
|
| 906 |
|
| 907 |
// CTaskArrow::Constructor — stdcall(this, parent, fire_params, local_struct), RET 0x10
|
| 908 |
-
let ctor: unsafe extern "stdcall" fn(
|
| 909 |
core::mem::transmute(rb(va::CTASK_ARROW_CTOR));
|
| 910 |
-
ctor(buffer
|
| 911 |
|
| 912 |
let _ = log_line(&format!(
|
| 913 |
"[Weapon] CreateArrow: worm=0x{:08X} arrow=0x{:08X}",
|
|
|
|
| 17 |
|
| 18 |
use openwa_core::address::va;
|
| 19 |
use openwa_core::engine::ddgame::{self, TeamArenaRef};
|
| 20 |
+
use openwa_core::fixed::Fixed;
|
| 21 |
+
use openwa_core::game::weapon::{WeaponEntry, WeaponFireParams, WeaponSpawnData};
|
| 22 |
use openwa_core::game::Weapon;
|
| 23 |
use openwa_core::log::log_line;
|
| 24 |
use openwa_core::task::Task;
|
|
|
|
| 196 |
/// Completion flag at worm+0x3C (CGameTask.subclass_data[12]).
|
| 197 |
/// Params pointer at entry+0x3C (WeaponEntry.fire_complete) — different object, same offset.
|
| 198 |
unsafe extern "cdecl" fn fire_weapon_impl(
|
| 199 |
+
entry: *const WeaponEntry, local_struct: *const u8, worm: *mut CTaskWorm,
|
| 200 |
) {
|
| 201 |
use openwa_core::rebase::rb;
|
| 202 |
|
|
|
|
| 204 |
let subtype_34 = (*entry).fire_subtype_34;
|
| 205 |
let subtype_38 = (*entry).fire_subtype_38;
|
| 206 |
let fire_params = &raw const (*entry).fire_params;
|
|
|
|
|
|
|
| 207 |
// Log weapon fire
|
| 208 |
let weapon_id = (*worm).selected_weapon;
|
| 209 |
let weapon_name = Weapon::try_from(weapon_id)
|
|
|
|
| 218 |
|
| 219 |
match weapon_type {
|
| 220 |
1 => match subtype_38 {
|
| 221 |
+
1 => call_fire_placed_explosive(worm, fire_params, local_struct, rb(0x51EC80)), // PlacedExplosive
|
| 222 |
+
2 => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51DFB0)), // Projectile
|
| 223 |
+
3 => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
|
| 224 |
+
4 => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51ED90)), // CreateArrow (Shotgun/Longbow)
|
| 225 |
_ => {}
|
| 226 |
},
|
| 227 |
2 => match subtype_38 {
|
| 228 |
+
1 => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51E1C0)), // RopeType1
|
| 229 |
+
2 => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
|
| 230 |
+
3 => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51E240)), // RopeType3
|
| 231 |
_ => {}
|
| 232 |
},
|
| 233 |
3 => {
|
| 234 |
+
// StrikeFire takes a pointer to the subtype_34 field (reinterpreted as fire params)
|
| 235 |
let subtype_34_ptr = &raw const (*entry).fire_subtype_34 as *const WeaponFireParams;
|
| 236 |
+
call_fire_stdcall3(worm, subtype_34_ptr, local_struct, rb(0x51E2C0));
|
| 237 |
}
|
| 238 |
4 => {
|
| 239 |
+
fire_weapon_special(subtype_34, entry, worm, local_struct);
|
|
|
|
| 240 |
}
|
| 241 |
_ => {}
|
| 242 |
}
|
|
|
|
| 244 |
(*worm).set_fire_complete(1);
|
| 245 |
}
|
| 246 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
// ── Sub-function bridges ────────────────────────────────────
|
| 248 |
// All bridges save/restore ESI+EDI, set ESI=EDI=worm, then call.
|
| 249 |
// This preserves LLVM's callee-saved registers while providing
|
|
|
|
| 253 |
/// Args: (worm, fire_params, local_struct, addr).
|
| 254 |
#[unsafe(naked)]
|
| 255 |
unsafe extern "C" fn call_fire_placed_explosive(
|
| 256 |
+
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _local_struct: *const u8, _addr: u32,
|
| 257 |
) {
|
| 258 |
core::arch::naked_asm!(
|
| 259 |
"push ebx",
|
|
|
|
| 276 |
/// Bridge: Projectile/Rope/Grenade — stdcall(worm, fire_params, local_struct), RET 0xC.
|
| 277 |
#[unsafe(naked)]
|
| 278 |
unsafe extern "C" fn call_fire_stdcall3(
|
| 279 |
+
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _local: *const u8, _addr: u32,
|
| 280 |
) {
|
| 281 |
core::arch::naked_asm!(
|
| 282 |
"push ebx",
|
|
|
|
| 299 |
/// Bridge: CreateWeaponProjectile — thiscall(ECX=worm, fire_params, local_struct), RET 0x8.
|
| 300 |
#[unsafe(naked)]
|
| 301 |
unsafe extern "C" fn call_fire_thiscall2(
|
| 302 |
+
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _local: *const u8, _addr: u32,
|
| 303 |
) {
|
| 304 |
core::arch::naked_asm!(
|
| 305 |
"push ebx",
|
|
|
|
| 324 |
#[unsafe(naked)]
|
| 325 |
#[allow(dead_code)]
|
| 326 |
unsafe extern "C" fn call_fire_stdcall2(
|
| 327 |
+
_worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _local: *const u8, _addr: u32,
|
| 328 |
) {
|
| 329 |
core::arch::naked_asm!(
|
| 330 |
"push ebx",
|
|
|
|
| 343 |
);
|
| 344 |
}
|
| 345 |
|
|
|
|
| 346 |
/// Type 4 (special) weapon dispatch.
|
| 347 |
///
|
| 348 |
/// EAX at entry = weapon entry pointer (unchanged from type-4 switch).
|
| 349 |
/// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
|
| 350 |
/// Handlers without explicit MOV EAX inherit the entry pointer.
|
| 351 |
unsafe fn fire_weapon_special(
|
| 352 |
+
subtype: i32, entry: *const WeaponEntry, worm: *mut CTaskWorm, local_struct: *const u8,
|
|
|
|
| 353 |
) {
|
| 354 |
use openwa_core::rebase::rb;
|
| 355 |
+
|
| 356 |
+
// Pointer to fire_subtype_38 field, reinterpreted as fire params pointer for Girder
|
| 357 |
+
let params_38_ptr = &raw const (*entry).fire_subtype_38 as *const WeaponFireParams;
|
| 358 |
|
| 359 |
match subtype {
|
| 360 |
+
1 => fire_worm_vtable_0xe(worm, 0x6C), // Blowtorch
|
| 361 |
+
2 => call_fire_usercall(entry as *const (), worm, rb(0x51E3E0)), // Drill (EAX=entry)
|
| 362 |
+
3 => call_fire_stdcall3(worm, params_38_ptr, local_struct, rb(0x51E350)), // Girder
|
| 363 |
+
4 => fire_worm_vtable_0xe(worm, 0x6D), // Baseball Bat
|
| 364 |
+
5 => fire_worm_vtable_0xe(worm, 0x75), // Fire Punch
|
| 365 |
+
6 => fire_worm_vtable_0xe(worm, 0x70), // Dragon Ball
|
| 366 |
+
8 => fire_worm_vtable_0xe(worm, 0x6E), // Kamikaze
|
| 367 |
+
9 => call_fire_usercall_stdcall1(entry as *const (), worm, local_struct, rb(0x51E480)), // Prod (EAX=entry)
|
| 368 |
+
10 => call_fire_usercall(worm as *const (), worm, rb(0x51E710)), // Air Strike (EAX=worm)
|
| 369 |
+
11 => fire_worm_vtable_0xe(worm, 0x71), // Scales of Justice
|
| 370 |
+
13 => fire_send_team_message(worm, 0x2B), // Napalm Strike
|
| 371 |
+
14 => fire_mail_mine_mole(worm), // Mail/Mine/Mole (pure Rust)
|
| 372 |
16 => {
|
| 373 |
// Teleport: MOV EAX,[ESI+0x44] (worm state) before check
|
| 374 |
let worm_state = (*worm).state();
|
| 375 |
+
let result = call_fire_usercall_ret(worm_state as *const (), worm, rb(0x516930));
|
| 376 |
if result != 0 {
|
| 377 |
+
call_fire_usercall(result as *const (), worm, rb(0x51EB00));
|
| 378 |
} else {
|
| 379 |
+
fire_worm_vtable_0xe(worm, 0x74);
|
| 380 |
}
|
| 381 |
}
|
| 382 |
+
17 => call_fire_usercall(worm as *const (), worm, rb(0x51E920)), // Freeze (EAX=worm)
|
| 383 |
+
18 => fire_worm_vtable_0xe(worm, 0x72), // Suicide Bomber
|
| 384 |
+
19 => fire_skip_go(worm, entry), // Skip Go (pure Rust)
|
| 385 |
+
20 => fire_surrender(worm), // Surrender (pure Rust)
|
| 386 |
+
21 => fire_select_worm(worm), // Select Worm (pure Rust)
|
| 387 |
+
22 => call_fire_usercall(entry as *const (), worm, rb(0x51EC30)), // Jet Pack (EAX=entry)
|
| 388 |
+
23 => fire_worm_vtable_0xe(worm, 0x78), // Magic Bullet
|
| 389 |
+
24 => call_fire_usercall(entry as *const (), worm, rb(0x51EA60)), // Low Grav (EAX=entry)
|
| 390 |
_ => {}
|
| 391 |
}
|
| 392 |
}
|
|
|
|
| 507 |
unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
|
| 508 |
use openwa_core::engine::ddgame::TeamArenaRef;
|
| 509 |
let ddgame = (*worm).ddgame();
|
| 510 |
+
let version = (*ddgame).version_flag_4;
|
| 511 |
let worm_state = (*worm).state();
|
| 512 |
|
| 513 |
let should_call_vtable = version < 2
|
|
|
|
| 515 |
&& (worm_state == 0x7D || (worm_state == 0x78 && version < 8)));
|
| 516 |
|
| 517 |
if should_call_vtable {
|
| 518 |
+
fire_worm_vtable_0xe(worm, 0x65);
|
| 519 |
}
|
| 520 |
|
| 521 |
fire_send_team_message(worm, 0x28);
|
|
|
|
| 533 |
/// Args: (spawn_data, worm, fire_params, addr).
|
| 534 |
#[unsafe(naked)]
|
| 535 |
unsafe extern "C" fn call_projectile_fire_single(
|
| 536 |
+
_spawn_data: *const WeaponSpawnData, _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _addr: u32,
|
| 537 |
) {
|
| 538 |
core::arch::naked_asm!(
|
| 539 |
"push ebx",
|
|
|
|
| 556 |
/// Args: (this, parent, fire_params, spawn_data, ctor_addr).
|
| 557 |
#[unsafe(naked)]
|
| 558 |
unsafe extern "C" fn call_missile_ctor(
|
| 559 |
+
_this: *mut u8, _parent: *mut u8, _fire_params: *const WeaponFireParams, _spawn_data: *const u8, _addr: u32,
|
| 560 |
) {
|
| 561 |
core::arch::naked_asm!(
|
| 562 |
"push ebx",
|
|
|
|
| 579 |
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET.
|
| 580 |
/// Args: (eax_val, worm, addr). Saves/restores ESI+EDI. Uses EBX to hold addr.
|
| 581 |
#[unsafe(naked)]
|
| 582 |
+
unsafe extern "C" fn call_fire_usercall(_eax: *const (), _worm: *mut CTaskWorm, _addr: u32) {
|
| 583 |
core::arch::naked_asm!(
|
| 584 |
"push ebx",
|
| 585 |
"push esi",
|
|
|
|
| 598 |
|
| 599 |
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET, returns EAX.
|
| 600 |
#[unsafe(naked)]
|
| 601 |
+
unsafe extern "C" fn call_fire_usercall_ret(_eax: *const (), _worm: *mut CTaskWorm, _addr: u32) -> i32 {
|
| 602 |
core::arch::naked_asm!(
|
| 603 |
"push ebx",
|
| 604 |
"push esi",
|
|
|
|
| 618 |
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm) + stdcall(1 param), RET 0x4.
|
| 619 |
#[unsafe(naked)]
|
| 620 |
unsafe extern "C" fn call_fire_usercall_stdcall1(
|
| 621 |
+
_eax: *const (), _worm: *mut CTaskWorm, _param: *const u8, _addr: u32,
|
| 622 |
) {
|
| 623 |
core::arch::naked_asm!(
|
| 624 |
"push ebx",
|
|
|
|
| 641 |
/// Original: `(**(code **)(*worm + 0x38))(msg_id)`.
|
| 642 |
/// Uses naked bridge to avoid LLVM stack tracking issues with push/RET mismatch.
|
| 643 |
#[unsafe(naked)]
|
| 644 |
+
unsafe extern "C" fn fire_worm_vtable_0xe(_worm: *mut CTaskWorm, _msg_id: u32) {
|
| 645 |
core::arch::naked_asm!(
|
| 646 |
"push ebx",
|
| 647 |
"push esi",
|
|
|
|
| 696 |
/// Original: 0x51E0F0. Allocates CTaskMissile (0x40C bytes), looks up
|
| 697 |
/// parent CTaskTurnGame via SharedData, calls the original constructor.
|
| 698 |
unsafe extern "cdecl" fn create_weapon_projectile_impl(
|
| 699 |
+
worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const u8,
|
| 700 |
) {
|
| 701 |
use openwa_core::rebase::rb;
|
| 702 |
use openwa_core::task::{SharedDataTable, Task};
|
|
|
|
| 726 |
// Call original CTaskMissile::Constructor
|
| 727 |
// thiscall(ECX=buffer, parent, fire_params, local_struct), RET 0xC
|
| 728 |
call_missile_ctor(
|
| 729 |
+
buffer,
|
| 730 |
+
parent as *mut u8,
|
| 731 |
+
fire_params,
|
| 732 |
local_struct,
|
| 733 |
rb(va::CTASK_MISSILE_CTOR),
|
| 734 |
);
|
|
|
|
| 770 |
/// e. Write rotated velocity into local spawn data
|
| 771 |
/// f. Call ProjectileFire_Single(loop_counter, fire_params) with EDI=spawn_data
|
| 772 |
unsafe extern "cdecl" fn projectile_fire_impl(
|
| 773 |
+
worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const WeaponSpawnData,
|
| 774 |
) {
|
| 775 |
use openwa_core::rebase::rb;
|
| 776 |
|
| 777 |
+
let params = &*fire_params;
|
| 778 |
+
// collision_radius field is polymorphic — for ProjectileFire it holds the shot count
|
| 779 |
+
let shot_count = params.collision_radius.0;
|
| 780 |
if shot_count <= 0 {
|
| 781 |
return;
|
| 782 |
}
|
| 783 |
|
| 784 |
+
// Copy spawn template from caller's stack buffer
|
| 785 |
+
let mut spawn_data = *local_struct;
|
|
|
|
| 786 |
|
| 787 |
+
// Read template velocity (will be rotated per-shot)
|
| 788 |
+
let template_speed_x = spawn_data.initial_speed_x.0;
|
| 789 |
+
let template_speed_y = spawn_data.initial_speed_y.0;
|
| 790 |
|
| 791 |
// Trig table: sin at SIN_TABLE, cos at SIN_TABLE + 256*4
|
| 792 |
let sin_table = rb(va::SIN_TABLE) as *const i32;
|
|
|
|
| 799 |
let rng = ddgame.advance_rng();
|
| 800 |
|
| 801 |
// Compute spread angle: ((rng_low16 - 0x8000) * spread_param) / 360
|
| 802 |
+
// _fp_04 field is polymorphic — for ProjectileFire it holds spread angle
|
| 803 |
let rng_centered = (rng & 0xFFFF) as i32 - 0x8000;
|
| 804 |
+
let spread_param = params._fp_04;
|
| 805 |
let angle = (rng_centered * spread_param) / 360;
|
| 806 |
|
| 807 |
// Cos/sin table lookup with linear interpolation
|
|
|
|
| 827 |
+ fixed_mul(cos_val, template_speed_y);
|
| 828 |
|
| 829 |
// Write rotated velocity into spawn data
|
| 830 |
+
spawn_data.initial_speed_x = Fixed(speed_x);
|
| 831 |
+
spawn_data.initial_speed_y = Fixed(speed_y);
|
| 832 |
|
| 833 |
// Call ProjectileFire_Single(worm, fire_params) with EDI=&spawn_data
|
| 834 |
call_projectile_fire_single(
|
| 835 |
+
&raw const spawn_data,
|
| 836 |
+
worm,
|
| 837 |
+
fire_params,
|
| 838 |
rb(va::PROJECTILE_FIRE_SINGLE),
|
| 839 |
);
|
| 840 |
}
|
|
|
|
| 870 |
}
|
| 871 |
|
| 872 |
unsafe extern "cdecl" fn create_arrow_impl(
|
| 873 |
+
worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const u8,
|
| 874 |
) {
|
| 875 |
use openwa_core::rebase::rb;
|
| 876 |
use openwa_core::task::{SharedDataTable, Task};
|
|
|
|
| 896 |
core::ptr::write_bytes(buffer, 0, 0x148);
|
| 897 |
|
| 898 |
// CTaskArrow::Constructor — stdcall(this, parent, fire_params, local_struct), RET 0x10
|
| 899 |
+
let ctor: unsafe extern "stdcall" fn(*mut u8, *mut u8, *const WeaponFireParams, *const u8) =
|
| 900 |
core::mem::transmute(rb(va::CTASK_ARROW_CTOR));
|
| 901 |
+
ctor(buffer, parent as *mut u8, fire_params, local_struct);
|
| 902 |
|
| 903 |
let _ = log_line(&format!(
|
| 904 |
"[Weapon] CreateArrow: worm=0x{:08X} arrow=0x{:08X}",
|