ud_emulator/com/mod.rs
1//! COM (Component Object Model) scaffolding — round 25, stage 1.
2//!
3//! Round 24 closed with the verdict that `WMVDS32.AX` and
4//! `MPG4DS32.AX` lack a `DriverProc` export entirely: they are
5//! pure DirectShow filters that expose `DllGetClassObject` plus
6//! a family of `IBaseFilter`-derived COM objects. The VfW
7//! `DriverProc` ABI is fundamentally absent in the wmpcdcs8-2001
8//! bundle for those two binaries, so any path that wants to
9//! decode WMV through them must reach in through the DirectShow
10//! IBaseFilter ABI instead.
11//!
12//! This module is the foundation for that work. It introduces
13//! just enough COM machinery to:
14//!
15//! * Describe an interface identifier ([`Guid`]) — including a
16//! parser from the canonical MIDL `{xxxxxxxx-xxxx-xxxx-xxxx-…}`
17//! string form so the IID constants below read like the
18//! header files.
19//! * Hardcode the IIDs we will care about (IUnknown,
20//! IClassFactory, IBaseFilter, IPin, IMemInputPin, IEnumPins,
21//! IMemAllocator, IMediaSample, IFilterGraph) so later stages
22//! can cite them without re-quoting the GUIDs in three places.
23//! * Track guest-side COM objects ([`ComObjectTable`]) — when a
24//! guest interface pointer leaves the sandbox into our test
25//! harness, we register it here so `Release` semantics are
26//! bookkept on the host side: the table records the refcount
27//! each side believes the object holds. We do **not** crack
28//! the guest vtable pointer — calls into vtable methods just
29//! reach through guest memory like any other indirect call,
30//! driven by [`call_method`].
31//! * Public ABI HRESULT constants so test assertions read like
32//! the MSDN reference (`S_OK`, `E_NOINTERFACE`, `E_NOTIMPL`).
33//!
34//! ### Reference material
35//!
36//! All interface signatures, GUID values, and HRESULT semantics
37//! come from Microsoft's public ABI documentation:
38//!
39//! * "Component Object Model (COM)" — IUnknown reference.
40//! <https://learn.microsoft.com/en-us/windows/win32/com/component-object-model--com-->
41//! * "DirectShow Reference" — IBaseFilter / IPin / IMemInputPin /
42//! IMemAllocator / IMediaSample / IFilterGraph interface
43//! references.
44//! <https://learn.microsoft.com/en-us/windows/win32/directshow/directshow-reference>
45//! * Windows SDK headers (`unknwn.h`, `axextend.h`, `strmif.h`,
46//! `amvideo.h`) — header ABI declarations only.
47//!
48//! We do NOT consult the DirectShow BaseClasses sample source
49//! (`CBaseFilter` / `CTransformFilter` `.cpp`); only the public
50//! interface signatures, GUID values, and HRESULT semantics from
51//! MSDN + the MIDL-generated header declarations.
52
53use crate::emulator::{Cpu, Mmu};
54
55pub mod asf_amt;
56pub mod call;
57pub mod host_iface;
58pub mod host_iface_r31;
59
60pub use asf_amt::{
61 extract_wma_amt_from_asf, locate_first_data_packet, AmtBlueprint, AsfParseError,
62 ASF_AUDIO_MEDIA, ASF_HEADER_OBJECT, ASF_STREAM_PROPERTIES_OBJECT,
63};
64pub use call::{add_ref, call_method, query_interface, release};
65pub use host_iface::{
66 all_set_properties, clear_query_info_log, clear_set_properties_log, last_set_properties,
67 media_sample_set_payload, mint_host_filter_graph, mint_host_media_sample,
68 mint_host_mem_allocator, mint_host_mem_allocator_class_factory,
69 mint_host_output_pin_with_connection, query_filter_info_call_count, query_filter_info_calls,
70 query_pin_info_call_count, query_pin_info_calls, AllocatorPropertiesCapture,
71 DEFAULT_MEM_ALLOCATOR_FACTORY_CAPACITY, DEFAULT_MEM_ALLOCATOR_FACTORY_POOL,
72};
73
74/// Canonical 128-bit globally-unique identifier. Layout matches
75/// the MIDL `GUID` struct in `guiddef.h`:
76///
77/// ```c
78/// typedef struct _GUID {
79/// unsigned long Data1;
80/// unsigned short Data2;
81/// unsigned short Data3;
82/// unsigned char Data4[8];
83/// } GUID;
84/// ```
85///
86/// Stored canonically (little-endian on `Data1..3`, raw bytes on
87/// `Data4`) so that [`Self::write_le`] / [`Self::read_le`]
88/// round-trip with the in-memory layout the codec sees.
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
90pub struct Guid {
91 pub data1: u32,
92 pub data2: u16,
93 pub data3: u16,
94 pub data4: [u8; 8],
95}
96
97impl Guid {
98 /// Build a `Guid` from its four wire-form fields.
99 pub const fn new(data1: u32, data2: u16, data3: u16, data4: [u8; 8]) -> Self {
100 Guid {
101 data1,
102 data2,
103 data3,
104 data4,
105 }
106 }
107
108 /// Parse the canonical MIDL string form
109 /// `{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}`. Both the curly
110 /// braces and the hyphens at the standard positions are
111 /// required; case-insensitive on hex digits. Returns
112 /// [`GuidParseError`] on any deviation.
113 ///
114 /// This is a `const`-style parser used in tests to stage
115 /// canned IIDs without writing out the four-field struct
116 /// literal. The hardcoded IID constants below use
117 /// [`Self::new`] for readability + `const` evaluability.
118 pub fn parse(s: &str) -> Result<Self, GuidParseError> {
119 let bytes = s.as_bytes();
120 if bytes.len() != 38 {
121 return Err(GuidParseError::WrongLength { len: bytes.len() });
122 }
123 if bytes[0] != b'{' || bytes[37] != b'}' {
124 return Err(GuidParseError::MissingBraces);
125 }
126 // Hyphen positions: 9, 14, 19, 24 (1-based, after '{').
127 for &i in &[9usize, 14, 19, 24] {
128 if bytes[i] != b'-' {
129 return Err(GuidParseError::MissingHyphen { at: i });
130 }
131 }
132 let hex = |start: usize, len: usize| -> Result<u64, GuidParseError> {
133 let mut acc: u64 = 0;
134 for i in 0..len {
135 let c = bytes[start + i];
136 let nib = match c {
137 b'0'..=b'9' => (c - b'0') as u64,
138 b'a'..=b'f' => (c - b'a' + 10) as u64,
139 b'A'..=b'F' => (c - b'A' + 10) as u64,
140 _ => {
141 return Err(GuidParseError::BadHex {
142 at: start + i,
143 byte: c,
144 });
145 }
146 };
147 acc = (acc << 4) | nib;
148 }
149 Ok(acc)
150 };
151 let data1 = hex(1, 8)? as u32;
152 let data2 = hex(10, 4)? as u16;
153 let data3 = hex(15, 4)? as u16;
154 let mut data4 = [0u8; 8];
155 for (i, slot) in data4.iter_mut().enumerate().take(2) {
156 *slot = hex(20 + 2 * i, 2)? as u8;
157 }
158 for (i, slot) in data4.iter_mut().enumerate().skip(2) {
159 *slot = hex(25 + 2 * (i - 2), 2)? as u8;
160 }
161 Ok(Guid {
162 data1,
163 data2,
164 data3,
165 data4,
166 })
167 }
168
169 /// Format back into the canonical MIDL string form, in
170 /// upper-case hex (which is what `StringFromGUID2` emits).
171 pub fn to_braced_string(self) -> String {
172 format!(
173 "{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
174 self.data1,
175 self.data2,
176 self.data3,
177 self.data4[0],
178 self.data4[1],
179 self.data4[2],
180 self.data4[3],
181 self.data4[4],
182 self.data4[5],
183 self.data4[6],
184 self.data4[7],
185 )
186 }
187
188 /// Encode the GUID into 16 bytes in the wire layout (LE on
189 /// the first three fields, raw on `Data4`). Suitable for
190 /// staging the GUID into guest memory before passing the
191 /// pointer to a vtable method.
192 pub fn write_le(self) -> [u8; 16] {
193 let mut out = [0u8; 16];
194 out[0..4].copy_from_slice(&self.data1.to_le_bytes());
195 out[4..6].copy_from_slice(&self.data2.to_le_bytes());
196 out[6..8].copy_from_slice(&self.data3.to_le_bytes());
197 out[8..16].copy_from_slice(&self.data4);
198 out
199 }
200
201 /// Decode 16 bytes laid out per [`Self::write_le`]. Returns
202 /// `None` if `bytes.len() < 16`.
203 pub fn read_le(bytes: &[u8]) -> Option<Self> {
204 if bytes.len() < 16 {
205 return None;
206 }
207 let data1 = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
208 let data2 = u16::from_le_bytes([bytes[4], bytes[5]]);
209 let data3 = u16::from_le_bytes([bytes[6], bytes[7]]);
210 let mut data4 = [0u8; 8];
211 data4.copy_from_slice(&bytes[8..16]);
212 Some(Guid {
213 data1,
214 data2,
215 data3,
216 data4,
217 })
218 }
219
220 /// Stage `self` at `addr` in guest memory. Caller must have
221 /// mapped the destination region R+W.
222 pub fn stage(self, mmu: &mut Mmu, addr: u32) -> Result<(), crate::emulator::Trap> {
223 mmu.write_initializer(addr, &self.write_le())
224 }
225
226 /// Read 16 bytes back from guest memory at `addr`.
227 pub fn load(mmu: &Mmu, addr: u32) -> Result<Self, crate::emulator::Trap> {
228 let mut buf = [0u8; 16];
229 for (i, slot) in buf.iter_mut().enumerate() {
230 *slot = mmu.load8(addr + i as u32)?;
231 }
232 Self::read_le(&buf).ok_or(crate::emulator::Trap::MemoryFault { addr })
233 }
234}
235
236impl core::fmt::Display for Guid {
237 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
238 f.write_str(&self.to_braced_string())
239 }
240}
241
242/// Error returned by [`Guid::parse`].
243#[derive(Clone, Debug, PartialEq, Eq)]
244pub enum GuidParseError {
245 /// String length is not exactly 38 bytes.
246 WrongLength { len: usize },
247 /// Missing the leading `{` or trailing `}`.
248 MissingBraces,
249 /// Missing a `-` separator at one of the standard positions.
250 MissingHyphen { at: usize },
251 /// Encountered a non-hex byte where a hex digit was expected.
252 BadHex { at: usize, byte: u8 },
253}
254
255impl core::fmt::Display for GuidParseError {
256 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
257 match self {
258 GuidParseError::WrongLength { len } => {
259 write!(f, "GUID string must be 38 chars (got {len})")
260 }
261 GuidParseError::MissingBraces => f.write_str("GUID string missing { … } braces"),
262 GuidParseError::MissingHyphen { at } => {
263 write!(f, "GUID string missing '-' at position {at}")
264 }
265 GuidParseError::BadHex { at, byte } => {
266 write!(f, "GUID string non-hex byte {byte:#x} at position {at}")
267 }
268 }
269 }
270}
271
272impl std::error::Error for GuidParseError {}
273
274// ---- Hardcoded IIDs ----------------------------------------------------
275//
276// Values are transcribed from the public Windows SDK MIDL-
277// generated headers (`unknwn.h`, `objbase.h`, `strmif.h`,
278// `axextend.h`). Only the GUID values + the interface method
279// signatures we reproduce in `call.rs` are referenced — never
280// the BaseClasses sample source.
281
282/// `IID_IUnknown` — the universal COM base interface
283/// (`{00000000-0000-0000-C000-000000000046}`). Vtable slots:
284/// `0=QueryInterface`, `1=AddRef`, `2=Release`. Source:
285/// `unknwn.h` from the Windows SDK.
286pub const IID_IUNKNOWN: Guid = Guid::new(
287 0x0000_0000,
288 0x0000,
289 0x0000,
290 [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46],
291);
292
293/// `IID_IClassFactory` (`{00000001-0000-0000-C000-000000000046}`).
294/// Vtable adds slots 3=`CreateInstance`, 4=`LockServer`.
295pub const IID_ICLASSFACTORY: Guid = Guid::new(
296 0x0000_0001,
297 0x0000,
298 0x0000,
299 [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46],
300);
301
302/// `IID_IPersist` (`{0000010C-0000-0000-C000-000000000046}`).
303/// One method beyond IUnknown: 3=`GetClassID`.
304pub const IID_IPERSIST: Guid = Guid::new(
305 0x0000_010C,
306 0x0000,
307 0x0000,
308 [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46],
309);
310
311/// `IID_IMediaFilter` (`{56A86899-0AD4-11CE-B03A-0020AF0BA770}`).
312/// Adds (after IPersist's GetClassID):
313/// 4=`Stop`, 5=`Pause`, 6=`Run`, 7=`GetState`, 8=`SetSyncSource`,
314/// 9=`GetSyncSource`. Source: `strmif.h`.
315pub const IID_IMEDIAFILTER: Guid = Guid::new(
316 0x56A8_6899,
317 0x0AD4,
318 0x11CE,
319 [0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
320);
321
322/// `IID_IBaseFilter` (`{56A86895-0AD4-11CE-B03A-0020AF0BA770}`).
323/// Adds (after IMediaFilter's 6 methods):
324/// 10=`EnumPins`, 11=`FindPin`, 12=`QueryFilterInfo`,
325/// 13=`JoinFilterGraph`, 14=`QueryVendorInfo`.
326pub const IID_IBASEFILTER: Guid = Guid::new(
327 0x56A8_6895,
328 0x0AD4,
329 0x11CE,
330 [0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
331);
332
333/// `IID_IPin` (`{56A86891-0AD4-11CE-B03A-0020AF0BA770}`).
334/// Vtable slots beyond IUnknown:
335/// 3=`Connect`, 4=`ReceiveConnection`, 5=`Disconnect`,
336/// 6=`ConnectedTo`, 7=`ConnectionMediaType`, 8=`QueryPinInfo`,
337/// 9=`QueryDirection`, 10=`QueryId`, 11=`QueryAccept`,
338/// 12=`EnumMediaTypes`, 13=`QueryInternalConnections`,
339/// 14=`EndOfStream`, 15=`BeginFlush`, 16=`EndFlush`,
340/// 17=`NewSegment`.
341pub const IID_IPIN: Guid = Guid::new(
342 0x56A8_6891,
343 0x0AD4,
344 0x11CE,
345 [0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
346);
347
348/// `IID_IMemInputPin` (`{56A8689D-0AD4-11CE-B03A-0020AF0BA770}`).
349/// Slots beyond IUnknown:
350/// 3=`GetAllocator`, 4=`NotifyAllocator`,
351/// 5=`GetAllocatorRequirements`, 6=`Receive`,
352/// 7=`ReceiveMultiple`, 8=`ReceiveCanBlock`.
353pub const IID_IMEMINPUTPIN: Guid = Guid::new(
354 0x56A8_689D,
355 0x0AD4,
356 0x11CE,
357 [0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
358);
359
360/// `IID_IEnumPins` (`{56A86892-0AD4-11CE-B03A-0020AF0BA770}`).
361/// Slots beyond IUnknown:
362/// 3=`Next`, 4=`Skip`, 5=`Reset`, 6=`Clone`.
363pub const IID_IENUMPINS: Guid = Guid::new(
364 0x56A8_6892,
365 0x0AD4,
366 0x11CE,
367 [0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
368);
369
370/// `IID_IMemAllocator` (`{56A8689C-0AD4-11CE-B03A-0020AF0BA770}`).
371/// Slots beyond IUnknown:
372/// 3=`SetProperties`, 4=`GetProperties`, 5=`Commit`, 6=`Decommit`,
373/// 7=`GetBuffer`, 8=`ReleaseBuffer`.
374pub const IID_IMEMALLOCATOR: Guid = Guid::new(
375 0x56A8_689C,
376 0x0AD4,
377 0x11CE,
378 [0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
379);
380
381/// `IID_IMediaSample` (`{56A8689A-0AD4-11CE-B03A-0020AF0BA770}`).
382/// 17 slots beyond IUnknown. Source: `strmif.h`.
383pub const IID_IMEDIASAMPLE: Guid = Guid::new(
384 0x56A8_689A,
385 0x0AD4,
386 0x11CE,
387 [0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
388);
389
390/// `IID_IMediaSample2` (`{36B73884-C2C8-11CF-8B46-00805F6CEF60}`).
391///
392/// Extends [`IID_IMEDIASAMPLE`] with two methods at slots 19 and 20
393/// (after IMediaSample's 19 = 3 IUnknown + 16 IMediaSample):
394/// `19=GetProperties(DWORD cb, BYTE* pProps)`,
395/// `20=SetProperties(DWORD cb, const BYTE* pProps)`. Both transport
396/// the public `AM_SAMPLE2_PROPERTIES` struct (64 bytes per
397/// `strmif.h`).
398///
399/// Source: Microsoft Platform SDK `strmif.h`. Reverse-engineered
400/// from the disassembly of MPG4DS32.AX RVA `0x4064f3` (round 39):
401/// `CTransformFilter::Transform` calls `pSampleOut->QueryInterface(
402/// IID_IMediaSample2, &p2)` immediately after fetching the output
403/// buffer; on success it reads the AMT properties via slots 19/20
404/// of the returned interface. Returning `E_NOINTERFACE` forces the
405/// codec down a fallback branch that ends up trapping in the GUID-
406/// equality helper at RVA `0x7176`.
407pub const IID_IMEDIASAMPLE2: Guid = Guid::new(
408 0x36B7_3884,
409 0xC2C8,
410 0x11CF,
411 [0x8B, 0x46, 0x00, 0x80, 0x5F, 0x6C, 0xEF, 0x60],
412);
413
414/// `IID_IFilterGraph` (`{56A8689F-0AD4-11CE-B03A-0020AF0BA770}`).
415/// Slots beyond IUnknown:
416/// 3=`AddFilter`, 4=`RemoveFilter`, 5=`EnumFilters`,
417/// 6=`FindFilterByName`, 7=`ConnectDirect`, 8=`Reconnect`,
418/// 9=`Disconnect`, 10=`SetDefaultSyncSource`.
419pub const IID_IFILTERGRAPH: Guid = Guid::new(
420 0x56A8_689F,
421 0x0AD4,
422 0x11CE,
423 [0xB0, 0x3A, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70],
424);
425
426/// `MSADDS_AUDIO_DECODER_CLSID` (`{22E24591-49D0-11D2-BB50-006008320064}`).
427///
428/// The CLSID under which `msadds32.ax`'s "Windows Media Audio
429/// Decoder" DirectShow filter is registered. Discovered by
430/// reverse-engineering the splitter's `DllGetClassObject`
431/// prologue (RVA `0x3635` in the wmpcdcs8-2001 bundle) — the
432/// prologue compares `*rclsid` against an in-`.rdata` table at
433/// RVA `0x11000`, count word at RVA `0x11028`. Entry 0 of that
434/// table (count = 2, stride = 20 bytes) points to the GUID
435/// literal at `.rdata` offset `0xf248`, which decodes to this
436/// value.
437///
438/// The companion "Microsoft MS Audio Decompressor Control Property
439/// page" CLSID is [`MSADDS_AUDIO_PROPERTY_PAGE_CLSID`] below.
440///
441/// Clean-room reverse engineering only: prologue disassembled
442/// from raw bytes against Intel SDM Vol. 2A opcode encodings; no
443/// Wine / ReactOS / MinGW source consulted. The CLSID itself is
444/// public installation metadata (the splitter's `DllRegisterServer`
445/// writes it to `HKCR\CLSID\{...}\InprocServer32` at install).
446pub const MSADDS_AUDIO_DECODER_CLSID: Guid = Guid::new(
447 0x22E2_4591,
448 0x49D0,
449 0x11D2,
450 [0xBB, 0x50, 0x00, 0x60, 0x08, 0x32, 0x00, 0x64],
451);
452
453/// `MSADDS_AUDIO_PROPERTY_PAGE_CLSID`
454/// (`{8FE7E181-BB96-11D2-A1CB-00609778EA66}`).
455///
456/// Entry 1 of the `msadds32.ax` `DllGetClassObject` CLSID table —
457/// the property-page UI vestige for "Microsoft MS Audio
458/// Decompressor Control". Not exercised on the audio-decode
459/// path; pinned here so a probe that lands on it (e.g. dynamic
460/// `DllRegisterServer` walk in a future round) can identify
461/// what it found.
462pub const MSADDS_AUDIO_PROPERTY_PAGE_CLSID: Guid = Guid::new(
463 0x8FE7_E181,
464 0xBB96,
465 0x11D2,
466 [0xA1, 0xCB, 0x00, 0x60, 0x97, 0x78, 0xEA, 0x66],
467);
468
469/// `CLSID_MemoryAllocator` (`{1E651CC0-B199-11D0-8212-00C04FC32C45}`).
470///
471/// Source: Windows SDK header `axextend.h`. This is the canonical
472/// DirectShow memory-allocator class — `CoCreateInstance(this CLSID,
473/// NULL, CLSCTX_INPROC_SERVER, IID_IMemAllocator, &alloc)` is how
474/// every DirectShow input/output pin instantiates a fresh
475/// IMemAllocator backed by host-managed buffer pool.
476///
477/// Round 35 — registered in the host class-factory cache at
478/// `Sandbox::new` so codecs that internally call CoCreateInstance
479/// for it (mpg4ds32 from inside `IMemInputPin::GetAllocator`) get a
480/// usable allocator pointer rather than the round-34 baseline
481/// `CLASS_E_CLASSNOTAVAILABLE` (`0x80040111`).
482pub const CLSID_MEMORY_ALLOCATOR: Guid = Guid::new(
483 0x1E65_1CC0,
484 0xB199,
485 0x11D0,
486 [0x82, 0x12, 0x00, 0xC0, 0x4F, 0xC3, 0x2C, 0x45],
487);
488
489// ---- Public HRESULT codes ----------------------------------------------
490//
491// Subset that the round-25 tests assert against. Source:
492// `winerror.h` from the Windows SDK. Cited values are public
493// constants documented on every MSDN HRESULT page.
494
495/// `S_OK = 0x00000000` — success.
496pub const S_OK: u32 = 0x0000_0000;
497/// `S_FALSE = 0x00000001` — operation succeeded but did not need
498/// to do anything (e.g. `IBaseFilter::Run` returned because the
499/// filter was already running).
500pub const S_FALSE: u32 = 0x0000_0001;
501/// `E_NOINTERFACE = 0x80004002` — `QueryInterface` rejected the
502/// requested IID.
503pub const E_NOINTERFACE: u32 = 0x8000_4002;
504/// `E_NOTIMPL = 0x80004001` — method not implemented.
505pub const E_NOTIMPL: u32 = 0x8000_4001;
506/// `E_POINTER = 0x80004003` — caller passed a NULL/invalid
507/// pointer.
508pub const E_POINTER: u32 = 0x8000_4003;
509/// `E_FAIL = 0x80004005` — generic failure.
510pub const E_FAIL: u32 = 0x8000_4005;
511/// `E_UNEXPECTED = 0x8000FFFF`.
512pub const E_UNEXPECTED: u32 = 0x8000_FFFF;
513/// `CLASS_E_CLASSNOTAVAILABLE = 0x80040111` — the CLSID is not
514/// registered with our in-process class-factory cache.
515pub const CLASS_E_CLASSNOTAVAILABLE: u32 = 0x8004_0111;
516
517// ---- Vtable-method slot numbers ----------------------------------------
518//
519// Standard COM ABI: every interface inherits IUnknown's three
520// methods at slots 0..3, then adds its own at slot 3 onward.
521
522/// Slot 0 of every COM vtable: `QueryInterface(REFIID, void**)`.
523pub const SLOT_QUERY_INTERFACE: u32 = 0;
524/// Slot 1: `AddRef()`.
525pub const SLOT_ADD_REF: u32 = 1;
526/// Slot 2: `Release()`.
527pub const SLOT_RELEASE: u32 = 2;
528
529/// `IClassFactory::CreateInstance(IUnknown* pUnkOuter, REFIID,
530/// void** ppv)` — vtable slot 3.
531pub const SLOT_CLASS_FACTORY_CREATE_INSTANCE: u32 = 3;
532/// `IClassFactory::LockServer(BOOL fLock)` — vtable slot 4.
533pub const SLOT_CLASS_FACTORY_LOCK_SERVER: u32 = 4;
534
535/// `IBaseFilter::Stop()` — slot 4 (after IPersist::GetClassID at
536/// slot 3).
537pub const SLOT_BASEFILTER_STOP: u32 = 4;
538/// `IBaseFilter::Pause()` — slot 5.
539pub const SLOT_BASEFILTER_PAUSE: u32 = 5;
540/// `IBaseFilter::Run(REFERENCE_TIME tStart)` — slot 6. `tStart`
541/// is a 64-bit integer; passed as two adjacent dwords on the
542/// stdcall stack (low dword first, high dword next).
543pub const SLOT_BASEFILTER_RUN: u32 = 6;
544/// `IBaseFilter::GetState(DWORD dwMilliSecsTimeout, FILTER_STATE
545/// *State)` — slot 7.
546pub const SLOT_BASEFILTER_GET_STATE: u32 = 7;
547/// `IBaseFilter::EnumPins(IEnumPins** ppEnum)` — slot 10.
548pub const SLOT_BASEFILTER_ENUM_PINS: u32 = 10;
549/// `IBaseFilter::FindPin(LPCWSTR Id, IPin** ppPin)` — slot 11.
550pub const SLOT_BASEFILTER_FIND_PIN: u32 = 11;
551/// `IBaseFilter::JoinFilterGraph(IFilterGraph* pGraph,
552/// LPCWSTR pName)` — slot 13.
553pub const SLOT_BASEFILTER_JOIN_FILTER_GRAPH: u32 = 13;
554
555/// `IMediaFilter::Stop()` — slot 4 (after IPersist::GetClassID at
556/// slot 3). Same numeric slot as `IBaseFilter::Stop` because
557/// `IBaseFilter` extends `IMediaFilter`.
558pub const SLOT_MEDIAFILTER_STOP: u32 = 4;
559/// `IMediaFilter::Pause()` — slot 5.
560pub const SLOT_MEDIAFILTER_PAUSE: u32 = 5;
561/// `IMediaFilter::Run(REFERENCE_TIME tStart)` — slot 6. `tStart`
562/// is a 64-bit integer marshalled as two adjacent dwords on the
563/// stdcall stack (low dword first, high dword next).
564pub const SLOT_MEDIAFILTER_RUN: u32 = 6;
565/// `IMediaFilter::GetState(DWORD dwMilliSecsTimeout, FILTER_STATE
566/// *State)` — slot 7.
567pub const SLOT_MEDIAFILTER_GET_STATE: u32 = 7;
568
569/// `IMemAllocator::SetProperties(ALLOCATOR_PROPERTIES* pRequest,
570/// ALLOCATOR_PROPERTIES* pActual)` — slot 3.
571pub const SLOT_MEMALLOCATOR_SET_PROPERTIES: u32 = 3;
572/// `IMemAllocator::Commit()` — slot 5.
573pub const SLOT_MEMALLOCATOR_COMMIT: u32 = 5;
574/// `IMemAllocator::Decommit()` — slot 6.
575pub const SLOT_MEMALLOCATOR_DECOMMIT: u32 = 6;
576/// `IMemAllocator::GetBuffer(IMediaSample** ppBuffer,
577/// REFERENCE_TIME* pStartTime, REFERENCE_TIME* pEndTime,
578/// DWORD dwFlags)` — slot 7.
579pub const SLOT_MEMALLOCATOR_GET_BUFFER: u32 = 7;
580/// `IMemAllocator::ReleaseBuffer(IMediaSample* pBuffer)` — slot 8.
581pub const SLOT_MEMALLOCATOR_RELEASE_BUFFER: u32 = 8;
582
583/// `IMediaSample::GetPointer(BYTE** ppBuffer)` — slot 3 (after
584/// IUnknown). Returns the start address of the sample's payload
585/// buffer through `*ppBuffer`.
586pub const SLOT_MEDIASAMPLE_GET_POINTER: u32 = 3;
587/// `IMediaSample::GetSize()` — slot 4. Returns the buffer
588/// capacity in bytes (NOT the actual data length).
589pub const SLOT_MEDIASAMPLE_GET_SIZE: u32 = 4;
590/// `IMediaSample::IsSyncPoint()` — slot 7. Returns `S_OK` for
591/// sync (key) frames, `S_FALSE` otherwise.
592pub const SLOT_MEDIASAMPLE_IS_SYNC_POINT: u32 = 7;
593/// `IMediaSample::SetSyncPoint(BOOL bIsSyncPoint)` — slot 8.
594pub const SLOT_MEDIASAMPLE_SET_SYNC_POINT: u32 = 8;
595/// `IMediaSample::GetActualDataLength()` — slot 11. Returns the
596/// number of valid bytes currently in the sample's buffer.
597pub const SLOT_MEDIASAMPLE_GET_ACTUAL_DATA_LENGTH: u32 = 11;
598/// `IMediaSample::SetActualDataLength(LONG lLen)` — slot 12.
599/// Caller declares the count of valid bytes the codec should
600/// process from the buffer. Must be ≤ `GetSize()`.
601pub const SLOT_MEDIASAMPLE_SET_ACTUAL_DATA_LENGTH: u32 = 12;
602/// `IMediaSample::GetMediaType(AM_MEDIA_TYPE** ppMediaType)` —
603/// slot 13.
604pub const SLOT_MEDIASAMPLE_GET_MEDIA_TYPE: u32 = 13;
605/// `IMediaSample::SetMediaType(AM_MEDIA_TYPE* pMediaType)` —
606/// slot 14.
607pub const SLOT_MEDIASAMPLE_SET_MEDIA_TYPE: u32 = 14;
608/// `IMediaSample::GetMediaTime(LONGLONG* pStart, LONGLONG* pEnd)`
609/// — slot 17.
610pub const SLOT_MEDIASAMPLE_GET_MEDIA_TIME: u32 = 17;
611/// `IMediaSample::SetMediaTime(LONGLONG* pStart, LONGLONG* pEnd)`
612/// — slot 18. Last method of `IMediaSample`.
613pub const SLOT_MEDIASAMPLE_SET_MEDIA_TIME: u32 = 18;
614
615/// `IMediaSample2::GetProperties(DWORD cb, BYTE* pProps)` — slot 19.
616/// Round 39 — first method beyond `IMediaSample`'s vtable.
617pub const SLOT_MEDIASAMPLE2_GET_PROPERTIES: u32 = 19;
618/// `IMediaSample2::SetProperties(DWORD cb, const BYTE* pProps)` —
619/// slot 20. Round 39 — second method beyond `IMediaSample`'s
620/// vtable.
621pub const SLOT_MEDIASAMPLE2_SET_PROPERTIES: u32 = 20;
622
623/// `IMemInputPin::GetAllocator(IMemAllocator** ppAllocator)` —
624/// slot 3. Per MSDN
625/// <https://learn.microsoft.com/en-us/windows/win32/api/strmif/nf-strmif-imeminputpin-getallocator>:
626/// "Retrieves the memory allocator proposed by this input pin."
627/// Most input pins create their own allocator and return it
628/// here; the upstream filter then calls `SetProperties` +
629/// `Commit` on it, then `NotifyAllocator(this, FALSE)` to
630/// confirm both ends agreed. Returns `VFW_E_NO_ALLOCATOR` if
631/// the pin has no preference.
632pub const SLOT_MEMINPUTPIN_GET_ALLOCATOR: u32 = 3;
633/// `IMemInputPin::NotifyAllocator(IMemAllocator*, BOOL bReadOnly)`
634/// — slot 4.
635pub const SLOT_MEMINPUTPIN_NOTIFY_ALLOCATOR: u32 = 4;
636/// `IMemInputPin::Receive(IMediaSample*)` — slot 6.
637pub const SLOT_MEMINPUTPIN_RECEIVE: u32 = 6;
638
639/// `IPin::ReceiveConnection(IPin* pConnector, AM_MEDIA_TYPE* pmt)`
640/// — slot 4.
641pub const SLOT_PIN_RECEIVE_CONNECTION: u32 = 4;
642/// `IPin::QueryDirection(PIN_DIRECTION*)` — slot 9. Codec-side
643/// pins return `PIN_INPUT (0)` or `PIN_OUTPUT (1)`.
644pub const SLOT_PIN_QUERY_DIRECTION: u32 = 9;
645/// `IPin::QueryAccept(AM_MEDIA_TYPE* pmt)` — slot 11. Returns
646/// `S_OK` if the pin will accept a connection with the given
647/// media type, `S_FALSE` or an error HRESULT otherwise. Round 60
648/// disassembles this method on `msadds32.ax`'s audio splitter
649/// input pin to identify the AMT validation criteria its
650/// internal decoder enforces.
651pub const SLOT_PIN_QUERY_ACCEPT: u32 = 11;
652/// `IPin::EnumMediaTypes(IEnumMediaTypes**)` — slot 12.
653pub const SLOT_PIN_ENUM_MEDIA_TYPES: u32 = 12;
654/// `IPin::EndOfStream()` — slot 14. Tells a downstream pin no
655/// more samples will arrive on the current segment.
656pub const SLOT_PIN_END_OF_STREAM: u32 = 14;
657/// `IPin::BeginFlush()` — slot 15. Signals that subsequent
658/// samples on this pin will be flushed; the receiver should
659/// discard any in-flight work.
660pub const SLOT_PIN_BEGIN_FLUSH: u32 = 15;
661/// `IPin::EndFlush()` — slot 16. Pairs with `BeginFlush`.
662pub const SLOT_PIN_END_FLUSH: u32 = 16;
663/// `IPin::NewSegment(REFERENCE_TIME start, REFERENCE_TIME stop,
664/// double rate)` — slot 17. Driven by the upstream filter at the
665/// start of each playback segment; some audio decoders won't
666/// process the first `Receive` until they've seen a `NewSegment`
667/// to seed their internal time-base + buffer-pool initialisation.
668pub const SLOT_PIN_NEW_SEGMENT: u32 = 17;
669
670/// `IEnumPins::Next(ULONG cPins, IPin** ppPins, ULONG* pcFetched)`
671/// — slot 3.
672pub const SLOT_ENUMPINS_NEXT: u32 = 3;
673
674/// `PIN_DIRECTION` enum: input pin. Source: `strmif.h` `PINDIR_INPUT`.
675pub const PIN_DIRECTION_INPUT: u32 = 0;
676/// `PIN_DIRECTION` enum: output pin.
677pub const PIN_DIRECTION_OUTPUT: u32 = 1;
678
679/// `FILTER_STATE` enum value `State_Stopped = 0` (per `strmif.h`).
680/// `IMediaFilter::GetState` returns this when the filter is not
681/// running and not paused.
682pub const FILTER_STATE_STOPPED: u32 = 0;
683/// `FILTER_STATE` enum value `State_Paused = 1`.
684pub const FILTER_STATE_PAUSED: u32 = 1;
685/// `FILTER_STATE` enum value `State_Running = 2`.
686pub const FILTER_STATE_RUNNING: u32 = 2;
687
688/// `VFW_S_STATE_INTERMEDIATE = 0x00040003` —
689/// `IMediaFilter::GetState` returns this when the filter is
690/// transitioning (caller should retry, possibly with a longer
691/// timeout). See MSDN
692/// <https://learn.microsoft.com/en-us/windows/win32/api/strmif/nf-strmif-imediafilter-getstate>.
693pub const VFW_S_STATE_INTERMEDIATE: u32 = 0x0004_0003;
694/// `VFW_S_CANT_CUE = 0x00040004` — Run() returned but the filter
695/// graph could not seek; non-fatal.
696pub const VFW_S_CANT_CUE: u32 = 0x0004_0004;
697
698/// `VFW_E_NOT_COMMITTED = 0x80040209` — IMemAllocator::GetBuffer
699/// returns this when the allocator has not been Commit()'d.
700pub const VFW_E_NOT_COMMITTED: u32 = 0x8004_0209;
701/// `VFW_E_NOT_CONNECTED = 0x80040209`'s sibling at 0x80040211 —
702/// also reused by IMemAllocator::GetBuffer for "pool exhausted"
703/// in our host stub (real DShow uses `VFW_E_TIMEOUT` here).
704pub const VFW_E_TIMEOUT: u32 = 0x8004_0211;
705/// `VFW_E_NO_ALLOCATOR = 0x80040261`.
706pub const VFW_E_NO_ALLOCATOR: u32 = 0x8004_0261;
707
708// ---- Host-side object-handle table -------------------------------------
709//
710// Round-25 stage 1 keeps this minimal: a counter for how many
711// distinct guest interface pointers the host has handed out, and
712// per-pointer reference-count bookkeeping so a leak in our
713// `Release` calls would surface as a non-zero refcount at
714// `Sandbox::drop`. The counter is the total number of `AddRef`
715// calls we have driven minus the total number of `Release`
716// calls; once we wire `IClassFactory::CreateInstance` we will
717// register every freshly-minted object here.
718
719/// Per-object COM bookkeeping the host keeps so it can detect
720/// `AddRef` / `Release` imbalances and reuse pointers across
721/// `QueryInterface` calls.
722#[derive(Debug, Clone)]
723pub struct ComObjectInfo {
724 /// Guest virtual address of the COM object (= the pointer
725 /// the codec returned to us). For multiple-interface
726 /// objects, the same underlying object surfaces at multiple
727 /// addresses; we treat each as its own entry.
728 pub guest_addr: u32,
729 /// Net AddRef–Release count we have driven. Excludes
730 /// refcount changes the codec performs internally; the
731 /// host's view starts at 1 (the `CreateInstance` /
732 /// `QueryInterface` returned the pointer with refcount 1
733 /// per the COM ABI contract).
734 pub host_refcount: i32,
735 /// The IID we last asserted this object satisfies, for
736 /// diagnostic logging. `None` means "not yet probed".
737 pub last_iid: Option<Guid>,
738}
739
740/// Host-side directory of COM objects the codec has handed back
741/// to the test harness. Lives inside [`crate::win32::HostState`]
742/// once round-25 wires it up. Round-25 stage 1 is the type
743/// definition + lookups; stage-2 onward populates entries from
744/// `CoCreateInstance` / `IClassFactory::CreateInstance` /
745/// `QueryInterface` returns.
746#[derive(Debug, Default, Clone)]
747pub struct ComObjectTable {
748 objects: std::collections::BTreeMap<u32, ComObjectInfo>,
749 /// Optional in-process class-factory registrations: CLSID →
750 /// guest-side IClassFactory pointer that was returned by
751 /// `DllGetClassObject(CLSID, IID_IClassFactory)`. Used by
752 /// `ole32!CoCreateInstance` to satisfy the codec's request
753 /// without going through SCM / the registry.
754 pub class_factories: std::collections::BTreeMap<Guid, u32>,
755}
756
757impl ComObjectTable {
758 /// Construct an empty table.
759 pub fn new() -> Self {
760 Self::default()
761 }
762
763 /// Register or look up a COM object. Returns the existing
764 /// entry when `addr` is already present (multiple
765 /// `QueryInterface` calls for the same IID return the same
766 /// pointer per COM ABI rules); otherwise inserts a fresh
767 /// entry with refcount 1.
768 pub fn intern(&mut self, addr: u32, iid: Option<Guid>) -> &mut ComObjectInfo {
769 self.objects.entry(addr).or_insert(ComObjectInfo {
770 guest_addr: addr,
771 host_refcount: 0,
772 last_iid: iid,
773 })
774 }
775
776 /// Total number of distinct guest pointers the host has
777 /// observed so far.
778 pub fn len(&self) -> usize {
779 self.objects.len()
780 }
781
782 /// Total live host refcount across every registered object.
783 pub fn total_refcount(&self) -> i32 {
784 self.objects.values().map(|o| o.host_refcount).sum()
785 }
786
787 /// True iff no objects are registered.
788 pub fn is_empty(&self) -> bool {
789 self.objects.is_empty()
790 }
791
792 /// Iterate over registered objects.
793 pub fn iter(&self) -> impl Iterator<Item = (&u32, &ComObjectInfo)> {
794 self.objects.iter()
795 }
796
797 /// Look up an object's host-side bookkeeping, immutable.
798 pub fn get(&self, addr: u32) -> Option<&ComObjectInfo> {
799 self.objects.get(&addr)
800 }
801
802 /// Look up an object's host-side bookkeeping, mutable.
803 pub fn get_mut(&mut self, addr: u32) -> Option<&mut ComObjectInfo> {
804 self.objects.get_mut(&addr)
805 }
806
807 /// Bump the host refcount.
808 pub fn record_addref(&mut self, addr: u32) {
809 if let Some(o) = self.objects.get_mut(&addr) {
810 o.host_refcount = o.host_refcount.saturating_add(1);
811 }
812 }
813
814 /// Drop the host refcount. Returns the new value.
815 pub fn record_release(&mut self, addr: u32) -> i32 {
816 if let Some(o) = self.objects.get_mut(&addr) {
817 o.host_refcount = o.host_refcount.saturating_sub(1);
818 return o.host_refcount;
819 }
820 0
821 }
822
823 /// Register an in-process class factory under `clsid`. The
824 /// runtime calls this after a successful
825 /// `DllGetClassObject(CLSID, IID_IClassFactory)`.
826 pub fn register_class_factory(&mut self, clsid: Guid, factory: u32) {
827 self.class_factories.insert(clsid, factory);
828 }
829
830 /// Look up a registered class factory.
831 pub fn lookup_class_factory(&self, clsid: &Guid) -> Option<u32> {
832 self.class_factories.get(clsid).copied()
833 }
834}
835
836/// Read the vtable pointer (the first 4 bytes of a COM object).
837/// COM objects are laid out as `[lpVtbl, …fields…]`; the vtable
838/// itself is an array of function pointers, indexed by slot.
839///
840/// This is the canonical pattern for calling into a guest COM
841/// object: load `[obj]` to get the vtable VA, then load
842/// `[vtable + 4*slot]` to get the method's guest VA.
843pub fn vtable_ptr(mmu: &Mmu, obj: u32) -> Result<u32, crate::emulator::Trap> {
844 mmu.load32(obj)
845}
846
847/// Resolve a vtable slot to the guest VA of the underlying
848/// method. Returns `Err(MemoryFault)` when either dereference
849/// touches an unmapped page.
850pub fn method_va(mmu: &Mmu, obj: u32, slot: u32) -> Result<u32, crate::emulator::Trap> {
851 let vtbl = vtable_ptr(mmu, obj)?;
852 mmu.load32(vtbl.wrapping_add(slot.wrapping_mul(4)))
853}
854
855/// Free helper used by `win32::ole32::stub_co_create_instance`:
856/// search the host class-factory cache for `clsid` and report
857/// whether the requested IID is one of `IUnknown`, `IClassFactory`
858/// — the only IIDs `CoCreateInstance` accepts when caller passes
859/// a class factory directly without an explicit `pUnkOuter`.
860///
861/// Returns the guest factory address on success, or `None` to
862/// signal `CLASS_E_CLASSNOTAVAILABLE` to the codec.
863pub fn lookup_in_process_class(table: &ComObjectTable, clsid: Guid, iid: Guid) -> Option<u32> {
864 if iid != IID_IUNKNOWN && iid != IID_ICLASSFACTORY {
865 return None;
866 }
867 table.lookup_class_factory(&clsid)
868}
869
870// ---- Cpu / Mmu glue used by `call::*` ---------------------------------
871//
872// `call::call_method` re-uses the existing `crate::win32::call_guest`
873// to drive the vtable target. We re-export the symbol here so
874// the `com::call` submodule does not have to reach across the
875// `win32` module boundary.
876
877#[doc(hidden)]
878pub(crate) fn drive_guest(
879 cpu: &mut Cpu,
880 mmu: &mut Mmu,
881 registry: &crate::win32::Registry,
882 state: &mut crate::win32::HostState,
883 target: u32,
884 args: &[u32],
885) -> Result<u32, crate::Error> {
886 crate::win32::call_guest(cpu, mmu, registry, state, target, args)
887}
888
889#[cfg(test)]
890mod tests {
891 use super::*;
892
893 #[test]
894 fn parse_iunknown_braced_form() {
895 let g = Guid::parse("{00000000-0000-0000-C000-000000000046}").unwrap();
896 assert_eq!(g, IID_IUNKNOWN);
897 }
898
899 #[test]
900 fn parse_iclassfactory_braced_form() {
901 let g = Guid::parse("{00000001-0000-0000-c000-000000000046}").unwrap();
902 assert_eq!(g, IID_ICLASSFACTORY);
903 }
904
905 #[test]
906 fn parse_ibasefilter_braced_form() {
907 let g = Guid::parse("{56A86895-0AD4-11CE-B03A-0020AF0BA770}").unwrap();
908 assert_eq!(g, IID_IBASEFILTER);
909 }
910
911 #[test]
912 fn msadds_audio_decoder_clsid_round_trips() {
913 // Pinned by round 57 — reverse-engineered from the
914 // splitter's `DllGetClassObject` CLSID table at RVA
915 // `0x11000`, entry 0 → CLSID pointer at `.rdata`
916 // offset `0xf248`. See [`MSADDS_AUDIO_DECODER_CLSID`].
917 let g = Guid::parse("{22E24591-49D0-11D2-BB50-006008320064}").unwrap();
918 assert_eq!(g, MSADDS_AUDIO_DECODER_CLSID);
919 assert_eq!(
920 MSADDS_AUDIO_DECODER_CLSID.to_braced_string(),
921 "{22E24591-49D0-11D2-BB50-006008320064}"
922 );
923 }
924
925 #[test]
926 fn msadds_audio_property_page_clsid_round_trips() {
927 let g = Guid::parse("{8FE7E181-BB96-11D2-A1CB-00609778EA66}").unwrap();
928 assert_eq!(g, MSADDS_AUDIO_PROPERTY_PAGE_CLSID);
929 assert_eq!(
930 MSADDS_AUDIO_PROPERTY_PAGE_CLSID.to_braced_string(),
931 "{8FE7E181-BB96-11D2-A1CB-00609778EA66}"
932 );
933 }
934
935 #[test]
936 fn parse_rejects_missing_braces() {
937 assert!(matches!(
938 Guid::parse("00000000-0000-0000-C000-000000000046").unwrap_err(),
939 GuidParseError::WrongLength { .. }
940 ));
941 assert!(matches!(
942 Guid::parse("[00000000-0000-0000-C000-000000000046]").unwrap_err(),
943 GuidParseError::MissingBraces
944 ));
945 }
946
947 #[test]
948 fn parse_rejects_missing_hyphen() {
949 // hyphen at offset 9 missing
950 let bad = "{00000000+0000-0000-C000-000000000046}";
951 assert!(matches!(
952 Guid::parse(bad).unwrap_err(),
953 GuidParseError::MissingHyphen { at: 9 }
954 ));
955 }
956
957 #[test]
958 fn parse_rejects_non_hex_byte() {
959 let bad = "{0000000Z-0000-0000-C000-000000000046}";
960 assert!(matches!(
961 Guid::parse(bad).unwrap_err(),
962 GuidParseError::BadHex { .. }
963 ));
964 }
965
966 #[test]
967 fn write_le_round_trips_via_read_le() {
968 let bytes = IID_IBASEFILTER.write_le();
969 assert_eq!(Guid::read_le(&bytes), Some(IID_IBASEFILTER));
970 }
971
972 #[test]
973 fn to_braced_string_matches_string_from_guid2_format() {
974 // Upper-case canonical form, which is what
975 // `ole32!StringFromGUID2` emits.
976 assert_eq!(
977 IID_IUNKNOWN.to_braced_string(),
978 "{00000000-0000-0000-C000-000000000046}"
979 );
980 assert_eq!(
981 IID_IBASEFILTER.to_braced_string(),
982 "{56A86895-0AD4-11CE-B03A-0020AF0BA770}"
983 );
984 }
985
986 #[test]
987 fn stage_and_load_round_trip_via_mmu() {
988 use crate::emulator::mmu::Perm;
989 let mut mmu = Mmu::new();
990 mmu.map(0x9000_0000, 0x1000, Perm::R | Perm::W);
991 IID_IPIN.stage(&mut mmu, 0x9000_0000).unwrap();
992 let g = Guid::load(&mmu, 0x9000_0000).unwrap();
993 assert_eq!(g, IID_IPIN);
994 }
995
996 #[test]
997 fn com_object_table_intern_starts_with_zero_refcount() {
998 let mut t = ComObjectTable::new();
999 let info = t.intern(0xCAFE_BABE, Some(IID_IUNKNOWN));
1000 assert_eq!(info.guest_addr, 0xCAFE_BABE);
1001 assert_eq!(info.host_refcount, 0);
1002 assert_eq!(info.last_iid, Some(IID_IUNKNOWN));
1003 }
1004
1005 #[test]
1006 fn com_object_table_addref_release_balance() {
1007 let mut t = ComObjectTable::new();
1008 t.intern(0x1000, None);
1009 t.record_addref(0x1000);
1010 t.record_addref(0x1000);
1011 assert_eq!(t.total_refcount(), 2);
1012 let now = t.record_release(0x1000);
1013 assert_eq!(now, 1);
1014 let now = t.record_release(0x1000);
1015 assert_eq!(now, 0);
1016 assert_eq!(t.total_refcount(), 0);
1017 }
1018
1019 #[test]
1020 fn com_object_table_register_and_lookup_class_factory() {
1021 let mut t = ComObjectTable::new();
1022 let clsid = Guid::parse("{4F03ADBE-9F75-4970-B9C8-EAB6A2E0EE96}").unwrap();
1023 t.register_class_factory(clsid, 0x1234_5678);
1024 assert_eq!(t.lookup_class_factory(&clsid), Some(0x1234_5678));
1025 let other = Guid::parse("{00000000-0000-0000-C000-000000000046}").unwrap();
1026 assert_eq!(t.lookup_class_factory(&other), None);
1027 }
1028
1029 #[test]
1030 fn lookup_in_process_class_only_for_iunknown_or_iclassfactory() {
1031 let mut t = ComObjectTable::new();
1032 let clsid = Guid::parse("{4F03ADBE-9F75-4970-B9C8-EAB6A2E0EE96}").unwrap();
1033 t.register_class_factory(clsid, 0xAA);
1034 assert_eq!(lookup_in_process_class(&t, clsid, IID_IUNKNOWN), Some(0xAA));
1035 assert_eq!(
1036 lookup_in_process_class(&t, clsid, IID_ICLASSFACTORY),
1037 Some(0xAA)
1038 );
1039 assert_eq!(lookup_in_process_class(&t, clsid, IID_IBASEFILTER), None);
1040 }
1041}