paavohtl Claude Opus 4.6 (1M context) commited on
Commit
e2abf8d
Β·
1 Parent(s): 32368d0

feat: define DSSoundVtable (24 slots) and patch is_slot_loaded to Rust

Browse files

Add typed DSSoundVtable struct with all 24 virtual method signatures,
named from decompilation analysis (update_channels, play_sound,
set_master_volume, load_wav, stop_channel, etc.).

Refine ChannelDescriptor fields from vtable method analysis: flags_a/b,
volume_num/val, ds_buffer, pool_index. Rename buffer pool fields to
reflect free-list/used-list semantics. Rename _field_bb0 to
total_bytes_loaded (incremented by load_wav).

Implement is_slot_loaded (slot 13) in Rust and patch it into WA's
DSSound vtable via patch_vtable.

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

crates/openwa-core/src/audio/dssound.rs CHANGED
@@ -3,8 +3,8 @@ use crate::fixed::Fixed;
3
  /// DSSound β€” DirectSound audio subsystem.
4
  ///
5
  /// Created by DSSound__Constructor (0x573D50).
6
- /// Vtable: 0x66AF20
7
- /// Size: 0xBE0 bytes (allocated by GameEngine__InitHardware).
8
  ///
9
  /// Manages sound playback through DirectSound.
10
  /// Has 8 channel descriptors, 500 sound channel slots, a 64-entry
@@ -13,23 +13,90 @@ use crate::fixed::Fixed;
13
  type Ptr32 = u32;
14
 
15
  /// Channel descriptor (0x18 bytes). 8 entries at DSSound+0x14.
16
- /// FUN_005742A0 initializes: field_00=-1, field_04=-1, field_10=0.
 
 
17
  #[repr(C)]
18
  #[derive(Clone, Copy)]
19
  pub struct ChannelDescriptor {
20
- pub field_00: u32, // init -1
21
- pub field_04: u32, // init -1
22
- pub _pad_08: [u32; 2],
23
- pub field_10: u32, // init 0
24
- pub _pad_14: u32,
 
 
 
 
 
 
 
25
  }
26
 
