use crate::emulator::{Cpu, Mmu};
pub mod asf_amt;
pub mod call;
pub mod host_iface;
pub mod host_iface_r31;
pub use asf_amt::{
extract_wma_amt_from_asf, locate_first_data_packet, AmtBlueprint, AsfParseError,
ASF_AUDIO_MEDIA, ASF_HEADER_OBJECT, ASF_STREAM_PROPERTIES_OBJECT,
};
pub use call::{add_ref, call_method, query_interface, release};
pub use host_iface::{
all_set_properties, clear_query_info_log, clear_set_properties_log, last_set_properties,
media_sample_set_payload, mint_host_filter_graph, mint_host_media_sample,
mint_host_mem_allocator, mint_host_mem_allocator_class_factory,
mint_host_output_pin_with_connection, query_filter_info_call_count, query_filter_info_calls,
query_pin_info_call_count, query_pin_info_calls, AllocatorPropertiesCapture,
DEFAULT_MEM_ALLOCATOR_FACTORY_CAPACITY, DEFAULT_MEM_ALLOCATOR_FACTORY_POOL,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Guid {
pub data1: u32,
pub data2: u16,
pub data3: u16,
pub data4: [u8; 8],
}
impl Guid {
pub const fn new(data1: u32, data2: u16, data3: u16, data4: [u8; 8]) -> Self {
Guid {
data1,
data2,
data3,
data4,
}
}
pub fn parse(s: &str) -> Result<Self, GuidParseError> {
let bytes = s.as_bytes();
if bytes.len() != 38 {
return Err(GuidParseError::WrongLength { len: bytes.len() });
}
if bytes[0] != b'{' || bytes[37] != b'}' {
return Err(GuidParseError::MissingBraces);
}
for &i in &[9usize, 14, 19, 24] {
if bytes[i] != b'-' {
return Err(GuidParseError::MissingHyphen { at: i });
}
}
let hex = |start: usize, len: usize| -> Result<u64, GuidParseError> {
let mut acc: u64 = 0;
for i in 0..len {
let c = bytes[start + i];
let nib = match c {
b'0'..=b'9' => (c - b'0') as u64,
b'a'..=b'f' => (c - b'a' + 10) as u64,
b'A'..=b'F' => (c - b'A' + 10) as u64,
_ => {
return Err(GuidParseError::BadHex {
at: start + i,
byte: c,
});
}
};
acc = (acc << 4) | nib;
}
Ok(acc)
};
let data1 = hex(1, 8)? as u32;
let data2 = hex(10, 4)? as u16;
let data3 = hex(15, 4)? as u16;
let mut data4 = [0u8; 8];
for (i, slot) in data4.iter_mut().enumerate().take(2) {
*slot = hex(20 + 2 * i, 2)? as u8;
}
for (i, slot) in data4.iter_mut().enumerate().skip(2) {
*slot = hex(25 + 2 * (i - 2), 2)? as u8;
}
Ok(Guid {
data1,
data2,
data3,
data4,
})
}
pub fn to_braced_string(self) -> String {
format!(
"{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
self.data1,
self.data2,
self.data3,
self.data4[0],
self.data4[1],
self.data4[2],
self.data4[3],
self.data4[4],
self.data4[5],
self.data4[6],
self.data4[7],
)
}
pub fn write_le(self) -> [u8; 16] {
let mut out = [0u8; 16];
out[0..4].copy_from_slice(&self.data1.to_le_bytes());
out[4..6].copy_from_slice(&self.data2.to_le_bytes());
out[6..8].copy_from_slice(&self.data3.to_le_bytes());
out[8..16].copy_from_slice(&self.data4);
out
}
pub fn read_le(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 16 {
return None;
}
let data1 = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let data2 = u16::from_le_bytes([bytes[4], bytes[5]]);
let data3 = u16::from_le_bytes([bytes[6], bytes[7]]);
let mut data4 = [0u8; 8];
data4.copy_from_slice(&bytes[8..16]);
Some(Guid {
data1,
data2,
data3,
data4,
})
}
pub fn stage(self, mmu: &mut Mmu, addr: u32) -> Result<(), crate::emulator::Trap> {
mmu.write_initializer(addr, &self.write_le())
}
pub fn load(mmu: &Mmu, addr: u32) -> Result<Self, crate::emulator::Trap> {
let mut buf = [0u8; 16];
for (i, slot) in buf.iter_mut().enumerate() {
*slot = mmu.load8(addr + i as u32)?;
}
Self::read_le(&buf).ok_or(crate::emulator::Trap::MemoryFault { addr })
}
}
impl core::fmt::Display for Guid {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.to_braced_string())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GuidParseError {
WrongLength { len: usize },
MissingBraces,
MissingHyphen { at: usize },
BadHex { at: usize, byte: u8 },
}
impl core::fmt::Display for GuidParseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
GuidParseError::WrongLength { len } => {
write!(f, "GUID string must be 38 chars (got {len})")
}
GuidParseError::MissingBraces => f.write_str("GUID string missing { … } braces"),
GuidParseError::MissingHyphen { at } => {
write!(f, "GUID string missing '-' at position {at}")
}
GuidParseError::BadHex { at, byte } => {
write!(f, "GUID string non-hex byte {byte:#x} at position {at}")
}
}
}
}
impl std::error::Error for GuidParseError {}
pub const IID_IUNKNOWN: Guid = Guid::new(
0x0000_0000,
0x0000,
0x0000,
[0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46],
);
pub const IID_ICLASSFACTORY: Guid = Guid::new(
0x0000_0001,
0x0000,
0x0000,
[0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46],
);
pub const IID_IPERSIST: Guid = Guid::new(
0x0000_010C,
0x0000,
0x0000,
[0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46],
);
pub const IID_IMEDIAFILTER: Guid = Guid::new(
0x56A8_6899,
0x0AD4,
0x11CE,
[0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
);
pub const IID_IBASEFILTER: Guid = Guid::new(
0x56A8_6895,
0x0AD4,
0x11CE,
[0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
);
pub const IID_IPIN: Guid = Guid::new(
0x56A8_6891,
0x0AD4,
0x11CE,
[0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
);
pub const IID_IMEMINPUTPIN: Guid = Guid::new(
0x56A8_689D,
0x0AD4,
0x11CE,
[0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
);
pub const IID_IENUMPINS: Guid = Guid::new(
0x56A8_6892,
0x0AD4,
0x11CE,
[0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
);
pub const IID_IMEMALLOCATOR: Guid = Guid::new(
0x56A8_689C,
0x0AD4,
0x11CE,
[0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
);
pub const IID_IMEDIASAMPLE: Guid = Guid::new(
0x56A8_689A,
0x0AD4,
0x11CE,
[0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
);
pub const IID_IMEDIASAMPLE2: Guid = Guid::new(
0x36B7_3884,
0xC2C8,
0x11CF,
[0x8B, 0x46, 0x00, 0x80, 0x5F, 0x6C, 0xEF, 0x60],
);
pub const IID_IFILTERGRAPH: Guid = Guid::new(
0x56A8_689F,
0x0AD4,
0x11CE,
[0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
);
pub const MSADDS_AUDIO_DECODER_CLSID: Guid = Guid::new(
0x22E2_4591,
0x49D0,
0x11D2,
[0xBB, 0x50, 0x00, 0x60, 0x08, 0x32, 0x00, 0x64],
);
pub const MSADDS_AUDIO_PROPERTY_PAGE_CLSID: Guid = Guid::new(
0x8FE7_E181,
0xBB96,
0x11D2,
[0xA1, 0xCB, 0x00, 0x60, 0x97, 0x78, 0xEA, 0x66],
);
pub const CLSID_MEMORY_ALLOCATOR: Guid = Guid::new(
0x1E65_1CC0,
0xB199,
0x11D0,
[0x82, 0x12, 0x00, 0xC0, 0x4F, 0xC3, 0x2C, 0x45],
);
pub const S_OK: u32 = 0x0000_0000;
pub const S_FALSE: u32 = 0x0000_0001;
pub const E_NOINTERFACE: u32 = 0x8000_4002;
pub const E_NOTIMPL: u32 = 0x8000_4001;
pub const E_POINTER: u32 = 0x8000_4003;
pub const E_FAIL: u32 = 0x8000_4005;
pub const E_UNEXPECTED: u32 = 0x8000_FFFF;
pub const CLASS_E_CLASSNOTAVAILABLE: u32 = 0x8004_0111;
pub const SLOT_QUERY_INTERFACE: u32 = 0;
pub const SLOT_ADD_REF: u32 = 1;
pub const SLOT_RELEASE: u32 = 2;
pub const SLOT_CLASS_FACTORY_CREATE_INSTANCE: u32 = 3;
pub const SLOT_CLASS_FACTORY_LOCK_SERVER: u32 = 4;
pub const SLOT_BASEFILTER_STOP: u32 = 4;
pub const SLOT_BASEFILTER_PAUSE: u32 = 5;
pub const SLOT_BASEFILTER_RUN: u32 = 6;
pub const SLOT_BASEFILTER_GET_STATE: u32 = 7;
pub const SLOT_BASEFILTER_ENUM_PINS: u32 = 10;
pub const SLOT_BASEFILTER_FIND_PIN: u32 = 11;
pub const SLOT_BASEFILTER_JOIN_FILTER_GRAPH: u32 = 13;
pub const SLOT_MEDIAFILTER_STOP: u32 = 4;
pub const SLOT_MEDIAFILTER_PAUSE: u32 = 5;
pub const SLOT_MEDIAFILTER_RUN: u32 = 6;
pub const SLOT_MEDIAFILTER_GET_STATE: u32 = 7;
pub const SLOT_MEMALLOCATOR_SET_PROPERTIES: u32 = 3;
pub const SLOT_MEMALLOCATOR_COMMIT: u32 = 5;
pub const SLOT_MEMALLOCATOR_DECOMMIT: u32 = 6;
pub const SLOT_MEMALLOCATOR_GET_BUFFER: u32 = 7;
pub const SLOT_MEMALLOCATOR_RELEASE_BUFFER: u32 = 8;
pub const SLOT_MEDIASAMPLE_GET_POINTER: u32 = 3;
pub const SLOT_MEDIASAMPLE_GET_SIZE: u32 = 4;
pub const SLOT_MEDIASAMPLE_IS_SYNC_POINT: u32 = 7;
pub const SLOT_MEDIASAMPLE_SET_SYNC_POINT: u32 = 8;
pub const SLOT_MEDIASAMPLE_GET_ACTUAL_DATA_LENGTH: u32 = 11;
pub const SLOT_MEDIASAMPLE_SET_ACTUAL_DATA_LENGTH: u32 = 12;
pub const SLOT_MEDIASAMPLE_GET_MEDIA_TYPE: u32 = 13;
pub const SLOT_MEDIASAMPLE_SET_MEDIA_TYPE: u32 = 14;
pub const SLOT_MEDIASAMPLE_GET_MEDIA_TIME: u32 = 17;
pub const SLOT_MEDIASAMPLE_SET_MEDIA_TIME: u32 = 18;
pub const SLOT_MEDIASAMPLE2_GET_PROPERTIES: u32 = 19;
pub const SLOT_MEDIASAMPLE2_SET_PROPERTIES: u32 = 20;
pub const SLOT_MEMINPUTPIN_GET_ALLOCATOR: u32 = 3;
pub const SLOT_MEMINPUTPIN_NOTIFY_ALLOCATOR: u32 = 4;
pub const SLOT_MEMINPUTPIN_RECEIVE: u32 = 6;
pub const SLOT_PIN_RECEIVE_CONNECTION: u32 = 4;
pub const SLOT_PIN_QUERY_DIRECTION: u32 = 9;
pub const SLOT_PIN_QUERY_ACCEPT: u32 = 11;
pub const SLOT_PIN_ENUM_MEDIA_TYPES: u32 = 12;
pub const SLOT_PIN_END_OF_STREAM: u32 = 14;
pub const SLOT_PIN_BEGIN_FLUSH: u32 = 15;
pub const SLOT_PIN_END_FLUSH: u32 = 16;
pub const SLOT_PIN_NEW_SEGMENT: u32 = 17;
pub const SLOT_ENUMPINS_NEXT: u32 = 3;
pub const PIN_DIRECTION_INPUT: u32 = 0;
pub const PIN_DIRECTION_OUTPUT: u32 = 1;
pub const FILTER_STATE_STOPPED: u32 = 0;
pub const FILTER_STATE_PAUSED: u32 = 1;
pub const FILTER_STATE_RUNNING: u32 = 2;
pub const VFW_S_STATE_INTERMEDIATE: u32 = 0x0004_0003;
pub const VFW_S_CANT_CUE: u32 = 0x0004_0004;
pub const VFW_E_NOT_COMMITTED: u32 = 0x8004_0209;
pub const VFW_E_TIMEOUT: u32 = 0x8004_0211;
pub const VFW_E_NO_ALLOCATOR: u32 = 0x8004_0261;
#[derive(Debug, Clone)]
pub struct ComObjectInfo {
pub guest_addr: u32,
pub host_refcount: i32,
pub last_iid: Option<Guid>,
}
#[derive(Debug, Default, Clone)]
pub struct ComObjectTable {
objects: std::collections::BTreeMap<u32, ComObjectInfo>,
pub class_factories: std::collections::BTreeMap<Guid, u32>,
}
impl ComObjectTable {
pub fn new() -> Self {
Self::default()
}
pub fn intern(&mut self, addr: u32, iid: Option<Guid>) -> &mut ComObjectInfo {
self.objects.entry(addr).or_insert(ComObjectInfo {
guest_addr: addr,
host_refcount: 0,
last_iid: iid,
})
}
pub fn len(&self) -> usize {
self.objects.len()
}
pub fn total_refcount(&self) -> i32 {
self.objects.values().map(|o| o.host_refcount).sum()
}
pub fn is_empty(&self) -> bool {
self.objects.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&u32, &ComObjectInfo)> {
self.objects.iter()
}
pub fn get(&self, addr: u32) -> Option<&ComObjectInfo> {
self.objects.get(&addr)
}
pub fn get_mut(&mut self, addr: u32) -> Option<&mut ComObjectInfo> {
self.objects.get_mut(&addr)
}
pub fn record_addref(&mut self, addr: u32) {
if let Some(o) = self.objects.get_mut(&addr) {
o.host_refcount = o.host_refcount.saturating_add(1);
}
}
pub fn record_release(&mut self, addr: u32) -> i32 {
if let Some(o) = self.objects.get_mut(&addr) {
o.host_refcount = o.host_refcount.saturating_sub(1);
return o.host_refcount;
}
0
}
pub fn register_class_factory(&mut self, clsid: Guid, factory: u32) {
self.class_factories.insert(clsid, factory);
}
pub fn lookup_class_factory(&self, clsid: &Guid) -> Option<u32> {
self.class_factories.get(clsid).copied()
}
}
pub fn vtable_ptr(mmu: &Mmu, obj: u32) -> Result<u32, crate::emulator::Trap> {
mmu.load32(obj)
}
pub fn method_va(mmu: &Mmu, obj: u32, slot: u32) -> Result<u32, crate::emulator::Trap> {
let vtbl = vtable_ptr(mmu, obj)?;
mmu.load32(vtbl.wrapping_add(slot.wrapping_mul(4)))
}
pub fn lookup_in_process_class(table: &ComObjectTable, clsid: Guid, iid: Guid) -> Option<u32> {
if iid != IID_IUNKNOWN && iid != IID_ICLASSFACTORY {
return None;
}
table.lookup_class_factory(&clsid)
}
#[doc(hidden)]
pub(crate) fn drive_guest(
cpu: &mut Cpu,
mmu: &mut Mmu,
registry: &crate::win32::Registry,
state: &mut crate::win32::HostState,
target: u32,
args: &[u32],
) -> Result<u32, crate::Error> {
crate::win32::call_guest(cpu, mmu, registry, state, target, args)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_iunknown_braced_form() {
let g = Guid::parse("{00000000-0000-0000-C000-000000000046}").unwrap();
assert_eq!(g, IID_IUNKNOWN);
}
#[test]
fn parse_iclassfactory_braced_form() {
let g = Guid::parse("{00000001-0000-0000-c000-000000000046}").unwrap();
assert_eq!(g, IID_ICLASSFACTORY);
}
#[test]
fn parse_ibasefilter_braced_form() {
let g = Guid::parse("{56A86895-0AD4-11CE-B03A-0020AF0BA770}").unwrap();
assert_eq!(g, IID_IBASEFILTER);
}
#[test]
fn msadds_audio_decoder_clsid_round_trips() {
let g = Guid::parse("{22E24591-49D0-11D2-BB50-006008320064}").unwrap();
assert_eq!(g, MSADDS_AUDIO_DECODER_CLSID);
assert_eq!(
MSADDS_AUDIO_DECODER_CLSID.to_braced_string(),
"{22E24591-49D0-11D2-BB50-006008320064}"
);
}
#[test]
fn msadds_audio_property_page_clsid_round_trips() {
let g = Guid::parse("{8FE7E181-BB96-11D2-A1CB-00609778EA66}").unwrap();
assert_eq!(g, MSADDS_AUDIO_PROPERTY_PAGE_CLSID);
assert_eq!(
MSADDS_AUDIO_PROPERTY_PAGE_CLSID.to_braced_string(),
"{8FE7E181-BB96-11D2-A1CB-00609778EA66}"
);
}
#[test]
fn parse_rejects_missing_braces() {
assert!(matches!(
Guid::parse("00000000-0000-0000-C000-000000000046").unwrap_err(),
GuidParseError::WrongLength { .. }
));
assert!(matches!(
Guid::parse("[00000000-0000-0000-C000-000000000046]").unwrap_err(),
GuidParseError::MissingBraces
));
}
#[test]
fn parse_rejects_missing_hyphen() {
let bad = "{00000000+0000-0000-C000-000000000046}";
assert!(matches!(
Guid::parse(bad).unwrap_err(),
GuidParseError::MissingHyphen { at: 9 }
));
}
#[test]
fn parse_rejects_non_hex_byte() {
let bad = "{0000000Z-0000-0000-C000-000000000046}";
assert!(matches!(
Guid::parse(bad).unwrap_err(),
GuidParseError::BadHex { .. }
));
}
#[test]
fn write_le_round_trips_via_read_le() {
let bytes = IID_IBASEFILTER.write_le();
assert_eq!(Guid::read_le(&bytes), Some(IID_IBASEFILTER));
}
#[test]
fn to_braced_string_matches_string_from_guid2_format() {
assert_eq!(
IID_IUNKNOWN.to_braced_string(),
"{00000000-0000-0000-C000-000000000046}"
);
assert_eq!(
IID_IBASEFILTER.to_braced_string(),
"{56A86895-0AD4-11CE-B03A-0020AF0BA770}"
);
}
#[test]
fn stage_and_load_round_trip_via_mmu() {
use crate::emulator::mmu::Perm;
let mut mmu = Mmu::new();
mmu.map(0x9000_0000, 0x1000, Perm::R | Perm::W);
IID_IPIN.stage(&mut mmu, 0x9000_0000).unwrap();
let g = Guid::load(&mmu, 0x9000_0000).unwrap();
assert_eq!(g, IID_IPIN);
}
#[test]
fn com_object_table_intern_starts_with_zero_refcount() {
let mut t = ComObjectTable::new();
let info = t.intern(0xCAFE_BABE, Some(IID_IUNKNOWN));
assert_eq!(info.guest_addr, 0xCAFE_BABE);
assert_eq!(info.host_refcount, 0);
assert_eq!(info.last_iid, Some(IID_IUNKNOWN));
}
#[test]
fn com_object_table_addref_release_balance() {
let mut t = ComObjectTable::new();
t.intern(0x1000, None);
t.record_addref(0x1000);
t.record_addref(0x1000);
assert_eq!(t.total_refcount(), 2);
let now = t.record_release(0x1000);
assert_eq!(now, 1);
let now = t.record_release(0x1000);
assert_eq!(now, 0);
assert_eq!(t.total_refcount(), 0);
}
#[test]
fn com_object_table_register_and_lookup_class_factory() {
let mut t = ComObjectTable::new();
let clsid = Guid::parse("{4F03ADBE-9F75-4970-B9C8-EAB6A2E0EE96}").unwrap();
t.register_class_factory(clsid, 0x1234_5678);
assert_eq!(t.lookup_class_factory(&clsid), Some(0x1234_5678));
let other = Guid::parse("{00000000-0000-0000-C000-000000000046}").unwrap();
assert_eq!(t.lookup_class_factory(&other), None);
}
#[test]
fn lookup_in_process_class_only_for_iunknown_or_iclassfactory() {
let mut t = ComObjectTable::new();
let clsid = Guid::parse("{4F03ADBE-9F75-4970-B9C8-EAB6A2E0EE96}").unwrap();
t.register_class_factory(clsid, 0xAA);
assert_eq!(lookup_in_process_class(&t, clsid, IID_IUNKNOWN), Some(0xAA));
assert_eq!(
lookup_in_process_class(&t, clsid, IID_ICLASSFACTORY),
Some(0xAA)
);
assert_eq!(lookup_in_process_class(&t, clsid, IID_IBASEFILTER), None);
}
}