paavohtl Claude Opus 4.6 (1M context) commited on
Commit
8a9e72a
Β·
1 Parent(s): 37cd0fa

feat: port Skip Go to pure Rust, type fire_weapon_special params

Browse files

Replace Skip Go (0x51E8C0) bridge with pure Rust implementation β€”
reads team arena flags via typed DDGame/GameInfo/CTaskWorm fields,
toggles bit based on weapon entry's fire_params and game_version.

Type fire_weapon_special params as *mut CTaskWorm and *const WeaponEntry.
Use (*worm).state() accessor for Teleport check instead of raw +0x44.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -232,7 +232,7 @@ unsafe extern "cdecl" fn fire_weapon_impl(
232
  }
233
  4 => {
234
  let subtype_38_ptr = &raw const (*entry).fire_subtype_38 as u32;
235
- fire_weapon_special(subtype_34, subtype_38_ptr, w, local_struct, entry);
236
  }
237
  _ => {}
238
  }
@@ -349,47 +349,80 @@ unsafe extern "C" fn call_fire_stdcall2(
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, params_38: u32, worm: u32, local_struct: u32,
353
  entry: *const WeaponEntry,
354
  ) {
355
  use openwa_core::rebase::rb;
356
- let entry_u32 = entry as u32;
 
357
 
358
  match subtype {
359
- 1 => fire_worm_vtable_0xe(worm, 0x6C), // Blowtorch
360
- 2 => call_fire_usercall(entry_u32, worm, rb(0x51E3E0)), // Drill (EAX=entry)
361
- 3 => call_fire_stdcall3(worm, params_38 as *const WeaponFireParams, local_struct, rb(0x51E350)), // Girder
362
- 4 => fire_worm_vtable_0xe(worm, 0x6D), // Baseball Bat
363
- 5 => fire_worm_vtable_0xe(worm, 0x75), // Fire Punch
364
- 6 => fire_worm_vtable_0xe(worm, 0x70), // Dragon Ball
365
- 8 => fire_worm_vtable_0xe(worm, 0x6E), // Kamikaze
366
- 9 => call_fire_usercall_stdcall1(entry_u32, worm, local_struct, rb(0x51E480)), // Prod (EAX=entry)
367
- 10 => call_fire_usercall(worm, worm, rb(0x51E710)), // Air Strike (EAX=worm)
368
- 11 => fire_worm_vtable_0xe(worm, 0x71), // Scales of Justice
369
- 13 => call_fire_usercall(entry_u32, worm, rb(0x51E5C0)), // Napalm (EAX=entry)
370
- 14 => call_fire_usercall(worm, worm, rb(0x51E670)), // Mail/Mine/Mole (EAX=worm)
371
  16 => {
372
- // Teleport: MOV EAX,[ESI+0x44] (worm state byte) before check
373
- let worm_state = *((worm as *const u8).add(0x44) as *const u32);
374
- let result = call_fire_usercall_ret(worm_state, worm, rb(0x516930));
375
  if result != 0 {
376
- call_fire_usercall(result as u32, worm, rb(0x51EB00)); // EAX=check result
377
  } else {
378
- fire_worm_vtable_0xe(worm, 0x74);
379
  }
380
  }
381
- 17 => call_fire_usercall(worm, worm, rb(0x51E920)), // Freeze (EAX=worm)
382
- 18 => fire_worm_vtable_0xe(worm, 0x72), // Suicide Bomber
383
- 19 => call_fire_usercall(entry_u32, worm, rb(0x51E8C0)), // Skip Go (EAX=entry)
384
- 20 => call_fire_usercall(worm, worm, rb(0x51E600)), // Surrender (EAX=worm)
385
- 21 => call_fire_usercall(entry_u32, worm, rb(0x51EBE0)), // Select Worm (EAX=entry)
386
- 22 => call_fire_usercall(entry_u32, worm, rb(0x51EC30)), // Jet Pack (EAX=entry)
387
- 23 => fire_worm_vtable_0xe(worm, 0x78), // Magic Bullet
388
- 24 => call_fire_usercall(entry_u32, worm, rb(0x51EA60)), // Low Grav (EAX=entry)
389
  _ => {}
390
  }
391
  }
392
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  /// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET.
394
  /// Args: (eax_val, worm, addr). Saves/restores ESI+EDI. Uses EBX to hold addr.
395
  #[unsafe(naked)]
 
232
  }
233
  4 => {
234
  let subtype_38_ptr = &raw const (*entry).fire_subtype_38 as u32;
235
+ fire_weapon_special(subtype_34, subtype_38_ptr, worm, local_struct, entry);
236
  }
237
  _ => {}
