Skip to main content

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}