paavohtl Claude Opus 4.6 (1M context) commited on
Commit
d43153a
Β·
1 Parent(s): e88ffaf

feat: add CTaskTeamVTable and typed handle_message dispatch

Browse files

Define CTaskTeamVTable (12 slots, vtable at 0x669EE4) via #[vtable] macro
with typed signatures for handle_message (slot 2), write_replay_state,
free, get_entity_data, and process_frame.

Add CTaskTeam::vtable() accessor and CTaskTeam::handle_message() method
that replaces raw vtable pointer arithmetic in weapon fire handlers.

Extract lookup_team_task() helper for SharedData CTaskTeam lookup.

Note: bind_CTaskTeamVTable! not used because CTask.vtable is *mut u8
(FieldRegistry derive doesn't support generic structs, so CTask<V>
would require manual impls across the entire task hierarchy).

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

crates/openwa-core/src/task/mod.rs CHANGED
@@ -23,6 +23,6 @@ pub use mine_oil_drum::{CTaskMine, CTaskOilDrum};
23
  pub use missile::{CTaskMissile, MissileType};
24
  pub use overlays::{BungeeTrailTask, WeaponAimTask};
25
  pub use supply_crate::CTaskCrate;
26
- pub use team::CTaskTeam;
27
  pub use turn_game::{CTaskTurnGame, TurnGameCtx};
28
  pub use worm::{CTaskWorm, CTaskWormVTable};
 
23
  pub use missile::{CTaskMissile, MissileType};
24
  pub use overlays::{BungeeTrailTask, WeaponAimTask};
25
  pub use supply_crate::CTaskCrate;
26
+ pub use team::{CTaskTeam, CTaskTeamVTable};
27
  pub use turn_game::{CTaskTurnGame, TurnGameCtx};
28
  pub use worm::{CTaskWorm, CTaskWormVTable};
crates/openwa-core/src/task/team.rs CHANGED
@@ -10,6 +10,35 @@ crate::define_addresses! {
10
  }
11
  }
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  /// Per-team state-tracker task β€” one instance per team, child of CTaskTurnGame.
14
  ///
15
  /// Tracks per-team data: which team number it represents, how many worms were
@@ -82,3 +111,28 @@ pub struct CTaskTeam {
82
  }
83
 
84
  const _: () = assert!(core::mem::size_of::<CTaskTeam>() == 0x460);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  }
11
  }
12
 
13
+ /// CTaskTeam vtable β€” 12 slots. Extends CTask base (8 slots) with team-specific behavior.
14
+ ///
15
+ /// Vtable at Ghidra 0x669EE4. Slot 2 (HandleMessage) is the main team message
16
+ /// dispatcher (1701 instructions, handles weapon fire, surrender, worm selection, etc.)
17
+ #[openwa_core::vtable(size = 12, va = 0x0066_9EE4, class = "CTaskTeam")]
18
+ pub struct CTaskTeamVTable {
19
+ /// WriteReplayState β€” serializes team state to replay stream.
20
+ /// thiscall + 1 stack param (stream), RET 0x4.
21
+ #[slot(0)]
22
+ pub write_replay_state: fn(this: *mut CTaskTeam, stream: *mut u8),
23
+ /// Free β€” destructor. thiscall + 1 stack param (flags), RET 0x4.
24
+ #[slot(1)]
25
+ pub free: fn(this: *mut CTaskTeam, flags: u8) -> *mut CTaskTeam,
26
+ /// HandleMessage β€” processes messages sent to this team (weapon fire,
27
+ /// surrender, worm selection, napalm strike, etc.)
28
+ /// thiscall + 4 stack params, RET 0x10.
29
+ #[slot(2)]
30
+ pub handle_message: fn(this: *mut CTaskTeam, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
31
+ /// GetEntityData β€” returns team data by query code.
32
+ /// thiscall + 3 stack params, RET 0xC.
33
+ #[slot(3)]
34
+ pub get_entity_data: fn(this: *mut CTaskTeam, query: u32, param: u32, out: *mut u32) -> u32,
35
+ // Slots 4-6: inherited CTask methods
36
+ /// ProcessFrame β€” per-frame team update.
37
+ /// thiscall + 1 stack param (flags), RET 0x4.
38
+ #[slot(7)]
39
+ pub process_frame: fn(this: *mut CTaskTeam, flags: u32),
40
+ }
41
+
42
  /// Per-team state-tracker task β€” one instance per team, child of CTaskTurnGame.
43
  ///
44
  /// Tracks per-team data: which team number it represents, how many worms were
 
111
  }
