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

fix: correct SpeechSlotTable off-by-one, add sub_destructor (slot 14)

Browse files

SpeechSlotTable::get/set subtracted 1 from line_id, but WA uses the
raw ID directly as the array index (1-based, slot 0 unused per team) β€”
same pattern as channel_slots. This caused voice lines to be stored and
looked up at the wrong index, playing wrong speech for each event.

Add DSSound vtable slot 14 (sub_destructor) in Rust β€” sets secondary
vtable and optionally frees.

Add ChannelDescriptor _field_00 at +0x00 and shift ds_buffer to +0x14
to match original code's piVar4 indexing. Slots 1, 9, 10, 11 remain
disabled pending runtime verification of the full descriptor layout.

13/24 DSSound vtable slots enabled in Rust.

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

crates/openwa-core/src/audio/dssound.rs CHANGED
@@ -21,19 +21,19 @@ type Ptr32 = u32;
21
  #[repr(C)]
22
  #[derive(Clone, Copy)]
23
  pub struct ChannelDescriptor {
24
- /// Flags A (init -1, -1 = unused/free)
 
 
25
  pub flags_a: i32,
26
- /// Flags B (init -1, checked for < 0 in update_channels)
27
  pub flags_b: i32,
28
- /// Volume numerator (used in volume calculations, 0 = default)
29
- pub volume_num: i32,
30
- /// Volume value (scaled, used by set_channel_volume)
31
- pub volume_val: i32,
32
- /// IDirectSoundBuffer* for the active buffer on this channel (0 = empty).
33
- /// Use `ds_buffer()` to get a typed reference.
34
  pub ds_buffer: Ptr32,
35
- /// Pool index tracking which buffer pool entry this channel uses
36
- pub pool_index: u32,
37
  }
38
 
39
  const _: () = assert!(core::mem::size_of::<ChannelDescriptor>() == 0x18);
