paavohtl Claude Opus 4.6 (1M context) commited on
Commit
0706c93
Β·
1 Parent(s): 9d278f7

refactor: rename CGameTask._unknown_30 to subclass_data, add CTaskWorm weapon accessors

Browse files

Rename the polymorphic 84-byte region at CGameTask+0x30 to
subclass_data with documentation of per-subclass usage (worm weapon
state, filter subscriptions, missile params, team vtable, etc.).

Add CTaskWorm weapon fire accessor methods:
- weapon_fire_type(), weapon_fire_subtype_34/38()
- weapon_fire_complete() / weapon_fire_complete_mut()
- weapon_params_ptr(), weapon_params_34_ptr(), weapon_params_38_ptr()
- weapon_self_ptr field at +0x36C

Make task::worm and task::game_task modules public.

Update FireWeapon impl to use worm_eax (first cdecl param = EAX) for
field reads β€” must match original register source for determinism.

Verified: headless PASS (byte-identical), headful 133+3 / 0 FAIL.

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

crates/openwa-core/src/task/game_task.rs CHANGED
@@ -13,8 +13,15 @@ use crate::fixed::Fixed;
13
  pub struct CGameTask {
14
  /// 0x00-0x2F: Base CTask fields
15
  pub base: CTask,
16
- /// 0x30-0x83: Unknown gameplay fields (84 bytes)
17
- pub _unknown_30: [u8; 0x54],
 
 
 
 
 
 
 
18
  /// 0x84: X position in fixed-point
19
  pub pos_x: Fixed,
20
  /// 0x88: Y position in fixed-point
 
13
  pub struct CGameTask {
14
  /// 0x00-0x2F: Base CTask fields
15
  pub base: CTask,
16
+ /// 0x30-0x83: Subclass-specific data (84 bytes). Each CGameTask derivative
17
+ /// uses this region differently:
18
+ /// - CTaskWorm: weapon fire state (+0x30 type, +0x34/+0x38 subtypes, +0x3C flag)
19
+ /// - CTaskFilter: boolean message subscription table (+0x30..+0x93)
20
+ /// - CTaskMissile: spawn/physics parameters
21
+ /// - CTaskTeam: secondary vtable pointer (+0x30)
22
+ /// - CTaskCloud: parallax scroll depth (+0x30)
23
+ /// Access via subclass accessor methods, not directly.
24
+ pub subclass_data: [u8; 0x54],
25
  /// 0x84: X position in fixed-point
26
  pub pos_x: Fixed,
27
  /// 0x88: Y position in fixed-point
crates/openwa-core/src/task/mod.rs CHANGED
@@ -10,7 +10,7 @@ mod overlays;
10
  mod supply_crate;
11
  mod team;
12
  mod turn_game;
13
- mod worm;
14
 
15
  pub use base::{CTask, CTaskBfsIter, SharedDataIter, SharedDataNode, SharedDataTable};
16
  pub use cloud::CTaskCloud;
 
10
  mod supply_crate;
11
  mod team;
12
  mod turn_game;
13
+ pub mod worm;
14
 
15
  pub use base::{CTask, CTaskBfsIter, SharedDataIter, SharedDataNode, SharedDataTable};
16
  pub use cloud::CTaskCloud;
crates/openwa-core/src/task/worm.rs CHANGED
@@ -75,7 +75,7 @@ const _: () = assert!(core::mem::size_of::<CTaskWormVTable>() == 20 * 4);
75
  /// Vtable at 0x6644C8. Class type byte: 0x12.
76
  ///
77
  /// # Important fields in the CGameTask base
78
- /// The worm state field lives at **offset +0x44** (inside `base._unknown_30`).
79
  /// Use [`CTaskWorm::state`] to read it without pointer arithmetic.
80
  ///
81
  /// Source: Ghidra decompilation of 0x50BFB0, vtable analysis of 0x6644C8,
@@ -175,16 +175,59 @@ pub struct CTaskWorm {
175
  const _: () = assert!(core::mem::size_of::<CTaskWorm>() == 0x3FC);
176
 
177
  impl CTaskWorm {
178
- /// Returns the worm's current state code (lives at offset +0x44, inside the
179
- /// CGameTask base's `_unknown_40` padding region).
180
  ///
181
  /// Known states: `0x65`=idle, `0x67`=active turn, `0x7F`=drowning,
182
  /// `0x80`=hurt, `0x81`/`0x86`=dead, `0x87`=dead variant, `0x8B`=unknown.
183
  pub fn state(&self) -> u32 {
184
- // SAFETY: offset 0x44 is within CGameTask._unknown_40 (0x40..0x84).
185
  unsafe { *((self as *const CTaskWorm as *const u8).add(0x44) as *const u32) }
186
  }
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  /// Returns a reference to the vtable.
189
  ///
190
  /// # Safety
 
75
  /// Vtable at 0x6644C8. Class type byte: 0x12.
76
  ///
77
  /// # Important fields in the CGameTask base
78
+ /// The worm state field lives at **offset +0x44** (inside `base.subclass_data`).
79
  /// Use [`CTaskWorm::state`] to read it without pointer arithmetic.
80
  ///
81
  /// Source: Ghidra decompilation of 0x50BFB0, vtable analysis of 0x6644C8,
 
175
  const _: () = assert!(core::mem::size_of::<CTaskWorm>() == 0x3FC);
176
 
177
  impl CTaskWorm {
178
+ /// Returns the worm's current state code (lives at offset +0x44, inside
179
+ /// `base.subclass_data`).
180
  ///
181
  /// Known states: `0x65`=idle, `0x67`=active turn, `0x7F`=drowning,
182
  /// `0x80`=hurt, `0x81`/`0x86`=dead, `0x87`=dead variant, `0x8B`=unknown.
183
  pub fn state(&self) -> u32 {
 
184
  unsafe { *((self as *const CTaskWorm as *const u8).add(0x44) as *const u32) }
185
  }
186
 
187
+ // ── Weapon fire state accessors (offsets +0x30..+0x3C in subclass_data) ──
188
+
189
+ /// Weapon fire type (1=projectile, 2=rope, 3=grenade, 4=special).
190
+ /// Offset +0x30 in CGameTask.subclass_data.
191
+ pub fn weapon_fire_type(&self) -> i32 {
192
+ unsafe { *(self.base.subclass_data.as_ptr().add(0) as *const i32) }
193
+ }
194
+
195
+ /// Weapon fire subtype for types 3 and 4. Offset +0x34.
196
+ pub fn weapon_fire_subtype_34(&self) -> i32 {
197
+ unsafe { *(self.base.subclass_data.as_ptr().add(4) as *const i32) }
198
+ }
199
+
200
+ /// Weapon fire subtype for types 1 and 2. Offset +0x38.
201
+ pub fn weapon_fire_subtype_38(&self) -> i32 {
202
+ unsafe { *(self.base.subclass_data.as_ptr().add(8) as *const i32) }
203
+ }
204
+
205
+ /// Weapon fire completion flag. Offset +0x3C.
206
+ /// Set to 0 before dispatch, 1 after.
207
+ pub fn weapon_fire_complete(&self) -> i32 {
208
+ unsafe { *(self.base.subclass_data.as_ptr().add(12) as *const i32) }
209
+ }
210
+
211
+ /// Mutable pointer to weapon_fire_complete for setting the flag.
212
+ pub fn weapon_fire_complete_mut(&mut self) -> &mut i32 {
213
+ unsafe { &mut *(self.base.subclass_data.as_mut_ptr().add(12) as *mut i32) }
214
+ }
215
+
216
+ /// Address of weapon_fire_complete field (passed as params base to fire handlers).
217
+ pub fn weapon_params_ptr(&self) -> u32 {
218
+ self as *const _ as u32 + 0x3C
219
+ }
220
+
221
+ /// Address of weapon_fire_subtype_34 field (params for GrenadeMortar).
222
+ pub fn weapon_params_34_ptr(&self) -> u32 {
223
+ self as *const _ as u32 + 0x34
224
+ }
225
+
226
+ /// Address of weapon_fire_subtype_38 field (params for type-4 specials).
227
+ pub fn weapon_params_38_ptr(&self) -> u32 {
228
+ self as *const _ as u32 + 0x38
229
+ }
230
+
231
  /// Returns a reference to the vtable.
232
  ///
233
  /// # Safety
crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -179,28 +179,38 @@ unsafe extern "C" fn trampoline_fire_weapon() {
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 {
@@ -216,18 +226,13 @@ unsafe extern "cdecl" fn fire_weapon_impl(weapon_ctx: u32, local_struct: u32, wo
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 ────────────────────────────────────
 
179
  }
180
 
181
  /// Rust implementation of FireWeapon dispatch.
182
+ /// EAX = worm self-pointer (== stack param), ECX = local_struct from WeaponRelease.
183
+ unsafe extern "cdecl" fn fire_weapon_impl(worm_eax: u32, local_struct: u32, worm: u32) {
184
+ use openwa_core::rebase::rb;
185
+
186
+ // EAX (worm_eax) = *(CTaskWorm+0x36C), stack param (worm) = CTaskWorm*.
187
+ // Check if they're actually the same β€” they might not be!
188
+ if worm_eax != worm {
189
+ let _ = log_line(&format!(
190
+ "[Weapon] WARNING: EAX={:#x} != worm={:#x} (delta={})",
191
+ worm_eax, worm, worm_eax.wrapping_sub(worm) as i32
192
+ ));
193
+ }
194
+
195
+ // Use worm_eax (original EAX) for field reads β€” this is what the original code uses
196
+ let ctx = worm_eax as *const u8;
197
  let weapon_type = *(ctx.add(0x30) as *const i32);
198
  let subtype_34 = *(ctx.add(0x34) as *const i32);
199
  let subtype_38 = *(ctx.add(0x38) as *const i32);
200
+ let params = worm_eax.wrapping_add(0x3C);
 
201
 
202
  // Log weapon fire
203
+ let weapon_id = *((worm as *const u8).add(0x170) as *const u32);
204
  let weapon_name = Weapon::try_from(weapon_id)
205
+ .map(|wp| format!("{:?}", wp))
206
  .unwrap_or_else(|id| format!("Unknown({})", id));
207
  let _ = log_line(&format!(
208
  "[Weapon] FireWeapon: {} (id={}) type={} sub34={} sub38={}",
209
  weapon_name, weapon_id, weapon_type, subtype_34, subtype_38
210
  ));
211
 
212
+ // Clear completion flag before dispatch
213
+ core::ptr::write_volatile((worm + 0x3C) as *mut i32, 0);
 
 
214
 
215
  match weapon_type {
216
  1 => match subtype_38 {
 
226
  3 => call_fire_stdcall3(worm, params, local_struct, rb(0x51E240)), // RopeType3
227
  _ => {}
228
  },
229
+ 3 => call_fire_stdcall3(worm, worm_eax.wrapping_add(0x34), local_struct, rb(0x51E2C0)),
230
+ 4 => fire_weapon_special(subtype_34, worm_eax.wrapping_add(0x38), worm, local_struct),
 
 
 
 
 
 
231
  _ => {}
232
  }
233
 
234
+ // Set completion flag after dispatch
235
+ core::ptr::write_volatile((worm + 0x3C) as *mut i32, 1);
236
  }
237
 
238
  // ── Sub-function bridges ────────────────────────────────────
crates/openwa-wormkit/src/validation/mod.rs CHANGED
@@ -1529,7 +1529,7 @@ unsafe fn dump_missile_raw(ptr: u32) {
1529
  let base = ptr as *const u32;
1530
  let sections: &[(usize, usize, &str)] = &[
1531
  (0x000, 0x030, "CTask base"),
1532
- (0x030, 0x084, "CGameTask _unknown_30"),
1533
  (0x084, 0x098, "CGameTask pos/speed (0x84–0x97)"),
1534
  (0x098, 0x0FC, "CGameTask _unknown_98"),
1535
  (0x0FC, 0x130, "CTaskMissile 0xFC–0x12F"),
 
1529
  let base = ptr as *const u32;
1530
  let sections: &[(usize, usize, &str)] = &[
1531
  (0x000, 0x030, "CTask base"),
1532
+ (0x030, 0x084, "CGameTask subclass_data"),
1533
  (0x084, 0x098, "CGameTask pos/speed (0x84–0x97)"),
1534
  (0x098, 0x0FC, "CGameTask _unknown_98"),
1535
  (0x0FC, 0x130, "CTaskMissile 0xFC–0x12F"),