paavohtl Claude Opus 4.6 commited on
Commit
84a3c5e
·
1 Parent(s): 4031a30

feat: port DrawLineStrip/DrawPolygon, add RenderQueue to DDGame, replace Ptr32

Browse files

- Add DrawLineStrip (type 8) and DrawPolygon (type 9) as full Rust
replacements, completing all 10 RenderQueue enqueue methods
- Add RenderQueue::alloc_raw() for variable-size buffer allocation
- Add render_queue (*mut RenderQueue) field to DDGame at offset 0x524,
discovered via xref analysis of DrawPolygon callers
- Replace Ptr32 with *mut u8 / *mut RenderQueue in DDGame and RenderQueue
structs (Ptr32 was just u32; real pointers are more expressive)
- Add RENDER_QUEUE offset to ddgame_offsets

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

crates/openwa-types/src/address.rs CHANGED
@@ -478,6 +478,8 @@ pub mod va {
478
  pub const PC_LANDSCAPE: u32 = 0x4CC;
479
  /// Offset to weapon table pointer
480
  pub const WEAPON_TABLE: u32 = 0x510;
 
 
481
  /// Offset to weapon panel pointer
482
  pub const WEAPON_PANEL: u32 = 0x548;
483
  }
 
478
  pub const PC_LANDSCAPE: u32 = 0x4CC;
479
  /// Offset to weapon table pointer
480
  pub const WEAPON_TABLE: u32 = 0x510;
481
+ /// Offset to RenderQueue pointer
482
+ pub const RENDER_QUEUE: u32 = 0x524;
483
  /// Offset to weapon panel pointer
484
  pub const WEAPON_PANEL: u32 = 0x548;
485
  }
crates/openwa-types/src/ddgame.rs CHANGED
@@ -1,4 +1,4 @@
1
- use crate::task::Ptr32;
2
 
3
  /// DDGame — the main game engine object.
4
  ///
@@ -8,7 +8,7 @@ use crate::task::Ptr32;
8
  /// Allocated in DDGame__Constructor (0x56E220).
9
  /// The DDGame pointer is stored at DDGameWrapper+0x488 (DWORD index 0x122).
10
  ///
11
- /// PARTIAL: Fields up to 0x510 are densely mapped from the constructor.
12
  /// Beyond that, only scattered fields are known — use the `offsets` module.
13
  ///
14
  /// Note on offsets: The constructor accesses DDGame fields via
@@ -17,56 +17,66 @@ use crate::task::Ptr32;
17
  #[repr(C)]