238
  }
 
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, params_38: u32, worm: *mut CTaskWorm, local_struct: u32,
353
  entry: *const WeaponEntry,
354
  ) {
355
  use openwa_core::rebase::rb;
356
+ let w = worm as u32;
357
+ let e = entry as u32;
358
 
359
  match subtype {
360
+ 1 => fire_worm_vtable_0xe(w, 0x6C), // Blowtorch
361
+ 2 => call_fire_usercall(e, w, rb(0x51E3E0)), // Drill (EAX=entry)
362
+ 3 => call_fire_stdcall3(w, params_38 as *const WeaponFireParams, local_struct, rb(0x51E350)), // Girder
363
+ 4 => fire_worm_vtable_0xe(w, 0x6D), // Baseball Bat
364
+ 5 => fire_worm_vtable_0xe(w, 0x75), // Fire Punch
365
+ 6 => fire_worm_vtable_0xe(w, 0x70), // Dragon Ball
366
+ 8 => fire_worm_vtable_0xe(w, 0x6E), // Kamikaze
367
+ 9 => call_fire_usercall_stdcall1(e, w, local_struct, rb(0x51E480)), // Prod (EAX=entry)
368
+ 10 => call_fire_usercall(w, w, rb(0x51E710)), // Air Strike (EAX=worm)
369
+ 11 => fire_worm_vtable_0xe(w, 0x71), // Scales of Justice
370
+ 13 => call_fire_usercall(e, w, rb(0x51E5C0)), // Napalm (EAX=entry)
371
+ 14 => call_fire_usercall(w, w, rb(0x51E670)), // Mail/Mine/Mole (EAX=worm)
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, w, rb(0x516930));
376
  if result != 0 {
377
+ call_fire_usercall(result as u32, w, rb(0x51EB00));
378
  } else {
379
+ fire_worm_vtable_0xe(w, 0x74);
380
  }
381
  }
382
+ 17 => call_fire_usercall(w, w, rb(0x51E920)), // Freeze (EAX=worm)
383
+ 18 => fire_worm_vtable_0xe(w, 0x72), // Suicide Bomber
384
+ 19 => fire_skip_go(worm, entry), // Skip Go (pure Rust)
385
+ 20 => call_fire_usercall(w, w, rb(0x51E600)), // Surrender (EAX=worm)
386
+ 21 => call_fire_usercall(e, w, rb(0x51EBE0)), // Select Worm (EAX=entry)
387
+ 22 => call_fire_usercall(e, w, rb(0x51EC30)), // Jet Pack (EAX=entry)
388
+ 23 => fire_worm_vtable_0xe(w, 0x78), // Magic Bullet
389
+ 24 => call_fire_usercall(e, w, rb(0x51EA60)), // Low Grav (EAX=entry)
390
  _ => {}
391
  }
392
  }
393
 
394
+ // ── Pure Rust fire handlers (no bridge needed) ──────────────
395
+
396
+ /// Skip Go (subtype 19) β€” pure Rust replacement for 0x51E8C0.
397
+ ///
398
+ /// Toggles a bit in the team's arena state flags (DDGame+0x4628 + team*0x51C).
399
+ /// Bit position from weapon entry's fire_params field.
400
+ /// If game_version > 0x1C and bit already set: clears it. Otherwise: sets it.
401
+ unsafe fn fire_skip_go(worm: *const CTaskWorm, entry: *const WeaponEntry) {
402
+ use openwa_core::engine::DDGame;
403
+ use openwa_core::engine::GameInfo;
404
+
405
+ let ddgame = (*worm).base.base.ddgame as *const DDGame;
406
+ let game_info = (*ddgame).game_info as *const GameInfo;
407
+ let game_version = (*game_info).game_version;
408
+ let team_index = (*worm).team_index as usize;
409
+
410
+ let bit_index = (*entry).fire_params._data[0] & 0x1F;
411
+ let bit = 1u32 << bit_index;
412
+
413
+ // Team arena flags at DDGame+0x4628 + team_index * 0x51C
414
+ let flags_ptr = (ddgame as *mut u8).add(0x4628 + team_index * 0x51C) as *mut u32;
415
+ let flags = *flags_ptr;
416
+
417
+ if game_version > 0x1C && (flags & bit) != 0 {
418
+ *flags_ptr = flags & !bit; // clear
419
+ } else {
420
+ *flags_ptr = flags | bit; // set
421
+ }
422
+ }
423
+
424
+ // ── Naked asm bridges ───────────────────────────────────────
425
+
426
  /// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET.
427
  /// Args: (eax_val, worm, addr). Saves/restores ESI+EDI. Uses EBX to hold addr.
428
  #[unsafe(naked)]