paavohtl Claude Opus 4.6 commited on
Commit
e007c5c
·
1 Parent(s): beab230

fix(types): correct CTask struct layout from Ghidra decompilation

Browse files

Expanded opaque padding into named fields based on cross-referencing
Ghidra decompilation of CTask::Constructor (0x5625A0) and its helper
(0x562520) with wkJellyWorm CTask.h. Key findings:
- 3rd constructor param is DDGame pointer (stored at 0x2C), not class_type
- Children list fields (0x08-0x18) now individually named
- shared_data (0x24) and owns_shared_data (0x28) identified
- 82/82 static validation checks pass (up from 78)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

crates/openwa-harness/src/lib.rs CHANGED
@@ -551,8 +551,8 @@ mod tests {
551
  eprintln!(" Security cookie initialized");
552
  }
553
 
554
- // CTask::Constructor is __stdcall: (this, parent, class_type) -> this
555
- type CTaskCtorFn = unsafe extern "stdcall" fn(this: *mut u8, parent: u32, class_type: u32) -> *mut u8;
556
 
557
  unsafe {
558
  let ctor: CTaskCtorFn = img.func_ptr(va::CTASK_CONSTRUCTOR);
@@ -576,9 +576,9 @@ mod tests {
576
  va::CTASK_VTABLE
577
  );
578
 
579
- // Verify class_type at offset 0x2C
580
- let stored_class = *((task_ptr as usize + 0x2C) as *const u32);
581
- assert_eq!(stored_class, 0, "class_type should be 0");
582
 
583
  // Verify field at 0x08 is 0x10
584
  let field_08 = *((task_ptr as usize + 0x08) as *const u32);
@@ -591,7 +591,7 @@ mod tests {
591
  eprintln!("CTask::Constructor succeeded!");
592
  eprintln!(" vtable = 0x{vtable_ghidra:08X}");
593
  eprintln!(" children = 0x{children:08X}");
594
- eprintln!(" class_type = {stored_class}");
595
  }
596
  }
597
  }
 
551
  eprintln!(" Security cookie initialized");
552
  }
553
 
554
+ // CTask::Constructor is __stdcall: (this, parent, ddgame) -> this
555
+ type CTaskCtorFn = unsafe extern "stdcall" fn(this: *mut u8, parent: u32, ddgame: u32) -> *mut u8;
556
 
557
  unsafe {
558
  let ctor: CTaskCtorFn = img.func_ptr(va::CTASK_CONSTRUCTOR);
 
576
  va::CTASK_VTABLE
577
  );
578
 
579
+ // Verify ddgame pointer at offset 0x2C (3rd ctor param, we passed 0)
580
+ let stored_ddgame = *((task_ptr as usize + 0x2C) as *const u32);
581
+ assert_eq!(stored_ddgame, 0, "ddgame should be 0 (we passed 0)");
582
 
583
  // Verify field at 0x08 is 0x10
584
  let field_08 = *((task_ptr as usize + 0x08) as *const u32);
 
591
  eprintln!("CTask::Constructor succeeded!");
592
  eprintln!(" vtable = 0x{vtable_ghidra:08X}");
593
  eprintln!(" children = 0x{children:08X}");
594
+ eprintln!(" ddgame = {stored_ddgame}");
595
  }
596
  }
597
  }
crates/openwa-types/src/task.rs CHANGED
@@ -9,9 +9,7 @@ pub type Ptr32 = u32;
9
  /// All game objects inherit from CTask. Tasks form a tree via parent/children
10
  /// pointers and communicate through the TaskMessage system.
11
  ///
12
- /// PARTIAL: Many fields between known offsets are unknown.
13
- ///
14
- /// Source: wkJellyWorm CTask.h
15
  ///
16
  /// Vtable at 0x669F8C (8 methods):
17
  /// 0x00: 0x562710 vtable0 (init?)
@@ -28,15 +26,28 @@ pub struct CTask {
28
  pub vtable: Ptr32,
29
  /// 0x04: Parent task in the hierarchy
30
  pub parent: Ptr32,
31
- /// 0x08: Unknown (set to 0x10 in constructor — possibly capacity/flags)
32
- pub _unknown_08: u32,
33
- /// 0x0C-0x1F: Children task list (CList<CTask*>)
34
- /// Internal structure: max_size(4), unk4(4), size(4), data_ptr(4), hash_list(4)
35
- pub _children_raw: [u8; 20],
36
- /// 0x20: Task classification type
 
 
 
 
 
 
 
 
37
  pub class_type: ClassType,
38
- /// 0x24-0x2F: Unknown padding to 0x30
39
- pub _unknown_24: [u8; 12],
 
 
 
 
 
40
  }
41
 
42
  const _: () = assert!(core::mem::size_of::<CTask>() == 0x30);
 
9
  /// All game objects inherit from CTask. Tasks form a tree via parent/children
10
  /// pointers and communicate through the TaskMessage system.
11
  ///
12
+ /// Source: wkJellyWorm CTask.h, Ghidra decompilation of 0x5625A0 + 0x562520
 
 
13
  ///
14
  /// Vtable at 0x669F8C (8 methods):
