fix: DSSound channel priority stored wrong value, preventing sound eviction
Browse filescore_play_sound stored flags_and_slot (sound ID) in desc.flags_b instead
of priority (flags). This made all channels appear high-priority (IDs like
55, 121) so new sounds with real priority (1-8) could never evict them.
Channels accumulated until all 8 were permanently occupied, blocking all
subsequent pooled sounds (explosions, sizzle, etc.).
Also fixed stop_channel to unconditionally return pool entries to free list
even when the descriptor was already evicted by allocate_channel. WA does
this; without it, evicted pool entries leak and pool_free_count drops to 0.
Also fixed stop_channel used-list removal to use swap-remove (matching WA)
instead of shift-down.
Added sound_priority replay test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -518,7 +518,7 @@ unsafe fn core_play_sound(
|
|
| 518 |
// Store descriptor state.
|
| 519 |
let desc = &mut snd.channel_descs[di];
|
| 520 |
desc.flags_a = -1;
|
| 521 |
-
desc.flags_b =
|
| 522 |
desc.channel_freq = channel_freq;
|
| 523 |
desc.channel_volume = vol_scaled;
|
| 524 |
desc.ds_buffer = core::mem::transmute_copy(&new_buf);
|
|
@@ -710,46 +710,46 @@ pub unsafe extern "thiscall" fn stop_channel(this: *mut DSSound, pool_id: i32) -
|
|
| 710 |
}
|
| 711 |
let snd = &mut *this;
|
| 712 |
let desc_idx = snd.buffer_pool_shadow[idx];
|
| 713 |
-
if desc_idx < 0 || desc_idx as usize >= 8 {
|
| 714 |
-
return 0;
|
| 715 |
-
}
|
| 716 |
-
let di = desc_idx as usize;
|
| 717 |
-
let desc = &mut snd.channel_descs[di];
|
| 718 |
|
| 719 |
-
//
|
| 720 |
-
if
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 730 |
|
| 731 |
// Mark pool shadow as free.
|
| 732 |
snd.buffer_pool_shadow[idx] = -1;
|
| 733 |
|
| 734 |
-
//
|
|
|
|
|
|
|
| 735 |
let free_slot = snd.buffer_pool_free_count as usize;
|
| 736 |
snd.buffer_pool_free[free_slot] = idx as u32;
|
| 737 |
snd.buffer_pool_free_count += 1;
|
| 738 |
|
| 739 |
-
// Remove from used list.
|
|
|
|
| 740 |
let used_count = snd.buffer_pool_used_count as usize;
|
| 741 |
for i in 0..used_count {
|
| 742 |
if snd.buffer_pool_used[i] == idx as u32 {
|
| 743 |
-
// Shift remaining entries down.
|
| 744 |
-
for j in i..used_count - 1 {
|
| 745 |
-
snd.buffer_pool_used[j] = snd.buffer_pool_used[j + 1];
|
| 746 |
-
}
|
| 747 |
snd.buffer_pool_used_count -= 1;
|
| 748 |
-
|
|
|
|
| 749 |
}
|
| 750 |
}
|
| 751 |
-
|
| 752 |
-
1
|
| 753 |
}
|
| 754 |
|
| 755 |
/// Slot 12: load_wav — load a WAV file into a DirectSound secondary buffer
|
|
|
|
| 518 |
// Store descriptor state.
|
| 519 |
let desc = &mut snd.channel_descs[di];
|
| 520 |
desc.flags_a = -1;
|
| 521 |
+
desc.flags_b = priority;
|
| 522 |
desc.channel_freq = channel_freq;
|
| 523 |
desc.channel_volume = vol_scaled;
|
| 524 |
desc.ds_buffer = core::mem::transmute_copy(&new_buf);
|
|
|
|
| 710 |
}
|
| 711 |
let snd = &mut *this;
|
| 712 |
let desc_idx = snd.buffer_pool_shadow[idx];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
|
| 714 |
+
// If the descriptor is still valid, stop and release the buffer.
|
| 715 |
+
// (desc_idx may be -1 if the channel was evicted by allocate_channel)
|
| 716 |
+
if desc_idx >= 0 && (desc_idx as usize) < 8 {
|
| 717 |
+
let di = desc_idx as usize;
|
| 718 |
+
let desc = &mut snd.channel_descs[di];
|
| 719 |
|
| 720 |
+
// Stop and release the buffer.
|
| 721 |
+
if let Some(buf) = desc.take_buffer() {
|
| 722 |
+
let _ = buf.Stop();
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
// Reset descriptor to free state.
|
| 726 |
+
desc.channel_freq = 0;
|
| 727 |
+
desc.channel_volume = 0;
|
| 728 |
+
desc.flags_b = -1;
|
| 729 |
+
desc.flags_a = -1;
|
| 730 |
+
}
|
| 731 |
|
| 732 |
// Mark pool shadow as free.
|
| 733 |
snd.buffer_pool_shadow[idx] = -1;
|
| 734 |
|
| 735 |
+
// ALWAYS return pool index to free list, even if descriptor was already
|
| 736 |
+
// evicted. WA does this unconditionally — without it, evicted pool entries
|
| 737 |
+
// leak and pool_free_count drops to 0, preventing new pooled sounds.
|
| 738 |
let free_slot = snd.buffer_pool_free_count as usize;
|
| 739 |
snd.buffer_pool_free[free_slot] = idx as u32;
|
| 740 |
snd.buffer_pool_free_count += 1;
|
| 741 |
|
| 742 |
+
// Remove from used list (swap-remove with last entry, matching WA).
|
| 743 |
+
// Returns 1 if found in used list, 0 if not.
|
| 744 |
let used_count = snd.buffer_pool_used_count as usize;
|
| 745 |
for i in 0..used_count {
|
| 746 |
if snd.buffer_pool_used[i] == idx as u32 {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
snd.buffer_pool_used_count -= 1;
|
| 748 |
+
snd.buffer_pool_used[i] = snd.buffer_pool_used[snd.buffer_pool_used_count as usize];
|
| 749 |
+
return 1;
|
| 750 |
}
|
| 751 |
}
|
| 752 |
+
0
|
|
|
|
| 753 |
}
|
| 754 |
|
| 755 |
/// Slot 12: load_wav — load a WAV file into a DirectSound secondary buffer
|
|
@@ -515,7 +515,7 @@ pub fn install() -> Result<(), String> {
|
|
| 515 |
hook_play_sound_pooled_direct as *const (),
|
| 516 |
)?;
|
| 517 |
|
| 518 |
-
// Patch DSSound vtable: replace
|
| 519 |
patch_dssound_vtable()?;
|
| 520 |
|
| 521 |
// Hook CTaskWorm::PlaySound2 (FUN_00515020) — 23 callers in WA
|
|
|
|
| 515 |
hook_play_sound_pooled_direct as *const (),
|
| 516 |
)?;
|
| 517 |
|
| 518 |
+
// Patch DSSound vtable: replace all 24 slots with Rust implementations.
|
| 519 |
patch_dssound_vtable()?;
|
| 520 |
|
| 521 |
// Hook CTaskWorm::PlaySound2 (FUN_00515020) — 23 callers in WA
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:82df26aa4a414d9798c64e9175b583f8e5fdca234e48378aa6b3fdb4616b2dcb
|
| 3 |
+
size 19943
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:06f587257881df251a020b001a512d50693e84f1516acf2fcf9a20920730eacf
|
| 3 |
+
size 2681
|