112
 
113
  const _: () = assert!(core::mem::size_of::<CTaskTeam>() == 0x460);
114
+
115
+ impl CTaskTeam {
116
+ /// Get the typed vtable pointer.
117
+ pub fn vtable(&self) -> &CTaskTeamVTable {
118
+ unsafe { &*(self.base.vtable as *const CTaskTeamVTable) }
119
+ }
120
+
121
+ /// Call HandleMessage (vtable slot 2) on this team task.
122
+ ///
123
+ /// This is the main team message dispatcher β€” handles weapon fire results,
124
+ /// surrender, worm selection, napalm strike, and many other team-level events.
125
+ ///
126
+ /// # Safety
127
+ /// `sender` must be a valid CTask pointer. `data` must point to `size` bytes.
128
+ pub unsafe fn handle_message(
129
+ &mut self,
130
+ sender: *mut CTask,
131
+ msg_type: u32,
132
+ size: u32,
133
+ data: *const u8,
134
+ ) {
135
+ let vt = self.vtable();
136
+ (vt.handle_message)(self as *mut Self, sender, msg_type, size, data);
137
+ }
138
+ }
crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -402,62 +402,62 @@ unsafe fn fire_weapon_special(
402
 
403
  // ── Pure Rust fire handlers (no bridge needed) ──────────────
404
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  /// Send a message to the worm's CTaskTeam entity via SharedData lookup.
406
  ///
407
  /// Pattern shared by Napalm Strike (msg 0x2B), Surrender (msg 0x29), etc.
408
- /// Looks up entity with key (0, 0x14=CTaskTeam) in worm's shared data table,
409
- /// then calls entity->vtable[2] (HandleMessage) with the given message type.
410
  ///
411
  /// The 0x40C-byte local buffer is passed as data pointer; team_index is written
412
- /// at buf+0x08 to identify which team fired.
413
  unsafe fn fire_send_team_message(worm: *mut CTaskWorm, msg_type: u32) {
414
- use openwa_core::task::SharedDataTable;
415
-
416
- let task = &(*worm).base.base; // CTask base
417
- let table = SharedDataTable::from_task(task);
418
-
419
- // Lookup CTaskTeam (key_esi=0, key_edi=0x14)
420
- let team_entity = table.lookup(0, 0x14);
421
- if team_entity.is_null() {
422
  return;
423
  }
424
 
425
- // Prepare local buffer (0x40C bytes).
426
- // Original: buffer[0] = worm->team_index (from worm+0xFC), rest is stack garbage.
427
- // We zero-fill for safety, then set buffer[0] = team_index.
428
  let mut buf = [0u8; 0x40C];
429
  let team_index = (*worm).team_index;
430
  buf[0..4].copy_from_slice(&team_index.to_ne_bytes());
431
 
432
- // Call entity->vtable[2] = HandleMessage(sender=worm, msg, size=4, data=&buf)
433
- let vtable = *(team_entity as *const u32);
434
- let handle_message: unsafe extern "thiscall" fn(*mut u8, *mut CTaskWorm, u32, u32, *const u8) =
435
- core::mem::transmute(*(vtable as *const u32).add(2));
436
- handle_message(team_entity, worm, msg_type, 4, buf.as_ptr());
 
437
  }
438
 
439
  /// Select Worm (subtype 21) β€” pure Rust replacement for 0x51EBE0.
440
  ///
441
  /// Sends message 0x5D to CTaskTeam with buf = [8, team_index, ...].
442
  unsafe fn fire_select_worm(worm: *mut CTaskWorm) {
443
- use openwa_core::task::SharedDataTable;
444
-
445
- let task = &(*worm).base.base;
446
- let table = SharedDataTable::from_task(task);
447
- let team_entity = table.lookup(0, 0x14);
448
- if team_entity.is_null() {
449
  return;
450
  }
451
 
452
  let mut buf = [0u8; 0x40C];
453
- // buf[0..4] = 8 (constant), buf[4..8] = team_index
454
  buf[0..4].copy_from_slice(&8u32.to_ne_bytes());
455
  buf[4..8].copy_from_slice(&(*worm).team_index.to_ne_bytes());
456
 
457
- let vtable = *(team_entity as *const u32);
458
- let handle_message: unsafe extern "thiscall" fn(*mut u8, *mut CTaskWorm, u32, u32, *const u8) =
459
- core::mem::transmute(*(vtable as *const u32).add(2));
460
- handle_message(team_entity, worm, 0x5D, 0x408, buf.as_ptr());
 
 
461
  }
462
 
463
  /// Skip Go (subtype 19) β€” pure Rust replacement for 0x51E8C0.
 
402
 
403
  // ── Pure Rust fire handlers (no bridge needed) ──────────────
404
 
405
+ /// Look up the CTaskTeam entity for a worm via SharedData.
406
+ ///
407
+ /// Returns null if not found.
408
+ unsafe fn lookup_team_task(worm: *const CTaskWorm) -> *mut openwa_core::task::CTaskTeam {
409
+ use openwa_core::task::SharedDataTable;
410
+
411
+ let task = &(*worm).base.base;
412
+ let table = SharedDataTable::from_task(task);
413
+ // CTaskTeam is registered with key (0, 0x14) in SharedData
414
+ table.lookup(0, 0x14) as *mut openwa_core::task::CTaskTeam
415
+ }
416
+
417
  /// Send a message to the worm's CTaskTeam entity via SharedData lookup.
418
  ///
419
  /// Pattern shared by Napalm Strike (msg 0x2B), Surrender (msg 0x29), etc.
420
+ /// Looks up CTaskTeam, then calls HandleMessage (vtable slot 2).
 
421
  ///
422
  /// The 0x40C-byte local buffer is passed as data pointer; team_index is written
423
+ /// at buf+0x00 to identify which team fired.
424
  unsafe fn fire_send_team_message(worm: *mut CTaskWorm, msg_type: u32) {
425
+ let team = lookup_team_task(worm);
426
+ if team.is_null() {
 
 
 
 
 
 
427
  return;
428
  }
429
 
 
 
 
430
  let mut buf = [0u8; 0x40C];
431
  let team_index = (*worm).team_index;
432
  buf[0..4].copy_from_slice(&team_index.to_ne_bytes());
433
 
434
+ (*team).handle_message(
435
+ &raw mut (*worm).base.base,
436
+ msg_type,
437
+ 4,
438
+ buf.as_ptr(),
439
+ );
440
  }
441
 
442
  /// Select Worm (subtype 21) β€” pure Rust replacement for 0x51EBE0.
443
  ///
444
  /// Sends message 0x5D to CTaskTeam with buf = [8, team_index, ...].
445
  unsafe fn fire_select_worm(worm: *mut CTaskWorm) {
446
+ let team = lookup_team_task(worm);
447
+ if team.is_null() {
 
 
 
 
448
  return;
449
  }
450
 
451
  let mut buf = [0u8; 0x40C];
 
452
  buf[0..4].copy_from_slice(&8u32.to_ne_bytes());
453
  buf[4..8].copy_from_slice(&(*worm).team_index.to_ne_bytes());
454
 
455
+ (*team).handle_message(
456
+ &raw mut (*worm).base.base,
457
+ 0x5D,
458
+ 0x408,
459
+ buf.as_ptr(),
460
+ );
461
  }
462
 
463
  /// Skip Go (subtype 19) β€” pure Rust replacement for 0x51E8C0.