15
  /// 0x00: 0x562710 vtable0 (init?)
 
26
  pub vtable: Ptr32,
27
  /// 0x04: Parent task in the hierarchy
28
  pub parent: Ptr32,
29
+ /// 0x08: Children list max capacity (set to 0x10 in constructor)
30
+ pub children_max_size: u32,
31
+ /// 0x0C: Children list unknown field (set to 0 in constructor)
32
+ pub children_unk: u32,
33
+ /// 0x10: Children list current size
34
+ pub children_size: u32,
35
+ /// 0x14: Pointer to children data array (allocated 0x60 bytes in constructor)
36
+ pub children_data: Ptr32,
37
+ /// 0x18: Children hash list pointer (set to 0 in constructor)
38
+ pub children_hash: Ptr32,
39
+ /// 0x1C: Unknown (set to 0 by parent-linking helper FUN_00562520)
40
+ pub _unknown_1c: u32,
41
+ /// 0x20: Task classification type (set to ClassType::Task by FUN_00562520,
42
+ /// overridden by derived constructors)
43
  pub class_type: ClassType,
44
+ /// 0x24: Shared data buffer pointer (inherited from parent, or allocated
45
+ /// 0x420 bytes for root tasks)
46
+ pub shared_data: Ptr32,
47
+ /// 0x28: 1 if this task owns shared_data (root), 0 if inherited from parent
48
+ pub owns_shared_data: u32,
49
+ /// 0x2C: DDGame pointer (3rd param to CTask::Constructor, stored at this+0x2C)
50
+ pub ddgame: Ptr32,
51
  }
52
 
53
  const _: () = assert!(core::mem::size_of::<CTask>() == 0x30);
crates/openwa-validator/src/hooks/detour.rs CHANGED
@@ -57,14 +57,15 @@ unsafe extern "stdcall" fn hook_frontend_change_screen(screen_id: u32) {
57
  static ORIG_CTASK_CONSTRUCTOR: AtomicU32 = AtomicU32::new(0);
58
 
59
  /// Hook for CTask::Constructor (0x5625A0).
60
- /// stdcall(this, parent, class_type) -> this
61
- unsafe extern "stdcall" fn hook_ctask_constructor(this: u32, parent: u32, class_type: u32) -> u32 {
 
62
  let orig: unsafe extern "stdcall" fn(u32, u32, u32) -> u32 =
63
  core::mem::transmute(ORIG_CTASK_CONSTRUCTOR.load(Ordering::Relaxed));
64
- let result = orig(this, parent, class_type);
65
  // Log after calling original (so vtable is set)
66
  let _ = log_line(&format!(
67
- "[HOOK] CTask::ctor(this=0x{this:08X}, parent=0x{parent:08X}, type={class_type}) -> 0x{result:08X}"
68
  ));
69
  result
70
  }
 
57
  static ORIG_CTASK_CONSTRUCTOR: AtomicU32 = AtomicU32::new(0);
58
 
59
  /// Hook for CTask::Constructor (0x5625A0).
60
+ /// stdcall(this, parent, ddgame) -> this
61
+ /// 3rd param is the DDGame pointer, stored at this+0x2C.
62
+ unsafe extern "stdcall" fn hook_ctask_constructor(this: u32, parent: u32, ddgame: u32) -> u32 {
63
  let orig: unsafe extern "stdcall" fn(u32, u32, u32) -> u32 =
64
  core::mem::transmute(ORIG_CTASK_CONSTRUCTOR.load(Ordering::Relaxed));
65
+ let result = orig(this, parent, ddgame);
66
  // Log after calling original (so vtable is set)
67
  let _ = log_line(&format!(
68
+ "[HOOK] CTask::ctor(this=0x{this:08X}, parent=0x{parent:08X}, ddgame=0x{ddgame:08X}) -> 0x{result:08X}"
69
  ));
70
  result
71
  }
crates/openwa-validator/src/lib.rs CHANGED
@@ -324,8 +324,12 @@ fn validate_struct_offsets(result: &mut ValidationResult) {
324
  let _ = log_line(" CTask:");
325
  check_offset!(result, CTask, vtable, 0x00);
326
  check_offset!(result, CTask, parent, 0x04);
327
- check_offset!(result, CTask, _unknown_08, 0x08);
 
328
  check_offset!(result, CTask, class_type, 0x20);
 
 
 
329
 
330
  // CGameTask offsets
331
  let _ = log_line("");
 
324
  let _ = log_line(" CTask:");
325
  check_offset!(result, CTask, vtable, 0x00);
326
  check_offset!(result, CTask, parent, 0x04);
327
+ check_offset!(result, CTask, children_max_size, 0x08);
328
+ check_offset!(result, CTask, children_data, 0x14);
329
  check_offset!(result, CTask, class_type, 0x20);
330
+ check_offset!(result, CTask, shared_data, 0x24);
331
+ check_offset!(result, CTask, owns_shared_data, 0x28);
332
+ check_offset!(result, CTask, ddgame, 0x2C);
333
 
334
  // CGameTask offsets
335
  let _ = log_line("");