use super::{
Guid, IID_ICLASSFACTORY, IID_IFILTERGRAPH, IID_IMEDIASAMPLE, IID_IMEDIASAMPLE2,
IID_IMEMALLOCATOR, IID_IPIN, IID_IUNKNOWN,
};
use crate::emulator::{Cpu, Mmu};
use crate::win32::{HostState, Registry, StubFn, Win32Error};
const S_OK: u32 = 0x0000_0000;
const E_NOINTERFACE: u32 = 0x8000_4002;
const E_NOTIMPL: u32 = 0x8000_4001;
const HOST_DLL: &str = "host-com.host";
pub fn register(registry: &mut Registry) {
registry.register(HOST_DLL, "IFilterGraph::QueryInterface", qi as StubFn, 3);
registry.register(HOST_DLL, "IFilterGraph::AddRef", addref as StubFn, 1);
registry.register(HOST_DLL, "IFilterGraph::Release", release as StubFn, 1);
registry.register(HOST_DLL, "IFilterGraph::AddFilter", notimpl_3 as StubFn, 3);
registry.register(
HOST_DLL,
"IFilterGraph::RemoveFilter",
notimpl_2 as StubFn,
2,
);
registry.register(
HOST_DLL,
"IFilterGraph::EnumFilters",
notimpl_2 as StubFn,
2,
);
registry.register(
HOST_DLL,
"IFilterGraph::FindFilterByName",
notimpl_3 as StubFn,
3,
);
registry.register(
HOST_DLL,
"IFilterGraph::ConnectDirect",
notimpl_4 as StubFn,
4,
);
registry.register(HOST_DLL, "IFilterGraph::Reconnect", notimpl_2 as StubFn, 2);
registry.register(HOST_DLL, "IFilterGraph::Disconnect", notimpl_2 as StubFn, 2);
registry.register(
HOST_DLL,
"IFilterGraph::SetDefaultSyncSource",
notimpl_1 as StubFn,
1,
);
registry.register(HOST_DLL, "IPin::QueryInterface", pin_qi as StubFn, 3);
registry.register(HOST_DLL, "IPin::AddRef", addref as StubFn, 1);
registry.register(HOST_DLL, "IPin::Release", release as StubFn, 1);
registry.register(HOST_DLL, "IPin::Connect", notimpl_3 as StubFn, 3);
registry.register(HOST_DLL, "IPin::ReceiveConnection", notimpl_3 as StubFn, 3);
registry.register(HOST_DLL, "IPin::Disconnect", pin_s_ok_1 as StubFn, 1);
registry.register(HOST_DLL, "IPin::ConnectedTo", pin_connected_to as StubFn, 2);
registry.register(
HOST_DLL,
"IPin::ConnectionMediaType",
pin_connection_media_type as StubFn,
2,
);
registry.register(
HOST_DLL,
"IPin::QueryPinInfo",
pin_query_pin_info as StubFn,
2,
);
registry.register(
HOST_DLL,
"IPin::QueryDirection",
pin_query_direction as StubFn,
2,
);
registry.register(HOST_DLL, "IPin::QueryId", notimpl_2 as StubFn, 2);
registry.register(HOST_DLL, "IPin::QueryAccept", pin_s_ok_2 as StubFn, 2);
registry.register(
HOST_DLL,
"IPin::EnumMediaTypes",
pin_enum_media_types as StubFn,
2,
);
registry.register(
HOST_DLL,
"IPin::QueryInternalConnections",
notimpl_3 as StubFn,
3,
);
registry.register(HOST_DLL, "IPin::EndOfStream", pin_s_ok_1 as StubFn, 1);
registry.register(HOST_DLL, "IPin::BeginFlush", pin_s_ok_1 as StubFn, 1);
registry.register(HOST_DLL, "IPin::EndFlush", pin_s_ok_1 as StubFn, 1);
registry.register(HOST_DLL, "IPin::NewSegment", pin_s_ok_5 as StubFn, 5);
registry.register(
HOST_DLL,
"IEnumMediaTypes::QueryInterface",
enum_qi as StubFn,
3,
);
registry.register(HOST_DLL, "IEnumMediaTypes::AddRef", addref as StubFn, 1);
registry.register(HOST_DLL, "IEnumMediaTypes::Release", release as StubFn, 1);
registry.register(HOST_DLL, "IEnumMediaTypes::Next", enum_next as StubFn, 4);
registry.register(HOST_DLL, "IEnumMediaTypes::Skip", enum_skip as StubFn, 2);
registry.register(HOST_DLL, "IEnumMediaTypes::Reset", enum_reset as StubFn, 1);
registry.register(HOST_DLL, "IEnumMediaTypes::Clone", notimpl_2 as StubFn, 2);
registry.register(
HOST_DLL,
"IMemAllocator::QueryInterface",
alloc_qi as StubFn,
3,
);
registry.register(HOST_DLL, "IMemAllocator::AddRef", addref as StubFn, 1);
registry.register(HOST_DLL, "IMemAllocator::Release", release as StubFn, 1);
registry.register(
HOST_DLL,
"IMemAllocator::SetProperties",
alloc_set_properties as StubFn,
3,
);
registry.register(
HOST_DLL,
"IMemAllocator::GetProperties",
alloc_get_properties as StubFn,
2,
);
registry.register(HOST_DLL, "IMemAllocator::Commit", alloc_commit as StubFn, 1);
registry.register(
HOST_DLL,
"IMemAllocator::Decommit",
alloc_decommit as StubFn,
1,
);
registry.register(
HOST_DLL,
"IMemAllocator::GetBuffer",
alloc_get_buffer as StubFn,
5,
);
registry.register(
HOST_DLL,
"IMemAllocator::ReleaseBuffer",
alloc_release_buffer as StubFn,
2,
);
registry.register(
HOST_DLL,
"IMediaSample::QueryInterface",
sample_qi as StubFn,
3,
);
registry.register(HOST_DLL, "IMediaSample::AddRef", addref as StubFn, 1);
registry.register(
HOST_DLL,
"IMediaSample::Release",
sample_release as StubFn,
1,
);
registry.register(
HOST_DLL,
"IMediaSample::GetPointer",
sample_get_pointer as StubFn,
2,
);
registry.register(
HOST_DLL,
"IMediaSample::GetSize",
sample_get_size as StubFn,
1,
);
registry.register(
HOST_DLL,
"IMediaSample::GetTime",
sample_get_time as StubFn,
3,
);
registry.register(
HOST_DLL,
"IMediaSample::SetTime",
sample_set_time as StubFn,
3,
);
registry.register(
HOST_DLL,
"IMediaSample::IsSyncPoint",
sample_is_sync_point as StubFn,
1,
);
registry.register(
HOST_DLL,
"IMediaSample::SetSyncPoint",
sample_set_sync_point as StubFn,
2,
);
registry.register(
HOST_DLL,
"IMediaSample::IsPreroll",
sample_returns_s_false_1 as StubFn,
1,
);
registry.register(
HOST_DLL,
"IMediaSample::SetPreroll",
sample_returns_s_ok_2 as StubFn,
2,
);
registry.register(
HOST_DLL,
"IMediaSample::GetActualDataLength",
sample_get_actual_data_length as StubFn,
1,
);
registry.register(
HOST_DLL,
"IMediaSample::SetActualDataLength",
sample_set_actual_data_length as StubFn,
2,
);
registry.register(
HOST_DLL,
"IMediaSample::GetMediaType",
sample_get_media_type as StubFn,
2,
);
registry.register(
HOST_DLL,
"IMediaSample::SetMediaType",
sample_returns_s_ok_2 as StubFn,
2,
);
registry.register(
HOST_DLL,
"IMediaSample::IsDiscontinuity",
sample_returns_s_false_1 as StubFn,
1,
);
registry.register(
HOST_DLL,
"IMediaSample::SetDiscontinuity",
sample_returns_s_ok_2 as StubFn,
2,
);
registry.register(
HOST_DLL,
"IMediaSample::GetMediaTime",
sample_get_media_time as StubFn,
3,
);
registry.register(
HOST_DLL,
"IMediaSample::SetMediaTime",
sample_set_media_time as StubFn,
3,
);
registry.register(
HOST_DLL,
"IMediaSample2::GetProperties",
sample_get_properties as StubFn,
3,
);
registry.register(
HOST_DLL,
"IMediaSample2::SetProperties",
sample_set_properties as StubFn,
3,
);
registry.register(
HOST_DLL,
"IClassFactory::QueryInterface",
alloc_factory_qi as StubFn,
3,
);
registry.register(HOST_DLL, "IClassFactory::AddRef", addref as StubFn, 1);
registry.register(HOST_DLL, "IClassFactory::Release", release as StubFn, 1);
registry.register(
HOST_DLL,
"IClassFactory::CreateInstance",
alloc_factory_create_instance as StubFn,
4,
);
registry.register(
HOST_DLL,
"IClassFactory::LockServer",
alloc_factory_lock_server as StubFn,
2,
);
}
pub fn mint_host_mem_allocator(
state: &mut HostState,
mmu: &mut Mmu,
registry: &Registry,
pool_size: u32,
sample_capacity: u32,
media_type_ptr: u32,
) -> Result<u32, crate::Error> {
let obj = state.arena_alloc(96).map_err(crate::Error::Win32)?;
let vtbl = obj.wrapping_add(16);
mmu.write_initializer(obj, &vtbl.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 4, &1u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 8, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 12, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
let methods: [&str; 9] = [
"IMemAllocator::QueryInterface",
"IMemAllocator::AddRef",
"IMemAllocator::Release",
"IMemAllocator::SetProperties",
"IMemAllocator::GetProperties",
"IMemAllocator::Commit",
"IMemAllocator::Decommit",
"IMemAllocator::GetBuffer",
"IMemAllocator::ReleaseBuffer",
];
for (i, name) in methods.iter().enumerate() {
let thunk = registry
.resolve(HOST_DLL, name)
.ok_or_else(|| Win32Error::InvalidArgument {
stub: "mint_host_mem_allocator",
reason: format!("thunk {name:?} not registered"),
})
.map_err(crate::Error::Win32)?;
mmu.write_initializer(vtbl + (i as u32) * 4, &thunk.to_le_bytes())
.map_err(crate::Error::Trap)?;
}
let mut prev_link_addr: u32 = obj + 8;
for _ in 0..pool_size {
let sample = mint_host_media_sample(state, mmu, registry, sample_capacity, media_type_ptr)?;
mmu.write_initializer(prev_link_addr, &sample.to_le_bytes())
.map_err(crate::Error::Trap)?;
prev_link_addr = sample + 32;
}
mmu.write_initializer(prev_link_addr, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
Ok(obj)
}
pub fn mint_host_media_sample(
state: &mut HostState,
mmu: &mut Mmu,
registry: &Registry,
data_capacity: u32,
media_type_ptr: u32,
) -> Result<u32, crate::Error> {
let cap = data_capacity.div_ceil(16) * 16;
let header_size = 64u32 + 21 * 4; let obj = state
.arena_alloc(header_size.div_ceil(16) * 16)
.map_err(crate::Error::Win32)?;
let data_region = state
.arena_alloc(cap.max(16))
.map_err(crate::Error::Win32)?;
let vtbl = obj.wrapping_add(64);
mmu.write_initializer(obj, &vtbl.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 4, &1u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 8, &data_region.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 12, &cap.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 16, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 20, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 24, &media_type_ptr.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 28, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 32, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 36, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
for off in [40u32, 44, 48, 52, 56, 60] {
mmu.write_initializer(obj + off, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
}
let methods: [&str; 21] = [
"IMediaSample::QueryInterface",
"IMediaSample::AddRef",
"IMediaSample::Release",
"IMediaSample::GetPointer",
"IMediaSample::GetSize",
"IMediaSample::GetTime",
"IMediaSample::SetTime",
"IMediaSample::IsSyncPoint",
"IMediaSample::SetSyncPoint",
"IMediaSample::IsPreroll",
"IMediaSample::SetPreroll",
"IMediaSample::GetActualDataLength",
"IMediaSample::SetActualDataLength",
"IMediaSample::GetMediaType",
"IMediaSample::SetMediaType",
"IMediaSample::IsDiscontinuity",
"IMediaSample::SetDiscontinuity",
"IMediaSample::GetMediaTime",
"IMediaSample::SetMediaTime",
"IMediaSample2::GetProperties",
"IMediaSample2::SetProperties",
];
for (i, name) in methods.iter().enumerate() {
let thunk = registry
.resolve(HOST_DLL, name)
.ok_or_else(|| Win32Error::InvalidArgument {
stub: "mint_host_media_sample",
reason: format!("thunk {name:?} not registered"),
})
.map_err(crate::Error::Win32)?;
mmu.write_initializer(vtbl + (i as u32) * 4, &thunk.to_le_bytes())
.map_err(crate::Error::Trap)?;
}
Ok(obj)
}
pub fn media_sample_set_payload(
mmu: &mut Mmu,
sample: u32,
payload: &[u8],
sync_point: bool,
) -> Result<(), crate::Error> {
let cap = mmu.load32(sample + 12).map_err(crate::Error::Trap)?;
if (payload.len() as u32) > cap {
return Err(crate::Error::Win32(Win32Error::InvalidArgument {
stub: "media_sample_set_payload",
reason: format!("payload {} > sample cap {}", payload.len(), cap),
}));
}
let data = mmu.load32(sample + 8).map_err(crate::Error::Trap)?;
for (i, &b) in payload.iter().enumerate() {
mmu.store8(data + i as u32, b).map_err(crate::Error::Trap)?;
}
mmu.write_initializer(sample + 16, &(payload.len() as u32).to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(sample + 20, &(sync_point as u32).to_le_bytes())
.map_err(crate::Error::Trap)?;
Ok(())
}
pub fn mint_host_filter_graph(
state: &mut HostState,
mmu: &mut Mmu,
registry: &Registry,
) -> Result<u32, crate::Error> {
let obj = state.arena_alloc(64).map_err(crate::Error::Win32)?;
let vtbl = obj.wrapping_add(8);
mmu.write_initializer(obj, &vtbl.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 4, &1u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
let methods: [&str; 11] = [
"IFilterGraph::QueryInterface",
"IFilterGraph::AddRef",
"IFilterGraph::Release",
"IFilterGraph::AddFilter",
"IFilterGraph::RemoveFilter",
"IFilterGraph::EnumFilters",
"IFilterGraph::FindFilterByName",
"IFilterGraph::ConnectDirect",
"IFilterGraph::Reconnect",
"IFilterGraph::Disconnect",
"IFilterGraph::SetDefaultSyncSource",
];
for (i, name) in methods.iter().enumerate() {
let thunk = registry.resolve(HOST_DLL, name).ok_or_else(|| {
crate::Error::Win32(Win32Error::InvalidArgument {
stub: "mint_host_filter_graph",
reason: format!("host-com thunk {name:?} not registered"),
})
})?;
mmu.write_initializer(vtbl + (i as u32) * 4, &thunk.to_le_bytes())
.map_err(crate::Error::Trap)?;
}
Ok(obj)
}
pub fn mint_host_output_pin(
state: &mut HostState,
mmu: &mut Mmu,
registry: &Registry,
amt_addr: u32,
) -> Result<u32, crate::Error> {
mint_host_output_pin_with_connection(state, mmu, registry, amt_addr, 0)
}
pub fn mint_host_output_pin_with_connection(
state: &mut HostState,
mmu: &mut Mmu,
registry: &Registry,
amt_addr: u32,
connected_pin: u32,
) -> Result<u32, crate::Error> {
let obj = state.arena_alloc(112).map_err(crate::Error::Win32)?;
let vtbl = obj.wrapping_add(24);
mmu.write_initializer(obj, &vtbl.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 4, &1u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 8, &amt_addr.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 12, &connected_pin.to_le_bytes())
.map_err(crate::Error::Trap)?;
let parent_filter = super::host_iface_r31::mint_host_base_filter(state, mmu, registry, obj)?;
mmu.write_initializer(obj + 16, &parent_filter.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 20, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
let methods: [&str; 18] = [
"IPin::QueryInterface",
"IPin::AddRef",
"IPin::Release",
"IPin::Connect",
"IPin::ReceiveConnection",
"IPin::Disconnect",
"IPin::ConnectedTo",
"IPin::ConnectionMediaType",
"IPin::QueryPinInfo",
"IPin::QueryDirection",
"IPin::QueryId",
"IPin::QueryAccept",
"IPin::EnumMediaTypes",
"IPin::QueryInternalConnections",
"IPin::EndOfStream",
"IPin::BeginFlush",
"IPin::EndFlush",
"IPin::NewSegment",
];
for (i, name) in methods.iter().enumerate() {
let thunk = registry.resolve(HOST_DLL, name).ok_or_else(|| {
crate::Error::Win32(Win32Error::InvalidArgument {
stub: "mint_host_output_pin",
reason: format!("host-com thunk {name:?} not registered"),
})
})?;
mmu.write_initializer(vtbl + (i as u32) * 4, &thunk.to_le_bytes())
.map_err(crate::Error::Trap)?;
}
Ok(obj)
}
pub const DEFAULT_MEM_ALLOCATOR_FACTORY_POOL: u32 = 4;
pub const DEFAULT_MEM_ALLOCATOR_FACTORY_CAPACITY: u32 = 256 * 1024;
const CLASS_E_NOAGGREGATION: u32 = 0x8004_0110;
pub fn mint_host_mem_allocator_class_factory(
state: &mut HostState,
mmu: &mut Mmu,
registry: &Registry,
) -> Result<u32, crate::Error> {
let obj = state.arena_alloc(48).map_err(crate::Error::Win32)?;
let vtbl = obj.wrapping_add(8);
mmu.write_initializer(obj, &vtbl.to_le_bytes())
.map_err(crate::Error::Trap)?;
mmu.write_initializer(obj + 4, &1u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
let methods: [&str; 5] = [
"IClassFactory::QueryInterface",
"IClassFactory::AddRef",
"IClassFactory::Release",
"IClassFactory::CreateInstance",
"IClassFactory::LockServer",
];
for (i, name) in methods.iter().enumerate() {
let thunk = registry.resolve(HOST_DLL, name).ok_or_else(|| {
crate::Error::Win32(Win32Error::InvalidArgument {
stub: "mint_host_mem_allocator_class_factory",
reason: format!("host-com thunk {name:?} not registered"),
})
})?;
mmu.write_initializer(vtbl + (i as u32) * 4, &thunk.to_le_bytes())
.map_err(crate::Error::Trap)?;
}
Ok(obj)
}
fn alloc_factory_qi(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let piid = arg(cpu, mmu, 1)?;
let ppv = arg(cpu, mmu, 2)?;
if ppv == 0 {
return Ok(crate::com::E_POINTER);
}
let _ = mmu.write_initializer(ppv, &0u32.to_le_bytes());
if piid == 0 {
return Ok(crate::com::E_POINTER);
}
let iid = Guid::load(mmu, piid).map_err(|t| trap("HostIClassFactory::QI", t))?;
if iid == IID_IUNKNOWN || iid == IID_ICLASSFACTORY {
if let Ok(rc) = mmu.load32(this + 4) {
let _ = mmu.write_initializer(this + 4, &rc.saturating_add(1).to_le_bytes());
}
let _ = mmu.write_initializer(ppv, &this.to_le_bytes());
state.com.intern(this, Some(iid));
return Ok(S_OK);
}
Ok(E_NOINTERFACE)
}
fn alloc_factory_create_instance(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
registry: &Registry,
) -> Result<u32, Win32Error> {
let _this = arg(cpu, mmu, 0)?;
let p_unk_outer = arg(cpu, mmu, 1)?;
let p_iid = arg(cpu, mmu, 2)?;
let ppv = arg(cpu, mmu, 3)?;
if ppv == 0 {
return Ok(crate::com::E_POINTER);
}
let _ = mmu.write_initializer(ppv, &0u32.to_le_bytes());
if p_unk_outer != 0 {
return Ok(CLASS_E_NOAGGREGATION);
}
if p_iid == 0 {
return Ok(crate::com::E_POINTER);
}
let iid = Guid::load(mmu, p_iid).map_err(|t| trap("HostIClassFactory::CreateInstance", t))?;
if iid != IID_IUNKNOWN && iid != IID_IMEMALLOCATOR {
return Ok(E_NOINTERFACE);
}
let alloc = match super::mint_host_mem_allocator(
state,
mmu,
registry,
DEFAULT_MEM_ALLOCATOR_FACTORY_POOL,
DEFAULT_MEM_ALLOCATOR_FACTORY_CAPACITY,
0,
) {
Ok(a) => a,
Err(crate::Error::Win32(e)) => return Err(e),
Err(other) => {
return Err(Win32Error::InvalidArgument {
stub: "HostIClassFactory::CreateInstance",
reason: format!("mint_host_mem_allocator: {other}"),
});
}
};
state.com.intern(alloc, Some(iid));
mmu.write_initializer(ppv, &alloc.to_le_bytes())
.map_err(|t| trap("HostIClassFactory::CreateInstance", t))?;
Ok(S_OK)
}
fn alloc_factory_lock_server(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(S_OK)
}
fn mint_host_enum_media_types(
state: &mut HostState,
mmu: &mut Mmu,
registry: &Registry,
amt_addr: u32,
) -> Result<u32, Win32Error> {
let obj = state.arena_alloc(48)?;
let vtbl = obj.wrapping_add(16);
let _ = mmu.write_initializer(obj, &vtbl.to_le_bytes());
let _ = mmu.write_initializer(obj + 4, &1u32.to_le_bytes());
let _ = mmu.write_initializer(obj + 8, &amt_addr.to_le_bytes());
let _ = mmu.write_initializer(obj + 12, &0u32.to_le_bytes()); let methods: [&str; 7] = [
"IEnumMediaTypes::QueryInterface",
"IEnumMediaTypes::AddRef",
"IEnumMediaTypes::Release",
"IEnumMediaTypes::Next",
"IEnumMediaTypes::Skip",
"IEnumMediaTypes::Reset",
"IEnumMediaTypes::Clone",
];
for (i, name) in methods.iter().enumerate() {
let thunk =
registry
.resolve(HOST_DLL, name)
.ok_or_else(|| Win32Error::InvalidArgument {
stub: "mint_host_enum_media_types",
reason: format!("thunk {name:?} not registered"),
})?;
let _ = mmu.write_initializer(vtbl + (i as u32) * 4, &thunk.to_le_bytes());
}
Ok(obj)
}
fn qi(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let piid = arg(cpu, mmu, 1)?;
let ppv = arg(cpu, mmu, 2)?;
if ppv == 0 {
return Ok(crate::com::E_POINTER);
}
let _ = mmu.write_initializer(ppv, &0u32.to_le_bytes());
if piid == 0 {
return Ok(crate::com::E_POINTER);
}
let iid = Guid::load(mmu, piid).map_err(|t| trap("HostIFilterGraph::QI", t))?;
if iid == IID_IUNKNOWN || iid == IID_IFILTERGRAPH {
if let Ok(rc) = mmu.load32(this + 4) {
let _ = mmu.write_initializer(this + 4, &rc.saturating_add(1).to_le_bytes());
}
let _ = mmu.write_initializer(ppv, &this.to_le_bytes());
state.com.intern(this, Some(iid));
return Ok(S_OK);
}
Ok(E_NOINTERFACE)
}
fn addref(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let rc = mmu
.load32(this + 4)
.map_err(|t| trap("HostIFilterGraph::AddRef", t))?;
let nrc = rc.saturating_add(1);
mmu.write_initializer(this + 4, &nrc.to_le_bytes())
.map_err(|t| trap("HostIFilterGraph::AddRef", t))?;
Ok(nrc)
}
fn release(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let rc = mmu
.load32(this + 4)
.map_err(|t| trap("HostIFilterGraph::Release", t))?;
let nrc = if rc > 1 { rc - 1 } else { 1 };
mmu.write_initializer(this + 4, &nrc.to_le_bytes())
.map_err(|t| trap("HostIFilterGraph::Release", t))?;
Ok(nrc)
}
fn notimpl_1(_: &mut Cpu, _: &mut Mmu, _: &mut HostState, _: &Registry) -> Result<u32, Win32Error> {
Ok(E_NOTIMPL)
}
fn notimpl_2(_: &mut Cpu, _: &mut Mmu, _: &mut HostState, _: &Registry) -> Result<u32, Win32Error> {
Ok(E_NOTIMPL)
}
fn notimpl_3(_: &mut Cpu, _: &mut Mmu, _: &mut HostState, _: &Registry) -> Result<u32, Win32Error> {
Ok(E_NOTIMPL)
}
fn notimpl_4(_: &mut Cpu, _: &mut Mmu, _: &mut HostState, _: &Registry) -> Result<u32, Win32Error> {
Ok(E_NOTIMPL)
}
fn pin_qi(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let piid = arg(cpu, mmu, 1)?;
let ppv = arg(cpu, mmu, 2)?;
if ppv == 0 || piid == 0 {
return Ok(crate::com::E_POINTER);
}
let _ = mmu.write_initializer(ppv, &0u32.to_le_bytes());
let iid = Guid::load(mmu, piid).map_err(|t| trap("HostIPin::QI", t))?;
if iid == IID_IUNKNOWN || iid == IID_IPIN {
if let Ok(rc) = mmu.load32(this + 4) {
let _ = mmu.write_initializer(this + 4, &rc.saturating_add(1).to_le_bytes());
}
let _ = mmu.write_initializer(ppv, &this.to_le_bytes());
state.com.intern(this, Some(iid));
return Ok(S_OK);
}
Ok(E_NOINTERFACE)
}
fn pin_query_direction(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _this = arg(cpu, mmu, 0)?;
let p_pin_dir = arg(cpu, mmu, 1)?;
if p_pin_dir == 0 {
return Ok(crate::com::E_POINTER);
}
mmu.write_initializer(p_pin_dir, &1u32.to_le_bytes())
.map_err(|t| trap("HostIPin::QueryDirection", t))?;
Ok(S_OK)
}
fn pin_s_ok_2(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(S_OK)
}
fn pin_s_ok_1(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(S_OK)
}
fn pin_s_ok_5(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(S_OK)
}
fn pin_connection_media_type(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let pmt = arg(cpu, mmu, 1)?;
if pmt == 0 {
return Ok(crate::com::E_POINTER);
}
let amt_src = mmu
.load32(this + 8)
.map_err(|t| trap("HostIPin::ConnectionMediaType", t))?;
if amt_src == 0 {
return Ok(0x8004_0211 );
}
for i in 0..72u32 {
let b = mmu
.load8(amt_src + i)
.map_err(|t| trap("HostIPin::ConnectionMediaType", t))?;
mmu.store8(pmt + i, b)
.map_err(|t| trap("HostIPin::ConnectionMediaType", t))?;
}
Ok(S_OK)
}
fn pin_enum_media_types(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let pp = arg(cpu, mmu, 1)?;
if pp == 0 {
return Ok(crate::com::E_POINTER);
}
let amt_src = mmu
.load32(this + 8)
.map_err(|t| trap("HostIPin::EnumMediaTypes", t))?;
let new_enum = mint_host_enum_media_types(state, mmu, registry, amt_src)?;
mmu.write_initializer(pp, &new_enum.to_le_bytes())
.map_err(|t| trap("HostIPin::EnumMediaTypes", t))?;
Ok(S_OK)
}
fn pin_query_pin_info(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let p_info = arg(cpu, mmu, 1)?;
if p_info == 0 {
return Ok(crate::com::E_POINTER);
}
let parent_filter = mmu
.load32(this + 16)
.map_err(|t| trap("HostIPin::QueryPinInfo", t))?;
mmu.write_initializer(p_info, &parent_filter.to_le_bytes())
.map_err(|t| trap("HostIPin::QueryPinInfo", t))?;
if parent_filter != 0 {
if let Ok(rc) = mmu.load32(parent_filter + 4) {
let _ = mmu.write_initializer(parent_filter + 4, &rc.saturating_add(1).to_le_bytes());
}
state
.com
.intern(parent_filter, Some(crate::com::IID_IBASEFILTER));
}
mmu.write_initializer(p_info + 4, &1u32.to_le_bytes())
.map_err(|t| trap("HostIPin::QueryPinInfo", t))?;
let name_utf16: [u16; 11] = [
b'H' as u16,
b'o' as u16,
b's' as u16,
b't' as u16,
b'O' as u16,
b'u' as u16,
b't' as u16,
b'P' as u16,
b'i' as u16,
b'n' as u16,
0,
];
for (i, w) in name_utf16.iter().enumerate() {
mmu.write_initializer(p_info + 8 + (i as u32) * 2, &w.to_le_bytes())
.map_err(|t| trap("HostIPin::QueryPinInfo", t))?;
}
for off in (8 + 22)..(8 + 256u32) {
mmu.store8(p_info + off, 0)
.map_err(|t| trap("HostIPin::QueryPinInfo", t))?;
}
record_query_pin_info_call(state, this);
Ok(S_OK)
}
fn pin_connected_to(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let pp = arg(cpu, mmu, 1)?;
if pp == 0 {
return Ok(crate::com::E_POINTER);
}
let _ = mmu.write_initializer(pp, &0u32.to_le_bytes());
let connected = mmu
.load32(this + 12)
.map_err(|t| trap("HostIPin::ConnectedTo", t))?;
if connected == 0 {
return Ok(0x8004_0209);
}
if let Ok(rc) = mmu.load32(connected + 4) {
let _ = mmu.write_initializer(connected + 4, &rc.saturating_add(1).to_le_bytes());
}
state.com.intern(connected, Some(crate::com::IID_IPIN));
mmu.write_initializer(pp, &connected.to_le_bytes())
.map_err(|t| trap("HostIPin::ConnectedTo", t))?;
Ok(S_OK)
}
fn enum_qi(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let piid = arg(cpu, mmu, 1)?;
let ppv = arg(cpu, mmu, 2)?;
if ppv == 0 || piid == 0 {
return Ok(crate::com::E_POINTER);
}
let _ = mmu.write_initializer(ppv, &0u32.to_le_bytes());
let iid = Guid::load(mmu, piid).map_err(|t| trap("HostIEnum::QI", t))?;
if iid == IID_IUNKNOWN {
if let Ok(rc) = mmu.load32(this + 4) {
let _ = mmu.write_initializer(this + 4, &rc.saturating_add(1).to_le_bytes());
}
let _ = mmu.write_initializer(ppv, &this.to_le_bytes());
state.com.intern(this, Some(iid));
return Ok(S_OK);
}
Ok(E_NOINTERFACE)
}
fn enum_next(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let c = arg(cpu, mmu, 1)?;
let pp = arg(cpu, mmu, 2)?;
let p_fetched = arg(cpu, mmu, 3)?;
if pp == 0 {
return Ok(crate::com::E_POINTER);
}
let cursor = mmu.load32(this + 12).unwrap_or(0);
if c == 0 {
if p_fetched != 0 {
let _ = mmu.write_initializer(p_fetched, &0u32.to_le_bytes());
}
return Ok(S_OK);
}
if cursor == 0 {
let amt = mmu
.load32(this + 8)
.map_err(|t| trap("HostIEnum::Next", t))?;
mmu.write_initializer(pp, &amt.to_le_bytes())
.map_err(|t| trap("HostIEnum::Next", t))?;
if p_fetched != 0 {
let _ = mmu.write_initializer(p_fetched, &1u32.to_le_bytes());
}
let _ = mmu.write_initializer(this + 12, &1u32.to_le_bytes());
if c == 1 {
return Ok(S_OK);
}
return Ok(crate::com::S_FALSE);
}
let _ = mmu.write_initializer(pp, &0u32.to_le_bytes());
if p_fetched != 0 {
let _ = mmu.write_initializer(p_fetched, &0u32.to_le_bytes());
}
Ok(crate::com::S_FALSE)
}
fn enum_skip(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let _ = mmu.write_initializer(this + 12, &1u32.to_le_bytes());
Ok(S_OK)
}
fn enum_reset(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let _ = mmu.write_initializer(this + 12, &0u32.to_le_bytes());
Ok(S_OK)
}
fn alloc_qi(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let piid = arg(cpu, mmu, 1)?;
let ppv = arg(cpu, mmu, 2)?;
if ppv == 0 || piid == 0 {
return Ok(crate::com::E_POINTER);
}
let _ = mmu.write_initializer(ppv, &0u32.to_le_bytes());
let iid = Guid::load(mmu, piid).map_err(|t| trap("HostIMemAllocator::QI", t))?;
if iid == IID_IUNKNOWN || iid == IID_IMEMALLOCATOR {
if let Ok(rc) = mmu.load32(this + 4) {
let _ = mmu.write_initializer(this + 4, &rc.saturating_add(1).to_le_bytes());
}
let _ = mmu.write_initializer(ppv, &this.to_le_bytes());
state.com.intern(this, Some(iid));
return Ok(S_OK);
}
Ok(E_NOINTERFACE)
}
fn alloc_set_properties(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let p_request = arg(cpu, mmu, 1)?;
let p_actual = arg(cpu, mmu, 2)?;
if p_actual != 0 && p_request != 0 {
for i in 0..16u32 {
let b = mmu
.load8(p_request + i)
.map_err(|t| trap("HostIMemAllocator::SetProperties", t))?;
mmu.store8(p_actual + i, b)
.map_err(|t| trap("HostIMemAllocator::SetProperties", t))?;
}
}
if p_request != 0 {
let c_buffers = mmu
.load32(p_request)
.map_err(|t| trap("HostIMemAllocator::SetProperties", t))?;
let cb_buffer = mmu
.load32(p_request + 4)
.map_err(|t| trap("HostIMemAllocator::SetProperties", t))?;
let cb_align = mmu
.load32(p_request + 8)
.map_err(|t| trap("HostIMemAllocator::SetProperties", t))?;
let cb_prefix = mmu
.load32(p_request + 12)
.map_err(|t| trap("HostIMemAllocator::SetProperties", t))?;
record_set_properties(
state,
AllocatorPropertiesCapture {
this,
c_buffers,
cb_buffer,
cb_align,
cb_prefix,
},
);
}
Ok(S_OK)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AllocatorPropertiesCapture {
pub this: u32,
pub c_buffers: u32,
pub cb_buffer: u32,
pub cb_align: u32,
pub cb_prefix: u32,
}
fn set_properties_log(
) -> &'static std::sync::Mutex<std::collections::HashMap<usize, Vec<AllocatorPropertiesCapture>>> {
static L: std::sync::OnceLock<
std::sync::Mutex<std::collections::HashMap<usize, Vec<AllocatorPropertiesCapture>>>,
> = std::sync::OnceLock::new();
L.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}
fn host_key(state: &HostState) -> usize {
state as *const HostState as usize
}
fn record_set_properties(state: &HostState, cap: AllocatorPropertiesCapture) {
if let Ok(mut l) = set_properties_log().lock() {
l.entry(host_key(state)).or_default().push(cap);
}
}
pub fn last_set_properties(state: &HostState) -> Option<AllocatorPropertiesCapture> {
set_properties_log()
.lock()
.ok()
.and_then(|l| l.get(&host_key(state)).and_then(|v| v.last().copied()))
}
pub fn all_set_properties(state: &HostState) -> Vec<AllocatorPropertiesCapture> {
set_properties_log()
.lock()
.ok()
.map(|l| l.get(&host_key(state)).cloned().unwrap_or_default())
.unwrap_or_default()
}
pub fn clear_set_properties_log(state: &HostState) {
if let Ok(mut l) = set_properties_log().lock() {
l.remove(&host_key(state));
}
}
fn query_pin_info_log() -> &'static std::sync::Mutex<std::collections::HashMap<usize, Vec<u32>>> {
static L: std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<usize, Vec<u32>>>> =
std::sync::OnceLock::new();
L.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}
fn query_filter_info_log() -> &'static std::sync::Mutex<std::collections::HashMap<usize, Vec<u32>>>
{
static L: std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<usize, Vec<u32>>>> =
std::sync::OnceLock::new();
L.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}
fn record_query_pin_info_call(state: &HostState, this: u32) {
if let Ok(mut l) = query_pin_info_log().lock() {
l.entry(host_key(state)).or_default().push(this);
}
}
pub(crate) fn record_query_filter_info_call(state: &HostState, this: u32) {
if let Ok(mut l) = query_filter_info_log().lock() {
l.entry(host_key(state)).or_default().push(this);
}
}
pub fn query_pin_info_call_count(state: &HostState) -> usize {
query_pin_info_log()
.lock()
.ok()
.map(|l| l.get(&host_key(state)).map(|v| v.len()).unwrap_or(0))
.unwrap_or(0)
}
pub fn query_filter_info_call_count(state: &HostState) -> usize {
query_filter_info_log()
.lock()
.ok()
.map(|l| l.get(&host_key(state)).map(|v| v.len()).unwrap_or(0))
.unwrap_or(0)
}
pub fn query_pin_info_calls(state: &HostState) -> Vec<u32> {
query_pin_info_log()
.lock()
.ok()
.map(|l| l.get(&host_key(state)).cloned().unwrap_or_default())
.unwrap_or_default()
}
pub fn query_filter_info_calls(state: &HostState) -> Vec<u32> {
query_filter_info_log()
.lock()
.ok()
.map(|l| l.get(&host_key(state)).cloned().unwrap_or_default())
.unwrap_or_default()
}
pub fn clear_query_info_log(state: &HostState) {
if let Ok(mut l) = query_pin_info_log().lock() {
l.remove(&host_key(state));
}
if let Ok(mut l) = query_filter_info_log().lock() {
l.remove(&host_key(state));
}
}
fn alloc_get_properties(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let p_props = arg(cpu, mmu, 1)?;
if p_props == 0 {
return Ok(crate::com::E_POINTER);
}
let mut count: u32 = 0;
let mut cur = mmu
.load32(this + 8)
.map_err(|t| trap("HostIMemAllocator::GetProperties", t))?;
let mut buf_size: u32 = 0;
while cur != 0 && count < 1024 {
if count == 0 {
buf_size = mmu
.load32(cur + 12)
.map_err(|t| trap("HostIMemAllocator::GetProperties", t))?;
}
count += 1;
cur = mmu
.load32(cur + 32)
.map_err(|t| trap("HostIMemAllocator::GetProperties", t))?;
}
mmu.write_initializer(p_props, &count.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::GetProperties", t))?;
mmu.write_initializer(p_props + 4, &buf_size.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::GetProperties", t))?;
mmu.write_initializer(p_props + 8, &1u32.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::GetProperties", t))?;
mmu.write_initializer(p_props + 12, &0u32.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::GetProperties", t))?;
Ok(S_OK)
}
fn alloc_commit(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
mmu.write_initializer(this + 12, &1u32.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::Commit", t))?;
Ok(S_OK)
}
fn alloc_decommit(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
mmu.write_initializer(this + 12, &0u32.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::Decommit", t))?;
Ok(S_OK)
}
fn alloc_get_buffer(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let pp = arg(cpu, mmu, 1)?;
let _p_start = arg(cpu, mmu, 2)?;
let _p_end = arg(cpu, mmu, 3)?;
let _dw_flags = arg(cpu, mmu, 4)?;
if pp == 0 {
return Ok(crate::com::E_POINTER);
}
let _ = mmu.write_initializer(pp, &0u32.to_le_bytes());
let committed = mmu
.load32(this + 12)
.map_err(|t| trap("HostIMemAllocator::GetBuffer", t))?;
if committed == 0 {
return Ok(0x8004_0209 );
}
let mut cur = mmu
.load32(this + 8)
.map_err(|t| trap("HostIMemAllocator::GetBuffer", t))?;
let mut steps = 0u32;
while cur != 0 && steps < 1024 {
let in_use = match mmu.load32(cur + 36) {
Ok(v) => v,
Err(_) => {
return Ok(0x8004_0211 );
}
};
if in_use == 0 {
mmu.write_initializer(cur + 36, &1u32.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::GetBuffer", t))?;
mmu.write_initializer(cur + 4, &1u32.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::GetBuffer", t))?;
mmu.write_initializer(pp, &cur.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::GetBuffer", t))?;
return Ok(S_OK);
}
cur = match mmu.load32(cur + 32) {
Ok(v) => v,
Err(_) => {
return Ok(0x8004_0211 );
}
};
steps += 1;
}
Ok(0x8004_0211 )
}
fn alloc_release_buffer(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _this = arg(cpu, mmu, 0)?;
let sample = arg(cpu, mmu, 1)?;
if sample == 0 {
return Ok(crate::com::E_POINTER);
}
mmu.write_initializer(sample + 36, &0u32.to_le_bytes())
.map_err(|t| trap("HostIMemAllocator::ReleaseBuffer", t))?;
if let Ok(rc) = mmu.load32(sample + 4) {
let nrc = if rc > 1 { rc - 1 } else { 1 };
let _ = mmu.write_initializer(sample + 4, &nrc.to_le_bytes());
}
Ok(S_OK)
}
fn sample_release(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let rc = mmu
.load32(this + 4)
.map_err(|t| trap("HostIMediaSample::Release", t))?;
let nrc = rc.saturating_sub(1);
mmu.write_initializer(this + 4, &nrc.to_le_bytes())
.map_err(|t| trap("HostIMediaSample::Release", t))?;
if nrc == 0 {
let _ = mmu.write_initializer(this + 36, &0u32.to_le_bytes());
}
Ok(nrc)
}
fn sample_qi(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let piid = arg(cpu, mmu, 1)?;
let ppv = arg(cpu, mmu, 2)?;
if ppv == 0 || piid == 0 {
return Ok(crate::com::E_POINTER);
}
let _ = mmu.write_initializer(ppv, &0u32.to_le_bytes());
let iid = Guid::load(mmu, piid).map_err(|t| trap("HostIMediaSample::QI", t))?;
if iid == IID_IUNKNOWN || iid == IID_IMEDIASAMPLE || iid == IID_IMEDIASAMPLE2 {
if let Ok(rc) = mmu.load32(this + 4) {
let _ = mmu.write_initializer(this + 4, &rc.saturating_add(1).to_le_bytes());
}
let _ = mmu.write_initializer(ppv, &this.to_le_bytes());
state.com.intern(this, Some(iid));
return Ok(S_OK);
}
Ok(E_NOINTERFACE)
}
fn sample_get_pointer(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let pp = arg(cpu, mmu, 1)?;
if pp == 0 {
return Ok(crate::com::E_POINTER);
}
let data = mmu
.load32(this + 8)
.map_err(|t| trap("HostIMediaSample::GetPointer", t))?;
mmu.write_initializer(pp, &data.to_le_bytes())
.map_err(|t| trap("HostIMediaSample::GetPointer", t))?;
Ok(S_OK)
}
fn sample_get_size(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let cap = mmu
.load32(this + 12)
.map_err(|t| trap("HostIMediaSample::GetSize", t))?;
Ok(cap)
}
fn sample_get_time(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _this = arg(cpu, mmu, 0)?;
let p_start = arg(cpu, mmu, 1)?;
let p_end = arg(cpu, mmu, 2)?;
if p_start != 0 {
let _ = mmu.write_initializer(p_start, &[0u8; 8]);
}
if p_end != 0 {
let _ = mmu.write_initializer(p_end, &[0u8; 8]);
}
Ok(0x0004_0007 )
}
fn sample_set_time(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(S_OK)
}
fn sample_is_sync_point(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let flag = mmu
.load32(this + 20)
.map_err(|t| trap("HostIMediaSample::IsSyncPoint", t))?;
if flag != 0 {
Ok(S_OK)
} else {
Ok(crate::com::S_FALSE)
}
}
fn sample_set_sync_point(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let v = arg(cpu, mmu, 1)?;
mmu.write_initializer(this + 20, &(if v != 0 { 1u32 } else { 0 }).to_le_bytes())
.map_err(|t| trap("HostIMediaSample::SetSyncPoint", t))?;
Ok(S_OK)
}
fn sample_get_actual_data_length(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let len = mmu
.load32(this + 16)
.map_err(|t| trap("HostIMediaSample::GetActualDataLength", t))?;
Ok(len)
}
fn sample_set_actual_data_length(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let cb = arg(cpu, mmu, 1)?;
let cap = mmu
.load32(this + 12)
.map_err(|t| trap("HostIMediaSample::SetActualDataLength", t))?;
let n = cb.min(cap);
mmu.write_initializer(this + 16, &n.to_le_bytes())
.map_err(|t| trap("HostIMediaSample::SetActualDataLength", t))?;
Ok(S_OK)
}
fn sample_get_media_type(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let pp = arg(cpu, mmu, 1)?;
if pp == 0 {
return Ok(crate::com::E_POINTER);
}
let mt = mmu
.load32(this + 24)
.map_err(|t| trap("HostIMediaSample::GetMediaType", t))?;
mmu.write_initializer(pp, &mt.to_le_bytes())
.map_err(|t| trap("HostIMediaSample::GetMediaType", t))?;
if mt == 0 {
Ok(crate::com::S_FALSE)
} else {
Ok(S_OK)
}
}
fn sample_get_media_time(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _this = arg(cpu, mmu, 0)?;
let _p_start = arg(cpu, mmu, 1)?;
let _p_end = arg(cpu, mmu, 2)?;
Ok(0x8004_0251 )
}
fn sample_returns_s_false_1(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(crate::com::S_FALSE)
}
fn sample_returns_s_ok_2(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(S_OK)
}
fn sample_set_media_time(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(S_OK)
}
fn sample_get_properties(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let cb = arg(cpu, mmu, 1)?;
let p_props = arg(cpu, mmu, 2)?;
if p_props == 0 {
return Ok(crate::com::E_POINTER);
}
let actual_len = mmu
.load32(this + 16)
.map_err(|t| trap("HostIMediaSample2::GetProperties", t))?;
let sync_flag = mmu
.load32(this + 20)
.map_err(|t| trap("HostIMediaSample2::GetProperties", t))?;
let media_type_ptr = mmu
.load32(this + 24)
.map_err(|t| trap("HostIMediaSample2::GetProperties", t))?;
let data_region = mmu
.load32(this + 8)
.map_err(|t| trap("HostIMediaSample2::GetProperties", t))?;
let cap = mmu
.load32(this + 12)
.map_err(|t| trap("HostIMediaSample2::GetProperties", t))?;
let sample_flags = if sync_flag != 0 { 0x10u32 } else { 0u32 };
let mut props = [0u8; 0x30];
let mut put = |off: usize, v: u32| {
props[off..off + 4].copy_from_slice(&v.to_le_bytes());
};
put(0x00, 0x30); put(0x04, 0); put(0x08, sample_flags); put(0x0c, actual_len); put(0x20, 0); put(0x24, media_type_ptr); put(0x28, data_region); put(0x2c, cap); let n = (cb as usize).min(0x30);
for (i, &b) in props.iter().take(n).enumerate() {
mmu.store8(p_props + (i as u32), b)
.map_err(|t| trap("HostIMediaSample2::GetProperties", t))?;
}
Ok(S_OK)
}
fn sample_set_properties(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let this = arg(cpu, mmu, 0)?;
let cb = arg(cpu, mmu, 1)?;
let p_props = arg(cpu, mmu, 2)?;
if p_props == 0 {
return Ok(crate::com::E_POINTER);
}
if cb >= 0x10 {
let flags = mmu
.load32(p_props + 0x08)
.map_err(|t| trap("HostIMediaSample2::SetProperties", t))?;
let actual = mmu
.load32(p_props + 0x0c)
.map_err(|t| trap("HostIMediaSample2::SetProperties", t))?;
let cap = mmu
.load32(this + 12)
.map_err(|t| trap("HostIMediaSample2::SetProperties", t))?;
let n = actual.min(cap);
let _ = mmu.write_initializer(this + 16, &n.to_le_bytes());
let sync = u32::from(flags & 0x10 != 0);
let _ = mmu.write_initializer(this + 20, &sync.to_le_bytes());
}
Ok(S_OK)
}
fn arg(cpu: &Cpu, mmu: &Mmu, n: u32) -> Result<u32, Win32Error> {
crate::win32::arg_dword(cpu, mmu, n).map_err(|t| trap("HostIFilterGraph::arg", t))
}
fn trap(stub: &'static str, t: crate::emulator::Trap) -> Win32Error {
Win32Error::InvalidArgument {
stub,
reason: format!("{t}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::com::call::call_method;
use crate::Sandbox;
#[test]
fn host_filter_graph_layout_has_eleven_method_slots() {
let mut sb = Sandbox::new();
let g = mint_host_filter_graph(&mut sb.host, &mut sb.mmu, &sb.registry).unwrap();
let vtbl = sb.mmu.load32(g).unwrap();
assert_eq!(vtbl, g + 8);
for i in 0..11u32 {
let m = sb.mmu.load32(vtbl + i * 4).unwrap();
assert!(m != 0, "vtbl slot {i} unmapped");
assert!(
sb.registry.is_thunk(m),
"vtbl slot {i} = {m:#010x} not a registered thunk"
);
}
}
#[test]
fn host_filter_graph_addref_release_round_trip() {
let mut sb = Sandbox::new();
let g = mint_host_filter_graph(&mut sb.host, &mut sb.mmu, &sb.registry).unwrap();
assert_eq!(sb.mmu.load32(g + 4).unwrap(), 1);
let r = call_method(
&mut sb.cpu,
&mut sb.mmu,
&sb.registry,
&mut sb.host,
g,
crate::com::SLOT_ADD_REF,
&[],
)
.unwrap();
assert_eq!(r, 2);
assert_eq!(sb.mmu.load32(g + 4).unwrap(), 2);
let r = call_method(
&mut sb.cpu,
&mut sb.mmu,
&sb.registry,
&mut sb.host,
g,
crate::com::SLOT_RELEASE,
&[],
)
.unwrap();
assert_eq!(r, 1);
let r = call_method(
&mut sb.cpu,
&mut sb.mmu,
&sb.registry,
&mut sb.host,
g,
crate::com::SLOT_RELEASE,
&[],
)
.unwrap();
assert_eq!(r, 1);
}
#[test]
fn host_filter_graph_query_interface_for_iunknown_returns_self() {
let mut sb = Sandbox::new();
let g = mint_host_filter_graph(&mut sb.host, &mut sb.mmu, &sb.registry).unwrap();
let scratch = sb.host.arena_alloc(20).unwrap();
IID_IUNKNOWN.stage(&mut sb.mmu, scratch).unwrap();
sb.mmu
.write_initializer(scratch + 16, &0u32.to_le_bytes())
.unwrap();
let r = call_method(
&mut sb.cpu,
&mut sb.mmu,
&sb.registry,
&mut sb.host,
g,
crate::com::SLOT_QUERY_INTERFACE,
&[scratch, scratch + 16],
)
.unwrap();
assert_eq!(r, S_OK);
assert_eq!(sb.mmu.load32(scratch + 16).unwrap(), g);
}
#[test]
fn host_filter_graph_query_interface_unknown_iid_rejected() {
let mut sb = Sandbox::new();
let g = mint_host_filter_graph(&mut sb.host, &mut sb.mmu, &sb.registry).unwrap();
let scratch = sb.host.arena_alloc(20).unwrap();
crate::com::IID_IBASEFILTER
.stage(&mut sb.mmu, scratch)
.unwrap();
sb.mmu
.write_initializer(scratch + 16, &0u32.to_le_bytes())
.unwrap();
let r = call_method(
&mut sb.cpu,
&mut sb.mmu,
&sb.registry,
&mut sb.host,
g,
crate::com::SLOT_QUERY_INTERFACE,
&[scratch, scratch + 16],
)
.unwrap();
assert_eq!(r, E_NOINTERFACE);
assert_eq!(sb.mmu.load32(scratch + 16).unwrap(), 0);
}
#[test]
fn host_filter_graph_addfilter_returns_e_notimpl() {
let mut sb = Sandbox::new();
let g = mint_host_filter_graph(&mut sb.host, &mut sb.mmu, &sb.registry).unwrap();
let r = call_method(
&mut sb.cpu,
&mut sb.mmu,
&sb.registry,
&mut sb.host,
g,
3,
&[0xDEAD_BEEF, 0xCAFE_F00D],
)
.unwrap();
assert_eq!(r, E_NOTIMPL);
}
}