ud_emulator/lib.rs
1//! Pure-Rust 32-bit x86 emulator + PE loader + Video for Windows
2//! host. Lets oxideav delegate decoding (and eventually encoding)
3//! to legitimately-licensed Windows codec DLLs on any platform.
4//!
5//! **Round 1 — "Load + DllMain + clean exit".** The crate ships:
6//!
7//! * [`emulator::mmu`] — flat 4 GiB virtual address space, sparse
8//! 4 KiB pages with R/W/X permission bits.
9//! * [`emulator::regs`], [`emulator::decode`], [`emulator::isa_int`]
10//! — i386 register file, instruction decoder, executor for the
11//! integer base ISA.
12//! * [`pe`] — PE32-only loader: DOS + PE header parse, section
13//! mapping into the MMU, base relocation, IAT resolution against
14//! the Win32 stub registry, export-by-name lookup.
15//! * [`win32::kernel32`] — minimum stub set to satisfy a
16//! Cinepak-class DLL: `GetProcessHeap` / `HeapAlloc` /
17//! `HeapFree` / `HeapReAlloc` / `LocalAlloc` / `LocalFree` /
18//! `OutputDebugStringA` / `GetTickCount` /
19//! `InterlockedIncrement` / `InterlockedDecrement` /
20//! `LoadLibraryA` / `GetProcAddress`.
21//!
22//! **Round 2 — "Decode one Cinepak frame".** Adds:
23//!
24//! * [`Sandbox::call_export`] — generic stdcall guest-call helper.
25//! * [`win32::vfw32`] — `BITMAPINFOHEADER` marshalling, `ICDECOMPRESS`
26//! layout, and the `IC*` host surface (`ICOpen`, `ICClose`,
27//! `ICGetInfo`, `ICDecompressBegin`, `ICDecompressQuery`,
28//! `ICDecompress`, `ICDecompressEnd`) that drives the codec
29//! DLL's `DriverProc` end-to-end.
30//! * [`Sandbox::install_codec`] / [`Sandbox::ic_open`] etc — the
31//! ergonomic Rust-side wrappers the integration test uses.
32//!
33//! **Round 3 — "Real-codec smoke against IR32_32.DLL".** Adds:
34//!
35//! * `tests/common/mod.rs` — fixture-discovery helper:
36//! `OXIDEAV_VFW_FIXTURE_DIR` env var → Wine prefix → Windows
37//! system32 → on-disk cache → HTTPS fetch from
38//! `samples.oxideav.org`. CI=true bypasses the cache.
39//! * Round-3 m1 test asserted the exact set of 49 Win32 imports
40//! (gdi32 / user32 / winmm + 24 extra kernel32) the
41//! round-1+2 stub registry did not satisfy — round 4's
42//! concrete dispatch budget. Round 4 closed every gap; the
43//! m1 test now asserts zero unresolved imports.
44//! * `tests/m2_indeo3_driverproc.rs` retained the
45//! synthetic-codec walkthrough; a forward-compatible Indeo 3
46//! `DllMain → ICOpen → ICGetInfo → ICClose` walkthrough that
47//! activated once round 4 closed the import gaps.
48//!
49//! **Round 4 — "Close the 49 round-3 import gaps".** Adds the
50//! 49 stubs round 3 surfaced:
51//!
52//! * [`win32::gdi32`] — 8 fail-soft stubs for `BitBlt` /
53//! `CreateCompatibleDC` / `DeleteDC` / `GetDeviceCaps` /
54//! `GetNearestColor` / `GetObjectA` /
55//! `GetSystemPaletteEntries` / `SelectObject`.
56//! * [`win32::kernel32`] — 24 round-4 stubs covering the CRT
57//! init surface (`ExitProcess`, `GetACP` / `GetOEMCP` /
58//! `GetCPInfo`, `GetCommandLineA` / `GetEnvironmentStrings` /
59//! `GetFileType`, `GetLastError` / `SetLastError`,
60//! `GetModuleFileNameA` / `GetModuleHandleA`,
61//! `GetStartupInfoA` / `GetStdHandle` / `GetSystemInfo` /
62//! `GetVersion`, `GlobalAlloc` / `GlobalFree` / `GlobalLock`
63//! / `GlobalUnlock`, `MultiByteToWideChar` /
64//! `WideCharToMultiByte`, `RtlUnwind`, `VirtualAlloc` /
65//! `VirtualFree`, `WriteFile`).
66//! * [`win32::user32`] — 16 fail-soft stubs covering the
67//! dialog / paint surface; `MessageBoxA` logs to stderr +
68//! `host.message_box_log`; `wsprintfA` is a real cdecl
69//! variadic implementation.
70//! * [`win32::winmm`] — `DefDriverProc` (returns 0 / DRVCNF_OK).
71//! * [`emulator::mmu::Mmu::unmap`] +
72//! [`emulator::mmu::Mmu::find_free_range`] for the
73//! `VirtualAlloc` / `VirtualFree` family.
74//!
75//! With round 4 in place, `IR32_32.DLL` loads cleanly and
76//! `DllMain` runs until it hits the first ISA opcode our integer
77//! interpreter does not yet decode: `ADD AL, imm8` (opcode
78//! `0x04`) at `eip = 0x1000_612A`. That was round-4's hand-off
79//! to round 5.
80//!
81//! **Round 5 — "DllMain + ICOpen + ICGetInfo + ICClose against
82//! Intel IR32_32.DLL".** Adds:
83//!
84//! * The 8-bit primary ALU opcodes (`0x00..=0x05` ADD,
85//! `0x08..=0x0D` OR, `0x10..=0x15` ADC, …, `0x38..=0x3D` CMP)
86//! plus `r/m8 imm8` group-1 (`0x80`), `r/m8` group-3 (`0xF6`),
87//! `r/m8` group-4 (`0xFE`).
88//! * Group-2 `r/m8` shifts (`0xC0/0xD0/0xD2`) plus the
89//! `r/m32` 1/cl variants (`0xD1/0xD3`).
90//! * `IMUL r32, r/m32, imm32`/`imm8` (`0x69/0x6B`),
91//! `XCHG r/m, r` (`0x86/0x87`), `SAHF/LAHF` (`0x9E/0x9F`),
92//! `CMC` (`0xF5`), `PUSHAD/POPAD` (`0x60/0x61`), `ENTER`
93//! (`0xC8`), the full `MOVS/CMPS/STOS/LODS/SCAS` family with
94//! REP / REPE / REPNE prefixes.
95//! * `0F 40..4F CMOVcc`, `0F A3 BT`, `0F AB BTS`, `0F A4..A5
96//! SHLD`, `0F AC..AD SHRD`, `0F BA` group-8 (BT/BTS/BTR/BTC
97//! imm8), `0F B1 CMPXCHG`, `0F C1 XADD`, `0F C8..CF BSWAP`.
98//! * Per-instruction segment-override prefix routing through
99//! [`emulator::Cpu::set_fs_base`] / `set_gs_base`. The
100//! runtime maps a 4 KiB TEB at `0x7FFD_E000`, primes
101//! `FS:[0]` (SEH chain end-of-list = `-1`) and `FS:[0x18]`
102//! (TEB self-pointer), and points FS at it.
103//! * Corrected `vfw32::ICM_*` numeric values
104//! (`ICM_GETINFO = 0x5002`, `ICM_DECOMPRESS = 0x400D`, etc).
105//! * [`win32::vfw32::ic_open`] now stages a real 36-byte
106//! `ICOPEN` so the codec's `DRV_OPEN` allocates per-instance
107//! state (round 4 passed NULL).
108//! * [`win32::vfw32::ic_get_info`] falls back to an
109//! fcc-derived `szName` when the codec leaves it NUL
110//! (real `vfw32!ICGetInfo` fills it from the registry).
111//! * Bug-fix: round-4's `0xC6` (MOV r/m8, imm8) handler
112//! fetched the immediate BEFORE resolving the displacement.
113//!
114//! **Round 6 — "Drive the full IC* decode pipeline end-to-end
115//! against Intel IR32_32.DLL".** No new emulator code needed:
116//! round-5's ISA + segment-prefix coverage is sufficient for the
117//! `ICDecompressQuery → ICDecompressBegin → ICDecompress →
118//! ICDecompressEnd` sequence to walk cleanly. The
119//! `tests/m2_indeo3_driverproc.rs::indeo3_decompress_one_keyframe`
120//! integration test exercises the whole sequence against a
121//! synthetic IV31 keyframe (64×48). The codec accepts the input
122//! / output formats, sets up internal state, rejects the
123//! synthetic NULL-data-size sync frame at the bitstream-header
124//! validation step (returns `ICERR_BADIMAGE = -100`), and tears
125//! down cleanly. SPECGAP: the `IV5PLAY` fixture bundle ships
126//! only DLLs, no `.avi` payloads, so round-6 cannot exercise a
127//! real keyframe end-to-end. Round 7+ swaps the synthetic input
128//! for a real keyframe once one becomes available.
129//!
130//! **Round 7 — "Real IV31 keyframe decode through `cubes.mov`,
131//! plus MMX scaffolding".** Twin deliverables:
132//!
133//! * **Part A — Real keyframe decode.** Adds a test-side
134//! QuickTime / ISO BMFF chunk walker
135//! (`tests/common/mov_extractor.rs`, ~270 LOC, authored from
136//! ISO/IEC 14496-12 alone) that locates sample 0 in
137//! `cubes.mov` (160×120 yuv410p, 121 KB) from
138//! `samples.oxideav.org/ffmpeg/V-codecs/IV32/`. The new
139//! `tests/round7_cubes_mov.rs::cubes_mov_first_keyframe_decodes_through_ir32_32_dll`
140//! feeds the real 3079-byte IV31 keyframe through the IC*
141//! sequence; `ICDecompress` returns `ICERR_OK` and ~30 K of
142//! the 57.6 KB RGB24 output is non-zero — the first real
143//! pixel decode through `IR32_32.DLL`. The bug fix that
144//! unblocks this:
145//! `ICM_DECOMPRESS_BEGIN` was at `ICM_USER + 16 = 0x4010`
146//! (round-5 typo) — an unmapped slot in `IR32_32.DLL`'s
147//! dispatch table. The canonical vfw.h value is
148//! `ICM_USER + 12 = 0x400C`. Without the BEGIN handler running,
149//! `ICDecompress` bailed early at a `[state2_ptr] != 0`
150//! sentinel check. While here,
151//! `ICM_DECOMPRESS_GET_FORMAT` corrected from
152//! `0x4008` → `0x400A`.
153//! * **Part B — MMX scaffolding** for round 8.
154//! [`emulator::Cpu`] grows an `mmx: [u64; 8]` register file
155//! (`mm0..mm7`, per Intel SDM Vol. 1 §9.2.1). A new
156//! [`emulator::Trap::UnimplementedMmx`] variant carries the
157//! 2-byte opcode + EIP + an SDM-derived mnemonic hint
158//! (`"PADDB MMX"`, `"PXOR MMX"`, `"EMMS"`, …). The
159//! `0x0F 0x60..0x6F`, `0x0F 0x70..0x7F`, and
160//! `0x0F 0xD0..0xFF` opcode blocks (per SDM Vol. 2
161//! Appendix A Table A-3) now route through
162//! `emulator::isa_int::dispatch_mmx` to the structured trap
163//! instead of the generic `UndefinedOpcode`. Round 8 reads
164//! the trap log to land MMX semantics opcode-by-opcode.
165//!
166//! **Round 18 — `trace` Cargo feature.** Resolves the planned
167//! "trace mode" milestone documented in
168//! `docs/winmf/winmf-emulator.md` §"Trace mode". A new feature
169//! gate `trace` (off by default) adds `#[cfg]`'d probe sites at
170//! the four natural choke points: every `dispatch_stub` call
171//! (`kind=win32_call`), every guest memory access overlapping
172//! a registered watchpoint (`kind=mem_write` / `kind=mem_read`),
173//! every trap that bubbles out of the run loop (`kind=trap`),
174//! and — under the `trace-exec` sub-feature plus
175//! `Sandbox::set_exec_trace(true)` — every executed
176//! instruction (`kind=exec`). Output is JSONL on a sink
177//! configured via `OXIDEAV_VFW_TRACE_FILE=<path|2>` or
178//! [`Sandbox::set_trace_sink`]. With the feature off, every
179//! probe compiles away; release builds are bit-identical to
180//! the round-17 baseline. Companion CLI is
181//! `oxideav-tracevfw`.
182//!
183//! Modern codecs (H.264 / HEVC / AV1 / Opus / AAC / …) are decoded
184//! natively elsewhere in the workspace; this crate exists for
185//! **rare/legacy** codecs the project would otherwise permanently
186//! shelve. Codec DLLs never execute on the host CPU; they run
187//! through the bounded-MMU interpreter.
188//!
189//! See `OxideAV/docs/winmf/winmf-emulator.md` (659 lines, 13
190//! sections) for the full design contract.
191
192#![forbid(unsafe_code)]
193// Mirror-from-oxideav-vfw allowances. Proper `Debug` impls and
194// clippy-pedantic cleanup land in follow-up commits once the
195// crate has been integrated with the decompile pipeline; the
196// initial mirror keeps the source verbatim modulo crate-name
197// shims for ease of diffing against the upstream.
198#![allow(missing_debug_implementations)]
199#![allow(clippy::pedantic)]
200#![allow(clippy::all)]
201
202pub mod com;
203pub mod context;
204pub mod coverage;
205pub mod emulator;
206pub mod ffi;
207pub mod pe;
208pub mod runtime;
209pub mod sched;
210#[cfg(feature = "trace")]
211pub mod trace;
212pub mod win32;
213
214pub use context::{
215 Context, FileAccess, FileHandle, OpenKey, RegistryKey, RegistryValue, VirtualFs,
216 VirtualRegistry, HKCR, HKCU, HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE,
217 HKEY_USERS, HKLM, HKU,
218};
219pub use coverage::CoverageMap;
220pub use ffi::{CallArgs, Dword, FromRet, Guest};
221
222pub use com::{
223 Guid, GuidParseError, CLSID_MEMORY_ALLOCATOR, IID_IBASEFILTER, IID_ICLASSFACTORY,
224 IID_IENUMPINS, IID_IFILTERGRAPH, IID_IMEDIAFILTER, IID_IMEDIASAMPLE, IID_IMEMALLOCATOR,
225 IID_IMEMINPUTPIN, IID_IPERSIST, IID_IPIN, IID_IUNKNOWN, MSADDS_AUDIO_DECODER_CLSID,
226 MSADDS_AUDIO_PROPERTY_PAGE_CLSID,
227};
228pub use runtime::{Sandbox, DLL_PROCESS_ATTACH};
229#[cfg(feature = "trace")]
230pub use trace::{TraceState, WatchMode, Watchpoint};
231pub use win32::vfw32::Bih;
232
233/// Crate-local error type. Each layer (MMU / decoder / executor /
234/// PE loader / Win32 stub) has its own variant; sublayers nest
235/// their detail enums.
236#[derive(Debug, Clone, PartialEq, Eq)]
237pub enum Error {
238 /// Reserved placeholder. Removed in a later round once every
239 /// caller has migrated to a more specific variant.
240 NotImplemented,
241 /// Guest tripped a CPU trap (memory fault, illegal opcode,
242 /// privileged instruction, division by zero, …).
243 Trap(emulator::Trap),
244 /// PE loader rejected the input bytes — bad signature,
245 /// unsupported PE32+ / .NET / packed binary, malformed
246 /// directory entries, missing import, etc.
247 PeLoader(pe::PeError),
248 /// A Win32 stub was called with an argument the round-1 stub
249 /// surface cannot satisfy (unknown DLL, unknown ordinal,
250 /// invalid heap handle, etc.).
251 Win32(win32::Win32Error),
252}
253
254impl core::fmt::Display for Error {
255 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
256 match self {
257 Error::NotImplemented => f.write_str(
258 "oxideav-vfw: round-1 does not yet implement this code path; \
259 see crates/oxideav-vfw/README.md for the milestone schedule.",
260 ),
261 Error::Trap(t) => write!(f, "oxideav-vfw emulator trap: {t}"),
262 Error::PeLoader(e) => write!(f, "oxideav-vfw PE loader: {e}"),
263 Error::Win32(e) => write!(f, "oxideav-vfw Win32 stub: {e}"),
264 }
265 }
266}
267
268impl std::error::Error for Error {}
269
270impl From<emulator::Trap> for Error {
271 fn from(t: emulator::Trap) -> Self {
272 Error::Trap(t)
273 }
274}
275
276impl From<pe::PeError> for Error {
277 fn from(e: pe::PeError) -> Self {
278 Error::PeLoader(e)
279 }
280}
281
282impl From<win32::Win32Error> for Error {
283 fn from(e: win32::Win32Error) -> Self {
284 Error::Win32(e)
285 }
286}
287
288/// Crate-local Result alias.
289pub type Result<T> = core::result::Result<T, Error>;