18
  pub struct DDGame {
19
  /// 0x000: Base value (param_5 from constructor)
20
- pub _base_000: Ptr32,
21
  /// 0x004: Context pointer (param_3)
22
- pub _context: Ptr32,
23
  /// 0x008: param_4
24
- pub _param_008: Ptr32,
25
  /// 0x00C: Allocated object (0x608 bytes, conditional)
26
- pub _object_00c: Ptr32,
27
  /// 0x010: param_6
28
- pub _param_010: Ptr32,
29
  /// 0x014: param_7
30
- pub _param_014: Ptr32,
31
  /// 0x018: param_8
32
- pub _param_018: Ptr32,
33
  /// 0x01C: Caller/parent pointer (param_1)
34
- pub _caller: Ptr32,
35
  /// 0x020: PCLandscape pointer (copied from DDGameWrapper[0x133])
36
- pub landscape: Ptr32,
37
  /// 0x024: Game state pointer (param_10)
38
- pub game_state: Ptr32,
39
  /// 0x028: param_9
40
- pub _param_028: Ptr32,
41
  /// 0x02C: Secondary GfxDir object (0x70C bytes, conditional on GfxHandler 1)
42
- pub secondary_gfxdir: Ptr32,
43
  /// 0x030: Gradient image pointer
44
- pub gradient_image: Ptr32,
45
  /// 0x034: Gradient image 2 pointer
46
- pub gradient_image_2: Ptr32,
47
  /// 0x038-0x0B4: Arrow sprite object pointers (32 entries)
48
- pub arrow_sprites: [Ptr32; 32],
49
  /// 0x0B8-0x134: Arrow GfxDir pointers (32 entries, conditional)
50
- pub arrow_gfxdirs: [Ptr32; 32],
51
  /// 0x138: DisplayGfx object pointer (vtable 0x664144)
52
- pub display_gfx: Ptr32,
53
  /// 0x13C-0x37F: Unknown
54
  pub _unknown_13c: [u8; 0x244],
55
  /// 0x380: TaskStateMachine pointer (vtable 0x664118, 0x2C bytes)
56
- pub task_state_machine: Ptr32,
57
  /// 0x384-0x467: Unknown
58
  pub _unknown_384: [u8; 0xE4],
59
  /// 0x468: Landscape-derived value
60
- pub _landscape_val: Ptr32,
61
  /// 0x46C-0x488: 8 SpriteRegion pointers (0x9C bytes each, vtable 0x66B268)
62
  /// Created by SpriteRegion__Constructor (0x57DB20).
63
  /// Each contains 32 TaskStateMachine sub-objects.
64
- pub sprite_regions: [Ptr32; 8],
65
  /// 0x48C-0x508: Arrow collision region pointers (32 entries)
66
- pub arrow_collision_regions: [Ptr32; 32],
67
  /// 0x50C: Coordinate list object (capacity 600, 0x12C0 data buffer)
68
- pub coord_list: Ptr32,
69
- /// 0x510-0x98B7: Remaining fields (sparse — see offsets module)
 
 
 
 
 
 
 
 
 
 
70
  ///
71
  /// Known landmarks in this region:
72
  /// - 0x64D8: cleared by init
@@ -81,7 +91,7 @@ pub struct DDGame {
81
  ///
82
  /// Also includes FUN_00526120 zeroed offsets at stride 0x194:
83
  /// 0x379C, 0x3930, 0x3AC4, 0x3C58, 0x3DEC, 0x3F80, 0x4114, 0x42A8, 0x443C, 0x45D0
84
- pub _remaining: [u8; 0x93A8],
85
  }
86
 
87
  const _: () = assert!(core::mem::size_of::<DDGame>() == 0x98B8);
 
1
+ use crate::render::RenderQueue;
2
 
3
  /// DDGame — the main game engine object.
4
  ///
 
8
  /// Allocated in DDGame__Constructor (0x56E220).
9
  /// The DDGame pointer is stored at DDGameWrapper+0x488 (DWORD index 0x122).
10
  ///
11
+ /// PARTIAL: Fields up to 0x54C are densely mapped from the constructor.
12
  /// Beyond that, only scattered fields are known — use the `offsets` module.
13
  ///
14
  /// Note on offsets: The constructor accesses DDGame fields via
 
17
  #[repr(C)]
18
  pub struct DDGame {
19
  /// 0x000: Base value (param_5 from constructor)
20
+ pub _base_000: *mut u8,
21
  /// 0x004: Context pointer (param_3)
22
+ pub _context: *mut u8,
23
  /// 0x008: param_4
24
+ pub _param_008: *mut u8,
25
  /// 0x00C: Allocated object (0x608 bytes, conditional)
26
+ pub _object_00c: *mut u8,
27
  /// 0x010: param_6
28
+ pub _param_010: *mut u8,
29
  /// 0x014: param_7
30
+ pub _param_014: *mut u8,
31
  /// 0x018: param_8
32
+ pub _param_018: *mut u8,
33
  /// 0x01C: Caller/parent pointer (param_1)
34
+ pub _caller: *mut u8,
35
  /// 0x020: PCLandscape pointer (copied from DDGameWrapper[0x133])
36
+ pub landscape: *mut u8,
37
  /// 0x024: Game state pointer (param_10)
38
+ pub game_state: *mut u8,
39
  /// 0x028: param_9
40
+ pub _param_028: *mut u8,
41
  /// 0x02C: Secondary GfxDir object (0x70C bytes, conditional on GfxHandler 1)
42
+ pub secondary_gfxdir: *mut u8,
43
  /// 0x030: Gradient image pointer
44
+ pub gradient_image: *mut u8,
45
  /// 0x034: Gradient image 2 pointer
46
+ pub gradient_image_2: *mut u8,
47
  /// 0x038-0x0B4: Arrow sprite object pointers (32 entries)
48
+ pub arrow_sprites: [*mut u8; 32],
49
  /// 0x0B8-0x134: Arrow GfxDir pointers (32 entries, conditional)
50
+ pub arrow_gfxdirs: [*mut u8; 32],
51
  /// 0x138: DisplayGfx object pointer (vtable 0x664144)
52
+ pub display_gfx: *mut u8,
53
  /// 0x13C-0x37F: Unknown
54
  pub _unknown_13c: [u8; 0x244],
55
  /// 0x380: TaskStateMachine pointer (vtable 0x664118, 0x2C bytes)
56
+ pub task_state_machine: *mut u8,
57
  /// 0x384-0x467: Unknown
58
  pub _unknown_384: [u8; 0xE4],
59
  /// 0x468: Landscape-derived value
60
+ pub _landscape_val: *mut u8,
61
  /// 0x46C-0x488: 8 SpriteRegion pointers (0x9C bytes each, vtable 0x66B268)
62
  /// Created by SpriteRegion__Constructor (0x57DB20).
63
  /// Each contains 32 TaskStateMachine sub-objects.
64
+ pub sprite_regions: [*mut u8; 8],
65
  /// 0x48C-0x508: Arrow collision region pointers (32 entries)
66
+ pub arrow_collision_regions: [*mut u8; 32],
67
  /// 0x50C: Coordinate list object (capacity 600, 0x12C0 data buffer)
68
+ pub coord_list: *mut u8,
69
+ /// 0x510: Weapon table pointer
70
+ pub weapon_table: *mut u8,
71
+ /// 0x514-0x523: Unknown
72
+ pub _unknown_514: [u8; 0x10],
73
+ /// 0x524: RenderQueue pointer (passed as `this` to all Draw* functions)
74
+ pub render_queue: *mut RenderQueue,
75
+ /// 0x528-0x547: Unknown
76
+ pub _unknown_528: [u8; 0x20],
77
+ /// 0x548: Weapon panel pointer
78
+ pub weapon_panel: *mut u8,
79
+ /// 0x54C-0x98B7: Remaining fields (sparse — see offsets module)
80
  ///
81
  /// Known landmarks in this region:
82
  /// - 0x64D8: cleared by init
 
91
  ///
92
  /// Also includes FUN_00526120 zeroed offsets at stride 0x194:
93
  /// 0x379C, 0x3930, 0x3AC4, 0x3C58, 0x3DEC, 0x3F80, 0x4114, 0x42A8, 0x443C, 0x45D0
94
+ pub _remaining: [u8; 0x936C],
95
  }
96
 
97
  const _: () = assert!(core::mem::size_of::<DDGame>() == 0x98B8);
crates/openwa-types/src/render.rs CHANGED
@@ -1,5 +1,3 @@
1
- use crate::task::Ptr32;
2
-
3
  /// Render command entry (0x18 bytes).
4
  ///
5
  /// Enqueued by DrawSpriteGlobal (type 4), DrawSpriteLocal (type 5),
@@ -34,8 +32,8 @@ pub struct RenderQueue {
34
  pub _buffer: [u8; 0x10000],
35
  /// 0x10004: Number of enqueued entries (max 0x800)
36
  pub entry_count: u32,
37
- /// 0x10008: Pointer array — entry_ptrs[N] points to the N-th DrawSpriteCmd
38
- pub entry_ptrs: [Ptr32; 0x800],
39
  }
40
 
41
  impl RenderQueue {
@@ -54,10 +52,30 @@ impl RenderQueue {
54
  self.buffer_offset = new_offset;
55
 
56
  let entry = &mut *(self._buffer.as_mut_ptr().add(new_offset as usize) as *mut T);
57
- self.entry_ptrs[self.entry_count as usize] = entry as *mut T as u32;
58
  self.entry_count += 1;
59
  Some(entry)
60
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
 
63
  /// Type 0xD — single pixel (0x18 bytes).
@@ -156,6 +174,33 @@ pub struct DrawTextboxLocalCmd {
156
 
157
  const _: () = assert!(core::mem::size_of::<DrawTextboxLocalCmd>() == 0x34);
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  /// Render command type constants.
160
  pub mod command_type {
161
  pub const DRAW_RECT: u32 = 0;
@@ -164,6 +209,8 @@ pub mod command_type {
164
  pub const DRAW_SPRITE_GLOBAL: u32 = 4;
165
  pub const DRAW_SPRITE_LOCAL: u32 = 5;
166
  pub const DRAW_SPRITE_OFFSET: u32 = 6;
 
 
167
  pub const DRAW_SCALED: u32 = 0xB;
168
  pub const DRAW_PIXEL: u32 = 0xD;
169
  }
 
 
 
1
  /// Render command entry (0x18 bytes).
2
  ///
3
  /// Enqueued by DrawSpriteGlobal (type 4), DrawSpriteLocal (type 5),
 
32
  pub _buffer: [u8; 0x10000],
33
  /// 0x10004: Number of enqueued entries (max 0x800)
34
  pub entry_count: u32,
35
+ /// 0x10008: Pointer array — entry_ptrs[N] points to the N-th command
36
+ pub entry_ptrs: [*mut u8; 0x800],
37
  }
38
 
39
  impl RenderQueue {
 
52
  self.buffer_offset = new_offset;
53
 
54
  let entry = &mut *(self._buffer.as_mut_ptr().add(new_offset as usize) as *mut T);
55
+ self.entry_ptrs[self.entry_count as usize] = entry as *mut T as *mut u8;
56
  self.entry_count += 1;
57
  Some(entry)
58
  }
59
+
60
+ /// Allocate `size` bytes from the downward-growing buffer.
61
+ ///
62
+ /// Like `alloc<T>()` but for variable-size entries (e.g. DrawLineStrip, DrawPolygon).
63
+ /// Returns `None` if the queue is full or buffer exhausted.
64
+ pub unsafe fn alloc_raw(&mut self, size: usize) -> Option<*mut u8> {
65
+ if self.entry_count >= 0x800 {
66
+ return None;
67
+ }
68
+ let new_offset = self.buffer_offset - size as i32;
69
+ if new_offset < 0 {
70
+ return None;
71
+ }
72
+ self.buffer_offset = new_offset;
73
+
74
+ let ptr = self._buffer.as_mut_ptr().add(new_offset as usize);
75
+ self.entry_ptrs[self.entry_count as usize] = ptr;
76
+ self.entry_count += 1;
77
+ Some(ptr)
78
+ }
79
  }
80
 
81
  /// Type 0xD — single pixel (0x18 bytes).
 
174
 
175
  const _: () = assert!(core::mem::size_of::<DrawTextboxLocalCmd>() == 0x34);
176
 
177
+ /// Type 8 — line strip header (0x10 bytes, followed by count × 0xC vertex data).
178
+ ///
179
+ /// Total allocation: count × 0xC + 0x1C.
180
+ #[repr(C)]
181
+ pub struct DrawLineStripHeader {
182
+ pub command_type: u32, // 8
183
+ pub layer: u32, // hardcoded 0xE_0000
184
+ pub count: u32, // vertex count (from EDI)
185
+ pub param_1: u32, // stack param
186
+ }
187
+
188
+ const _: () = assert!(core::mem::size_of::<DrawLineStripHeader>() == 0x10);
189
+
190
+ /// Type 9 — polygon header (0x14 bytes, followed by count × 0xC vertex data).
191
+ ///
192
+ /// Total allocation: count × 0xC + 0x20.
193
+ #[repr(C)]
194
+ pub struct DrawPolygonHeader {
195
+ pub command_type: u32, // 9
196
+ pub layer: u32, // hardcoded 0xE_0000
197
+ pub count: u32, // vertex count (from ESI)
198
+ pub param_1: u32, // stack param 1
199
+ pub param_2: u32, // stack param 2
200
+ }
201
+
202
+ const _: () = assert!(core::mem::size_of::<DrawPolygonHeader>() == 0x14);
203
+
204
  /// Render command type constants.
205
  pub mod command_type {
206
  pub const DRAW_RECT: u32 = 0;
 
209
  pub const DRAW_SPRITE_GLOBAL: u32 = 4;
210
  pub const DRAW_SPRITE_LOCAL: u32 = 5;
211
  pub const DRAW_SPRITE_OFFSET: u32 = 6;
212
+ pub const DRAW_LINE_STRIP: u32 = 8;
213
+ pub const DRAW_POLYGON: u32 = 9;
214
  pub const DRAW_SCALED: u32 = 0xB;
215
  pub const DRAW_PIXEL: u32 = 0xD;
216
  }
crates/openwa-wormkit/src/replacements/sprite.rs CHANGED
@@ -36,6 +36,74 @@ unsafe extern "cdecl" fn draw_pixel_impl(
36
  }
37
  }
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  // ---------------------------------------------------------------------------
40
  // DrawScaled (0x541ED0) — type 0xB, ECX=this, 5 stack, RET 0x14
41
  // ---------------------------------------------------------------------------
@@ -276,6 +344,18 @@ pub unsafe fn install() -> Result<(), String> {
276
  trampoline_draw_pixel as *const (),
277
  )?;
278
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  let _ = hook::install(
280
  "DrawScaled",
281
  va::DRAW_SCALED,
 
36
  }
37
  }
38
 
39
+ // ---------------------------------------------------------------------------
40
+ // DrawLineStrip (0x541DD0) — type 8, EAX=this, EDI=count, 2 stack, RET 0x8
41
+ // Allocation: count * 0xC + 0x1C (variable size)
42
+ // ---------------------------------------------------------------------------
43
+
44
+ usercall_trampoline!(fn trampoline_draw_line_strip; impl_fn = draw_line_strip_impl;
45
+ regs = [eax, edi]; stack_params = 2; ret_bytes = "0x8");
46
+
47
+ unsafe extern "cdecl" fn draw_line_strip_impl(
48
+ this: u32,
49
+ count: u32,
50
+ vertex_ptr: u32,
51
+ param_1: u32,
52
+ ) {
53
+ let q = &mut *(this as *mut RenderQueue);
54
+ let total_size = count as usize * 0xC + 0x1C;
55
+
56
+ if let Some(ptr) = q.alloc_raw(total_size) {
57
+ let header = &mut *(ptr as *mut DrawLineStripHeader);
58
+ *header = DrawLineStripHeader {
59
+ command_type: command_type::DRAW_LINE_STRIP,
60
+ layer: 0xE_0000,
61
+ count,
62
+ param_1,
63
+ };
64
+ core::ptr::copy_nonoverlapping(
65
+ vertex_ptr as *const u8,
66
+ ptr.add(core::mem::size_of::<DrawLineStripHeader>()),
67
+ count as usize * 0xC,
68
+ );
69
+ }
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // DrawPolygon (0x541E50) — type 9, ECX=this, ESI=count, 3 stack, RET 0xC
74
+ // Allocation: count * 0xC + 0x20 (variable size)
75
+ // ---------------------------------------------------------------------------
76
+
77
+ usercall_trampoline!(fn trampoline_draw_polygon; impl_fn = draw_polygon_impl;
78
+ regs = [ecx, esi]; stack_params = 3; ret_bytes = "0xC");
79
+
80
+ unsafe extern "cdecl" fn draw_polygon_impl(
81
+ this: u32,
82
+ count: u32,
83
+ vertex_ptr: u32,
84
+ param_1: u32,
85
+ param_2: u32,
86
+ ) {
87
+ let q = &mut *(this as *mut RenderQueue);
88
+ let total_size = count as usize * 0xC + 0x20;
89
+
90
+ if let Some(ptr) = q.alloc_raw(total_size) {
91
+ let header = &mut *(ptr as *mut DrawPolygonHeader);
92
+ *header = DrawPolygonHeader {
93
+ command_type: command_type::DRAW_POLYGON,
94
+ layer: 0xE_0000,
95
+ count,
96
+ param_1,
97
+ param_2,
98
+ };
99
+ core::ptr::copy_nonoverlapping(
100
+ vertex_ptr as *const u8,
101
+ ptr.add(core::mem::size_of::<DrawPolygonHeader>()),
102
+ count as usize * 0xC,
103
+ );
104
+ }
105
+ }
106
+
107
  // ---------------------------------------------------------------------------
108
  // DrawScaled (0x541ED0) — type 0xB, ECX=this, 5 stack, RET 0x14
109
  // ---------------------------------------------------------------------------
 
344
  trampoline_draw_pixel as *const (),
345
  )?;
346
 
347
+ let _ = hook::install(
348
+ "DrawLineStrip",
349
+ va::DRAW_LINE_STRIP,
350
+ trampoline_draw_line_strip as *const (),
351
+ )?;
352
+
353
+ let _ = hook::install(
354
+ "DrawPolygon",
355
+ va::DRAW_POLYGON,
356
+ trampoline_draw_polygon as *const (),
357
+ )?;
358
+
359
  let _ = hook::install(
360
  "DrawScaled",
361
  va::DRAW_SCALED,