feat: replace DSSound vtable slots 9 (is_channel_playing) and 10 (stop_channel)
Browse filesAdd typed ChannelDescriptor::buffer() and take_buffer() helpers for
safe IDirectSoundBuffer access. Remove cfg(target_os) guards β this
project is 32-bit Windows only.
Slot 9: checks IDirectSoundBuffer::GetStatus via typed helper.
Slot 10: stops buffer via take_buffer() + Drop, resets descriptor,
returns pool index to free list, removes from used list.
14/24 DSSound vtable slots now Rust.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/audio/dssound.rs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
use crate::fixed::Fixed;
|
| 2 |
|
|
|
|
|
|
|
| 3 |
/// DSSound β DirectSound audio subsystem.
|
| 4 |
///
|
| 5 |
/// Created by DSSound__Constructor (0x573D50).
|
|
@@ -27,7 +29,8 @@ pub struct ChannelDescriptor {
|
|
| 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,
|
|
@@ -35,6 +38,28 @@ pub struct ChannelDescriptor {
|
|
| 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.
|
|
@@ -160,6 +185,85 @@ pub unsafe extern "thiscall" fn returns_0(_this: *mut DSSound) -> u32 { 0 }
|
|
| 160 |
/// Trivial stub β returns 1. Used for slot 23.
|
| 161 |
pub unsafe extern "thiscall" fn returns_1(_this: *mut DSSound) -> u32 { 1 }
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
/// Slot 12: load_wav β load a WAV file into a DirectSound secondary buffer
|
| 164 |
/// and store the buffer pointer at `channel_slots[slot_idx]`.
|
| 165 |
///
|
|
@@ -170,7 +274,6 @@ pub unsafe extern "thiscall" fn returns_1(_this: *mut DSSound) -> u32 { 1 }
|
|
| 170 |
/// for DirectSound COM calls.
|
| 171 |
///
|
| 172 |
/// Returns 1 on success, 0 on failure (matching original).
|
| 173 |
-
#[cfg(target_os = "windows")]
|
| 174 |
pub unsafe extern "thiscall" fn load_wav(
|
| 175 |
this: *mut DSSound,
|
| 176 |
slot_idx: i32,
|
|
|
|
| 1 |
use crate::fixed::Fixed;
|
| 2 |
|
| 3 |
+
use windows::Win32::Media::Audio::DirectSound::IDirectSoundBuffer;
|
| 4 |
+
|
| 5 |
/// DSSound β DirectSound audio subsystem.
|
| 6 |
///
|
| 7 |
/// Created by DSSound__Constructor (0x573D50).
|
|
|
|
| 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,
|
|
|
|
| 38 |
|
| 39 |
const _: () = assert!(core::mem::size_of::<ChannelDescriptor>() == 0x18);
|
| 40 |
|
| 41 |
+
impl ChannelDescriptor {
|
| 42 |
+
/// Get the DirectSound buffer as a typed reference, if present.
|
| 43 |
+
pub unsafe fn buffer(&self) -> Option<&IDirectSoundBuffer> {
|
| 44 |
+
if self.ds_buffer != 0 {
|
| 45 |
+
Some(&*(&self.ds_buffer as *const Ptr32 as *const IDirectSoundBuffer))
|
| 46 |
+
} else {
|
| 47 |
+
None
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
/// Take ownership of the buffer (for releasing). Zeroes the field.
|
| 52 |
+
pub unsafe fn take_buffer(&mut self) -> Option<IDirectSoundBuffer> {
|
| 53 |
+
if self.ds_buffer != 0 {
|
| 54 |
+
let buf: IDirectSoundBuffer = core::mem::transmute_copy(&self.ds_buffer);
|
| 55 |
+
self.ds_buffer = 0;
|
| 56 |
+
Some(buf)
|
| 57 |
+
} else {
|
| 58 |
+
None
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
/// DSSound vtable layout (24 slots at 0x66AF20).
|
| 64 |
///
|
| 65 |
/// Methods operate on the 8 channel descriptors and 64-entry buffer pool.
|
|
|
|
| 185 |
/// Trivial stub β returns 1. Used for slot 23.
|
| 186 |
pub unsafe extern "thiscall" fn returns_1(_this: *mut DSSound) -> u32 { 1 }
|
| 187 |
|
| 188 |
+
/// Slot 9: is_channel_playing β checks if a buffer pool entry is still playing.
|
| 189 |
+
/// `pool_id` is 1-based (1..64). Returns 1 if playing or invalid, 0 if stopped.
|
| 190 |
+
pub unsafe extern "thiscall" fn is_channel_playing(
|
| 191 |
+
this: *mut DSSound, pool_id: i32,
|
| 192 |
+
) -> u8 {
|
| 193 |
+
let idx = (pool_id - 1) as usize;
|
| 194 |
+
if idx >= 64 {
|
| 195 |
+
return 0;
|
| 196 |
+
}
|
| 197 |
+
let snd = &*this;
|
| 198 |
+
let desc_idx = snd.buffer_pool_shadow[idx];
|
| 199 |
+
if desc_idx < 0 {
|
| 200 |
+
return 0;
|
| 201 |
+
}
|
| 202 |
+
let desc = &snd.channel_descs[desc_idx as usize];
|
| 203 |
+
let Some(buf) = desc.buffer() else { return 0 };
|
| 204 |
+
let status = buf.GetStatus();
|
| 205 |
+
if let Ok(s) = status {
|
| 206 |
+
if (s & 1) != 0 {
|
| 207 |
+
// DSBSTATUS_PLAYING = 1
|
| 208 |
+
return 1;
|
| 209 |
+
}
|
| 210 |
+
}
|
| 211 |
+
1 // original returns 1 even on error
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
/// Slot 10: stop_channel β stops a buffer, releases it, and returns the pool entry.
|
| 215 |
+
/// `pool_id` is 1-based (1..64). Returns 1 on success, 0 on invalid.
|
| 216 |
+
pub unsafe extern "thiscall" fn stop_channel(
|
| 217 |
+
this: *mut DSSound, pool_id: i32,
|
| 218 |
+
) -> u32 {
|
| 219 |
+
let idx = (pool_id - 1) as usize;
|
| 220 |
+
if idx >= 64 {
|
| 221 |
+
return 0;
|
| 222 |
+
}
|
| 223 |
+
let snd = &mut *this;
|
| 224 |
+
let desc_idx = snd.buffer_pool_shadow[idx];
|
| 225 |
+
if desc_idx < 0 {
|
| 226 |
+
return 0;
|
| 227 |
+
}
|
| 228 |
+
let di = desc_idx as usize;
|
| 229 |
+
let desc = &mut snd.channel_descs[di];
|
| 230 |
+
|
| 231 |
+
// Stop and release the buffer.
|
| 232 |
+
if let Some(buf) = desc.take_buffer() {
|
| 233 |
+
let _ = buf.Stop();
|
| 234 |
+
// Release happens when buf drops
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
// Reset descriptor to free state.
|
| 238 |
+
desc.volume_num = 0;
|
| 239 |
+
desc.volume_val = 0;
|
| 240 |
+
desc.flags_b = -1;
|
| 241 |
+
desc.flags_a = -1;
|
| 242 |
+
|
| 243 |
+
// Mark pool shadow as free.
|
| 244 |
+
snd.buffer_pool_shadow[idx] = -1;
|
| 245 |
+
|
| 246 |
+
// Return pool index to free list.
|
| 247 |
+
let free_slot = snd.buffer_pool_free_count as usize;
|
| 248 |
+
snd.buffer_pool_free[free_slot] = idx as u32;
|
| 249 |
+
snd.buffer_pool_free_count += 1;
|
| 250 |
+
|
| 251 |
+
// Remove from used list.
|
| 252 |
+
let used_count = snd.buffer_pool_used_count as usize;
|
| 253 |
+
for i in 0..used_count {
|
| 254 |
+
if snd.buffer_pool_used[i] == idx as u32 {
|
| 255 |
+
// Shift remaining entries down.
|
| 256 |
+
for j in i..used_count - 1 {
|
| 257 |
+
snd.buffer_pool_used[j] = snd.buffer_pool_used[j + 1];
|
| 258 |
+
}
|
| 259 |
+
snd.buffer_pool_used_count -= 1;
|
| 260 |
+
break;
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
1
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
/// Slot 12: load_wav β load a WAV file into a DirectSound secondary buffer
|
| 268 |
/// and store the buffer pointer at `channel_slots[slot_idx]`.
|
| 269 |
///
|
|
|
|
| 274 |
/// for DirectSound COM calls.
|
| 275 |
///
|
| 276 |
/// Returns 1 on success, 0 on failure (matching original).
|
|
|
|
| 277 |
pub unsafe extern "thiscall" fn load_wav(
|
| 278 |
this: *mut DSSound,
|
| 279 |
slot_idx: i32,
|
crates/openwa-core/src/audio/mod.rs
CHANGED
|
@@ -7,7 +7,7 @@ pub mod streaming;
|
|
| 7 |
|
| 8 |
pub use active_sound::{ActiveSoundEntry, ActiveSoundTable};
|
| 9 |
pub use dssound::{DSSound, DSSoundVtable, ChannelDescriptor};
|
| 10 |
-
pub use dssound::{is_slot_loaded, load_wav, noop as dssound_noop, returns_0 as dssound_returns_0, returns_1 as dssound_returns_1};
|
| 11 |
pub use music::Music;
|
| 12 |
pub use sound::SoundId;
|
| 13 |
pub use speech::{SpeechLineId, SpeechLineTableEntry, SpeechSlotTable, SPEECH_LINE_TABLE_COUNT};
|
|
|
|
| 7 |
|
| 8 |
pub use active_sound::{ActiveSoundEntry, ActiveSoundTable};
|
| 9 |
pub use dssound::{DSSound, DSSoundVtable, ChannelDescriptor};
|
| 10 |
+
pub use dssound::{is_slot_loaded, is_channel_playing, stop_channel, load_wav, noop as dssound_noop, returns_0 as dssound_returns_0, returns_1 as dssound_returns_1};
|
| 11 |
pub use music::Music;
|
| 12 |
pub use sound::SoundId;
|
| 13 |
pub use speech::{SpeechLineId, SpeechLineTableEntry, SpeechSlotTable, SPEECH_LINE_TABLE_COUNT};
|
crates/openwa-wormkit/src/replacements/sound.rs
CHANGED
|
@@ -168,7 +168,8 @@ 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::{
|
| 171 |
-
is_slot_loaded,
|
|
|
|
| 172 |
};
|
| 173 |
|
| 174 |
let vtable = rb(va::DS_SOUND_VTABLE) as *mut u32;
|
|
@@ -177,6 +178,12 @@ unsafe fn patch_dssound_vtable() -> Result<(), String> {
|
|
| 177 |
// Slot 12: load_wav β WAV file β DirectSound secondary buffer (hound + windows crate)
|
| 178 |
*vt.add(12) = load_wav as *const () as u32;
|
| 179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
// Slot 13: is_slot_loaded β channel_slots check
|
| 181 |
*vt.add(13) = is_slot_loaded as *const () as u32;
|
| 182 |
|
|
@@ -192,6 +199,6 @@ unsafe fn patch_dssound_vtable() -> Result<(), String> {
|
|
| 192 |
// Trivial returns-1 (slot 23)
|
| 193 |
*vt.add(23) = dssound_returns_1 as *const () as u32;
|
| 194 |
|
| 195 |
-
let _ = log_line("[Sound] DSSound vtable: patched
|
| 196 |
}).map_err(|e| e.to_string())
|
| 197 |
}
|
|
|
|
| 168 |
use openwa_core::rebase::rb;
|
| 169 |
use openwa_core::vtable::patch_vtable;
|
| 170 |
use openwa_core::audio::{
|
| 171 |
+
is_slot_loaded, is_channel_playing, stop_channel, load_wav,
|
| 172 |
+
dssound_noop, dssound_returns_0, dssound_returns_1,
|
| 173 |
};
|
| 174 |
|
| 175 |
let vtable = rb(va::DS_SOUND_VTABLE) as *mut u32;
|
|
|
|
| 178 |
// Slot 12: load_wav β WAV file β DirectSound secondary buffer (hound + windows crate)
|
| 179 |
*vt.add(12) = load_wav as *const () as u32;
|
| 180 |
|
| 181 |
+
// Slot 9: is_channel_playing β GetStatus check
|
| 182 |
+
*vt.add(9) = is_channel_playing as *const () as u32;
|
| 183 |
+
|
| 184 |
+
// Slot 10: stop_channel β Stop, Release, return to pool
|
| 185 |
+
*vt.add(10) = stop_channel as *const () as u32;
|
| 186 |
+
|
| 187 |
// Slot 13: is_slot_loaded β channel_slots check
|
| 188 |
*vt.add(13) = is_slot_loaded as *const () as u32;
|
| 189 |
|
|
|
|
| 199 |
// Trivial returns-1 (slot 23)
|
| 200 |
*vt.add(23) = dssound_returns_1 as *const () as u32;
|
| 201 |
|
| 202 |
+
let _ = log_line("[Sound] DSSound vtable: patched 14/24 slots with Rust");
|
| 203 |
}).map_err(|e| e.to_string())
|
| 204 |
}
|