27
  const _: () = assert!(core::mem::size_of::<ChannelDescriptor>() == 0x18);
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  #[repr(C)]
30
  pub struct DSSound {
31
  /// 0x000: Vtable pointer (0x66AF20)
32
- pub vtable: Ptr32,
33
  /// 0x004: HWND (set after construction)
34
  pub hwnd: Ptr32,
35
  /// 0x008: IDirectSound* (from DirectSoundCreate)
@@ -40,25 +107,30 @@ pub struct DSSound {
40
  pub primary_buffer: Ptr32,
41
  /// 0x014-0xD3: 8 channel descriptors (0x18 bytes each = 0xC0 total)
42
  pub channel_descs: [ChannelDescriptor; 8],
43
- /// 0x0D4-0x8A3: 500 channel slot indices (zeroed by constructor)
 
 
44
  pub channel_slots: [u32; 500],
45
  /// 0x8A4: Master volume (Fixed, init 0x10000 = 1.0)
46
  pub volume: Fixed,
47
- /// 0x8A8-0x9A7: 64 buffer pool shadow entries (init all -1 by FUN_00574260)
48
- pub buffer_pool_shadow: [u32; 64],
49
- /// 0x9A8-0xAA7: 64 buffer pool indices (init 0..63 by FUN_00574260)
50
- pub buffer_pool: [u32; 64],
51
- /// 0xAA8: Buffer pool count (init 0x40 = 64)
52
- pub buffer_pool_count: u32,
53
- /// 0xAAC-0xBAB: Unknown (0x100 bytes)
54
- pub _unknown_aac: [u8; 0x100],
55
- /// 0xBAC: Buffer pool state (init 0 by FUN_00574260)
56
- pub buffer_pool_state: u32,
57
- /// 0xBB0: Unknown field (zeroed by constructor)
58
- pub _field_bb0: u32,
59
- /// 0xBB4: Status flag 1 (init 1)
 
 
 
60
  pub status_1: u32,
61
- /// 0xBB8: Status flag 2 (init 1)
62
  pub status_2: u32,
63
  /// 0xBBC: Init success flag β€” set to 1 when DirectSoundCreate +
64
  /// init_buffers + IDirectSoundBuffer::Play all succeed.
@@ -69,6 +141,16 @@ pub struct DSSound {
69
 
70
  const _: () = assert!(core::mem::size_of::<DSSound>() == 0xBE0);
71
 
 
 
 
 
 
 
 
 
 
 
72
  impl DSSound {
73
  /// Construct a DSSound with all fields initialized, matching the
74
  /// original constructor (0x573D50) + helpers FUN_005742A0/FUN_00574260.
@@ -84,7 +166,7 @@ impl DSSound {
84
  let mut snd: Self = core::mem::zeroed();
85
 
86
  // Vtable (WA's .rdata, identity-checked)
87
- snd.vtable = rb(va::DS_SOUND_VTABLE);
88
 
89
  // HWND
90
  snd.hwnd = hwnd;
@@ -92,23 +174,23 @@ impl DSSound {
92
  // Volume = 1.0 (16.16 fixed-point)
93
  snd.volume = Fixed(0x10000);
94
 
95
- // 8 channel descriptors: field_00=-1, field_04=-1, field_10=0
96
  // (rest is zero from mem::zeroed)
97
  for desc in &mut snd.channel_descs {
98
- desc.field_00 = 0xFFFF_FFFF;
99
- desc.field_04 = 0xFFFF_FFFF;
100
  }
101
 
102
  // 500 channel slots: already zeroed
103
 
104
- // Buffer pool: shadow entries all -1, indices 0..63
105
- for (i, slot) in snd.buffer_pool.iter_mut().enumerate() {
106
  *slot = i as u32;
107
  }
108
  for entry in &mut snd.buffer_pool_shadow {
109
- *entry = 0xFFFF_FFFF;
110
  }
111
- snd.buffer_pool_count = 64;
112
 
113
  // Status flags
114
  snd.status_1 = 1;
 
3
  /// DSSound β€” DirectSound audio subsystem.
4
  ///
5
  /// Created by DSSound__Constructor (0x573D50).
6
+ /// Vtable: 0x66AF20 (24 slots).
7
+ /// Size: 0xBE0 bytes.
8
  ///
9
  /// Manages sound playback through DirectSound.
10
  /// Has 8 channel descriptors, 500 sound channel slots, a 64-entry
 
13
  type Ptr32 = u32;
14
 
15
  /// Channel descriptor (0x18 bytes). 8 entries at DSSound+0x14.
16
+ ///
17
+ /// Each descriptor tracks one active DirectSound buffer being played.
18
+ /// Fields identified from vtable methods (update_channels, set_volume, etc.).
19
  #[repr(C)]
20
  #[derive(Clone, Copy)]
21
  pub struct ChannelDescriptor {
22
+ /// Flags A (init -1, -1 = unused/free)
23
+ pub flags_a: i32,
24
+ /// Flags B (init -1, checked for < 0 in update_channels)
25
+ pub flags_b: i32,
26
+ /// Volume numerator (used in volume calculations, 0 = default)
27
+ pub volume_num: i32,
28
+ /// Volume value (scaled, used by set_channel_volume)
29
+ pub volume_val: i32,
30
+ /// IDirectSoundBuffer* for the active buffer on this channel (0 = empty)
31
+ pub ds_buffer: Ptr32,
32
+ /// Pool index tracking which buffer pool entry this channel uses
33
+ pub pool_index: u32,
34
  }
35
 
36
  const _: () = assert!(core::mem::size_of::<ChannelDescriptor>() == 0x18);
37
 
38
+ /// DSSound vtable layout (24 slots at 0x66AF20).
39
+ ///
40
+ /// Methods operate on the 8 channel descriptors and 64-entry buffer pool.
41
+ /// Trivial slots (stubs, noops, return-constant) are marked.
42
+ #[repr(C)]
43
+ pub struct DSSoundVtable {
44
+ /// Slot 0 (0x573DB0): destructor β€” thiscall(this, flags)
45
+ pub destructor: unsafe extern "thiscall" fn(*mut DSSound, u8) -> *mut DSSound,
46
+ /// Slot 1 (0x574400): update_channels β€” iterates 8 descs, releases finished buffers
47
+ pub update_channels: unsafe extern "thiscall" fn(*mut DSSound),
48
+ /// Slot 2 (0x574460): set_volume_params β€” sets status_1/2, adjusts channel volumes
49
+ pub set_volume_params: unsafe extern "thiscall" fn(*mut DSSound, u32, i32),
50
+ /// Slot 3 (0x574730): play_sound β€” wrapper around core play, returns bool
51
+ pub play_sound: unsafe extern "thiscall" fn(*mut DSSound, u32, u32, u32, u32) -> bool,
52
+ /// Slot 4 (0x574770): play_sound_pooled β€” allocates from buffer pool, plays
53
+ pub play_sound_pooled: unsafe extern "thiscall" fn(*mut DSSound, u32, u32, u32, u32, u32) -> i32,
54
+ /// Slot 5 (0x574900): set_pan β€” sets pan on channel (dB lookup)
55
+ pub set_pan: unsafe extern "thiscall" fn(*mut DSSound, u32, u32) -> u32,
56
+ /// Slot 6 (0x505430): **stub** β€” returns 0
57
+ pub stub_6: unsafe extern "thiscall" fn(*mut DSSound) -> u32,
58
+ /// Slot 7 (0x574A10): set_master_volume β€” sets volume, adjusts all channels
59
+ pub set_master_volume: unsafe extern "thiscall" fn(*mut DSSound, i32) -> u32,
60
+ /// Slot 8 (0x574980): set_channel_volume β€” volume on specific channel
61
+ pub set_channel_volume: unsafe extern "thiscall" fn(*mut DSSound, i32, i32) -> u32,
62
+ /// Slot 9 (0x5747F0): is_channel_playing β€” checks buffer status
63
+ pub is_channel_playing: unsafe extern "thiscall" fn(*mut DSSound, i32) -> u8,
64
+ /// Slot 10 (0x574840): stop_channel β€” stops + releases buffer, returns to pool
65
+ pub stop_channel: unsafe extern "thiscall" fn(*mut DSSound, i32) -> u32,
66
+ /// Slot 11 (0x574AB0): release_finished β€” releases finished buffers, returns count
67
+ pub release_finished: unsafe extern "thiscall" fn(*mut DSSound) -> i32,
68
+ /// Slot 12 (0x573FF0): load_wav β€” opens WAV file, parses RIFF, creates buffer
69
+ pub load_wav: unsafe extern "thiscall" fn(*mut DSSound, i32, *const u8) -> u32,
70
+ /// Slot 13 (0x573FD0): is_slot_loaded β€” returns channel_slots[idx] != 0
71
+ pub is_slot_loaded: unsafe extern "thiscall" fn(*mut DSSound, i32) -> bool,
72
+ /// Slot 14 (0x573D30): sub_destructor β€” sets secondary vtable
73
+ pub sub_destructor: unsafe extern "thiscall" fn(*mut DSSound, u8) -> *mut DSSound,
74
+ /// Slot 15 (0x4AA060): **noop** β€” returns void
75
+ pub noop_15: unsafe extern "thiscall" fn(*mut DSSound),
76
+ /// Slot 16 (0x5931C0): **noop** β€” returns void
77
+ pub noop_16: unsafe extern "thiscall" fn(*mut DSSound),
78
+ /// Slot 17 (0x573D20): **returns_0**
79
+ pub returns_0_17: unsafe extern "thiscall" fn(*mut DSSound) -> u32,
80
+ /// Slot 18 (0x573D20): **returns_0** (same as 17)
81
+ pub returns_0_18: unsafe extern "thiscall" fn(*mut DSSound) -> u32,
82
+ /// Slot 19 (0x505430): **stub** β€” returns 0
83
+ pub stub_19: unsafe extern "thiscall" fn(*mut DSSound) -> u32,
84
+ /// Slot 20 (0x505430): **stub** β€” returns 0
85
+ pub stub_20: unsafe extern "thiscall" fn(*mut DSSound) -> u32,
86
+ /// Slot 21 (0x571AF0): **returns_0**
87
+ pub returns_0_21: unsafe extern "thiscall" fn(*mut DSSound) -> u32,
88
+ /// Slot 22 (0x505430): **stub** β€” returns 0
89
+ pub stub_22: unsafe extern "thiscall" fn(*mut DSSound) -> u32,
90
+ /// Slot 23 (0x4260E0): **returns_1**
91
+ pub returns_1_23: unsafe extern "thiscall" fn(*mut DSSound) -> u32,
92
+ }
93
+
94
+ const _: () = assert!(core::mem::size_of::<DSSoundVtable>() == 24 * 4);
95
+
96
  #[repr(C)]
97
  pub struct DSSound {
98
  /// 0x000: Vtable pointer (0x66AF20)
99
+ pub vtable: *const DSSoundVtable,
100
  /// 0x004: HWND (set after construction)
101
  pub hwnd: Ptr32,
102
  /// 0x008: IDirectSound* (from DirectSoundCreate)
 
107
  pub primary_buffer: Ptr32,
108
  /// 0x014-0xD3: 8 channel descriptors (0x18 bytes each = 0xC0 total)
109
  pub channel_descs: [ChannelDescriptor; 8],
110
+ /// 0x0D4-0x8A3: 500 channel slot indices (zeroed by constructor).
111
+ /// Each slot holds a pointer to an IDirectSoundBuffer for that sound effect.
112
+ /// Index is 1-based (slot 0 unused). slot[i] != 0 means sound i is loaded.
113
  pub channel_slots: [u32; 500],
114
  /// 0x8A4: Master volume (Fixed, init 0x10000 = 1.0)
115
  pub volume: Fixed,
116
+ /// 0x8A8-0x9A7: 64 buffer pool shadow entries (init all -1 by FUN_00574260).
117
+ /// Maps pool index β†’ channel descriptor index (-1 = free).
118
+ pub buffer_pool_shadow: [i32; 64],
119
+ /// 0x9A8-0xAA7: 64 buffer pool indices (init 0..63 by FUN_00574260).
120
+ /// Free-list stack of available pool indices.
121
+ pub buffer_pool_free: [u32; 64],
122
+ /// 0xAA8: Number of free entries in buffer_pool_free (init 0x40 = 64).
123
+ pub buffer_pool_free_count: u32,
124
+ /// 0xAAC-0xBAB: Used pool tracking (0x100 bytes = 64 u32 entries).
125
+ /// Tracks which pool indices are in use.
126
+ pub buffer_pool_used: [u32; 64],
127
+ /// 0xBAC: Number of used entries in buffer_pool_used (init 0).
128
+ pub buffer_pool_used_count: u32,
129
+ /// 0xBB0: Total bytes loaded (incremented by load_wav)
130
+ pub total_bytes_loaded: u32,
131
+ /// 0xBB4: Status flag 1 (init 1, used by set_volume_params)
132
  pub status_1: u32,
133
+ /// 0xBB8: Status flag 2 (init 1, used by set_volume_params)
134
  pub status_2: u32,
135
  /// 0xBBC: Init success flag β€” set to 1 when DirectSoundCreate +
136
  /// init_buffers + IDirectSoundBuffer::Play all succeed.
 
141
 
142
  const _: () = assert!(core::mem::size_of::<DSSound>() == 0xBE0);
143
 
144
+ // ── Trivial vtable method implementations ─────────────────────────────────
145
+
146
+ /// Slot 13: is_slot_loaded β€” returns whether sound at `slot_idx` has a buffer loaded.
147
+ /// One-liner: `channel_slots[slot_idx] != 0`.
148
+ pub unsafe extern "thiscall" fn is_slot_loaded(this: *mut DSSound, slot_idx: i32) -> bool {
149
+ (*this).channel_slots.get((slot_idx - 1) as usize).copied().unwrap_or(0) != 0
150
+ }
151
+
152
+ // ── Construction ──────────────────────────────────────────────────────────
153
+
154
  impl DSSound {
155
  /// Construct a DSSound with all fields initialized, matching the
156
  /// original constructor (0x573D50) + helpers FUN_005742A0/FUN_00574260.
 
166
  let mut snd: Self = core::mem::zeroed();
167
 
168
  // Vtable (WA's .rdata, identity-checked)
169
+ snd.vtable = rb(va::DS_SOUND_VTABLE) as *const DSSoundVtable;
170
 
171
  // HWND
172
  snd.hwnd = hwnd;
 
174
  // Volume = 1.0 (16.16 fixed-point)
175
  snd.volume = Fixed(0x10000);
176
 
177
+ // 8 channel descriptors: flags_a=-1, flags_b=-1
178
  // (rest is zero from mem::zeroed)
179
  for desc in &mut snd.channel_descs {
180
+ desc.flags_a = -1;
181
+ desc.flags_b = -1;
182
  }
183
 
184
  // 500 channel slots: already zeroed
185
 
186
+ // Buffer pool: shadow all -1, free list 0..63
187
+ for (i, slot) in snd.buffer_pool_free.iter_mut().enumerate() {
188
  *slot = i as u32;
189
  }
190
  for entry in &mut snd.buffer_pool_shadow {
191
+ *entry = -1;
192
  }
193
+ snd.buffer_pool_free_count = 64;
194
 
195
  // Status flags
196
  snd.status_1 = 1;
crates/openwa-core/src/audio/mod.rs CHANGED
@@ -6,7 +6,7 @@ pub mod speech;
6
  pub mod streaming;
7
 
8
  pub use active_sound::{ActiveSoundEntry, ActiveSoundTable};
9
- pub use dssound::DSSound;
10
  pub use music::Music;
11
  pub use sound::SoundId;
12
  pub use speech::{SpeechLineId, SpeechLineTableEntry, SpeechSlotTable, SPEECH_LINE_TABLE_COUNT};
 
6
  pub mod streaming;
7
 
8
  pub use active_sound::{ActiveSoundEntry, ActiveSoundTable};
9
+ pub use dssound::{DSSound, DSSoundVtable, ChannelDescriptor, is_slot_loaded};
10
  pub use music::Music;
11
  pub use sound::SoundId;
12
  pub use speech::{SpeechLineId, SpeechLineTableEntry, SpeechSlotTable, SPEECH_LINE_TABLE_COUNT};
crates/openwa-wormkit/src/replacements/sound.rs CHANGED
@@ -155,7 +155,31 @@ pub fn install() -> Result<(), String> {
155
  va::PLAY_SOUND_LOCAL,
156
  trampoline_play_sound_local as *const (),
157
  )?;
 
 
 
158
  }
159
 
160
  Ok(())
161
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  va::PLAY_SOUND_LOCAL,
156
  trampoline_play_sound_local as *const (),
157
  )?;
158
+
159
+ // Patch DSSound vtable: replace trivial slots with Rust implementations.
160
+ patch_dssound_vtable()?;
161
  }
162
 
163
  Ok(())
164
  }
165
+
166
+ /// Patch DSSound vtable (0x66AF20) to replace trivial methods with Rust.
167
+ unsafe fn patch_dssound_vtable() -> Result<(), String> {
168
+ use openwa_core::rebase::rb;
169
+ use openwa_core::vtable::patch_vtable;
170
+ use openwa_core::audio::is_slot_loaded;
171
+
172
+ let vtable = rb(va::DS_SOUND_VTABLE) as *mut u32;
173
+
174
+ patch_vtable(vtable, 24, |vt| {
175
+ let mut patched = 0u32;
176
+
177
+ // Slot 13: is_slot_loaded β€” one-liner channel_slots check
178
+ *vt.add(13) = is_slot_loaded as *const () as u32;
179
+ patched += 1;
180
+
181
+ let _ = log_line(&format!(
182
+ "[Sound] DSSound vtable: patched {patched}/24 slots with Rust"
183
+ ));
184
+ }).map_err(|e| e.to_string())
185
+ }