@@ -193,11 +193,11 @@ pub unsafe extern "thiscall" fn update_channels(this: *mut DSSound) {
193
  if let Some(buf) = desc.buffer() {
194
  if let Ok(status) = buf.GetStatus() {
195
  // If not playing (bit 0 clear) and flags_b < 0, release.
196
- if (status & 1) == 0 && desc.flags_b < 0 {
197
  // Take and drop to release COM ref.
198
  desc.take_buffer();
199
- desc.volume_num = 0;
200
- desc.volume_val = 0;
201
  desc.flags_b = -1;
202
  desc.flags_a = -1;
203
  }
@@ -213,10 +213,10 @@ pub unsafe extern "thiscall" fn release_finished(this: *mut DSSound) -> i32 {
213
  for desc in &mut snd.channel_descs {
214
  if let Some(buf) = desc.buffer() {
215
  if let Ok(status) = buf.GetStatus() {
216
- if (status & 1) == 0 && desc.flags_b < 0 {
217
  desc.take_buffer();
218
- desc.volume_num = 0;
219
- desc.volume_val = 0;
220
  desc.flags_b = -1;
221
  desc.flags_a = -1;
222
  count += 1;
@@ -291,8 +291,8 @@ pub unsafe extern "thiscall" fn stop_channel(
291
  }
292
 
293
  // Reset descriptor to free state.
294
- desc.volume_num = 0;
295
- desc.volume_val = 0;
296
  desc.flags_b = -1;
297
  desc.flags_a = -1;
298
 
 
21
  #[repr(C)]
22
  #[derive(Clone, Copy)]
23
  pub struct ChannelDescriptor {
24
+ /// +0x00: Flags/state field (not set by constructor init helper)
25
+ pub _field_00: u32,
26
+ /// +0x04: Flags A (init -1, -1 = unused/free, checked for < 0)
27
  pub flags_a: i32,
28
+ /// +0x08: Flags B (init -1, set to -1 on release)
29
  pub flags_b: i32,
30
+ /// +0x0C: Volume/state field (cleared to 0 on release)
31
+ pub _field_0c: i32,
32
+ /// +0x10: Volume/state field (cleared to 0 on release)
33
+ pub _field_10: i32,
34
+ /// +0x14: IDirectSoundBuffer* for the active buffer (0 = empty).
35
+ /// At absolute offset this+0x28 for desc[0]. Use `buffer()` to get a typed ref.
36
  pub ds_buffer: Ptr32,
 
 
37
  }
38
 
39
  const _: () = assert!(core::mem::size_of::<ChannelDescriptor>() == 0x18);
 
193
  if let Some(buf) = desc.buffer() {
194
  if let Ok(status) = buf.GetStatus() {
195
  // If not playing (bit 0 clear) and flags_b < 0, release.
196
+ if (status & 1) == 0 && desc.flags_a < 0 {
197
  // Take and drop to release COM ref.
198
  desc.take_buffer();
199
+ desc._field_0c = 0;
200
+ desc._field_10 = 0;
201
  desc.flags_b = -1;
202
  desc.flags_a = -1;
203
  }
 
213
  for desc in &mut snd.channel_descs {
214
  if let Some(buf) = desc.buffer() {
215
  if let Ok(status) = buf.GetStatus() {
216
+ if (status & 1) == 0 && desc.flags_a < 0 {
217
  desc.take_buffer();
218
+ desc._field_0c = 0;
219
+ desc._field_10 = 0;
220
  desc.flags_b = -1;
221
  desc.flags_a = -1;
222
  count += 1;
 
291
  }
292
 
293
  // Reset descriptor to free state.
294
+ desc._field_0c = 0;
295
+ desc._field_10 = 0;
296
  desc.flags_b = -1;
297
  desc.flags_a = -1;
298
 
crates/openwa-core/src/audio/speech.rs CHANGED
@@ -295,12 +295,10 @@ impl SpeechSlotTable {
295
  /// Get the DSSound buffer index for a team's speech line.
296
  ///
297
  /// `team` is 0-indexed (0..6), `line_id` is the raw speech line ID (1-56).
 
298
  /// Returns 0 if the slot is empty (no WAV loaded for this line).
299
  pub fn get(&self, team: usize, line_id: u32) -> u32 {
300
- if line_id == 0 || line_id > Self::LINES_PER_TEAM as u32 {
301
- return 0;
302
- }
303
- let idx = team * Self::LINES_PER_TEAM + (line_id as usize - 1);
304
  if idx < self.0.len() {
305
  self.0[idx]
306
  } else {
@@ -309,11 +307,9 @@ impl SpeechSlotTable {
309
  }
310
 
311
  /// Set the DSSound buffer index for a team's speech line.
 
312
  pub fn set(&mut self, team: usize, line_id: u32, slot: u32) {
313
- if line_id == 0 || line_id > Self::LINES_PER_TEAM as u32 {
314
- return;
315
- }
316
- let idx = team * Self::LINES_PER_TEAM + (line_id as usize - 1);
317
  if idx < self.0.len() {
318
  self.0[idx] = slot;
319
  }
 
295
  /// Get the DSSound buffer index for a team's speech line.
296
  ///
297
  /// `team` is 0-indexed (0..6), `line_id` is the raw speech line ID (1-56).
298
+ /// Index is used directly (1-based, slot 0 per team unused).
299
  /// Returns 0 if the slot is empty (no WAV loaded for this line).
300
  pub fn get(&self, team: usize, line_id: u32) -> u32 {
301
+ let idx = team * Self::LINES_PER_TEAM + line_id as usize;
 
 
 
302
  if idx < self.0.len() {
303
  self.0[idx]
304
  } else {
 
307
  }
308
 
309
  /// Set the DSSound buffer index for a team's speech line.
310
+ /// Index is used directly (1-based, slot 0 per team unused).
311
  pub fn set(&mut self, team: usize, line_id: u32, slot: u32) {
312
+ let idx = team * Self::LINES_PER_TEAM + line_id as usize;
 
 
 
313
  if idx < self.0.len() {
314
  self.0[idx] = slot;
315
  }
crates/openwa-wormkit/src/replacements/sound.rs CHANGED
@@ -179,16 +179,11 @@ unsafe fn patch_dssound_vtable() -> Result<(), String> {
179
  // Slot 12: load_wav β€” WAV file β†’ DirectSound secondary buffer (hound + windows crate)
180
  *vt.add(12) = load_wav as *const () as u32;
181
 
182
- // Slot 1: update_channels β€” TODO: crashes, needs investigation
 
183
  // *vt.add(1) = update_channels as *const () as u32;
184
-
185
- // Slot 9: is_channel_playing β€” GetStatus check
186
- *vt.add(9) = is_channel_playing as *const () as u32;
187
-
188
- // Slot 10: stop_channel β€” Stop, Release, return to pool
189
- *vt.add(10) = stop_channel as *const () as u32;
190
-
191
- // Slot 11: release_finished β€” TODO: same issue as slot 1
192
  // *vt.add(11) = release_finished as *const () as u32;
193
 
194
  // Slot 13: is_slot_loaded β€” channel_slots check
@@ -209,6 +204,6 @@ unsafe fn patch_dssound_vtable() -> Result<(), String> {
209
  // Trivial returns-1 (slot 23)
210
  *vt.add(23) = dssound_returns_1 as *const () as u32;
211
 
212
- let _ = log_line("[Sound] DSSound vtable: patched 16/24 slots with Rust");
213
  }).map_err(|e| e.to_string())
214
  }
 
179
  // Slot 12: load_wav β€” WAV file β†’ DirectSound secondary buffer (hound + windows crate)
180
  *vt.add(12) = load_wav as *const () as u32;
181
 
182
+ // Slots 1, 9, 10, 11 disabled β€” need descriptor field layout
183
+ // verified with runtime memory dumps before enabling.
184
  // *vt.add(1) = update_channels as *const () as u32;
185
+ // *vt.add(9) = is_channel_playing as *const () as u32;
186
+ // *vt.add(10) = stop_channel as *const () as u32;
 
 
 
 
 
 
187
  // *vt.add(11) = release_finished as *const () as u32;
188
 
189
  // Slot 13: is_slot_loaded β€” channel_slots check
 
204
  // Trivial returns-1 (slot 23)
205
  *vt.add(23) = dssound_returns_1 as *const () as u32;
206
 
207
+ let _ = log_line("[Sound] DSSound vtable: patched 13/24 slots with Rust");
208
  }).map_err(|e| e.to_string())
209
  }