Pure-Rust 32-bit x86 emulator + PE loader + Video for Windows host. Lets oxideav delegate decoding (and eventually encoding) to legitimately-licensed Windows codec DLLs on any platform.
Round 1 — "Load + DllMain + clean exit". The crate ships:
- [
emulator::mmu] — flat 4 GiB virtual address space, sparse 4 KiB pages with R/W/X permission bits. - [
emulator::regs], [emulator::decode], [emulator::isa_int] — i386 register file, instruction decoder, executor for the integer base ISA. - [
pe] — PE32-only loader: DOS + PE header parse, section mapping into the MMU, base relocation, IAT resolution against the Win32 stub registry, export-by-name lookup. - [
win32::kernel32] — minimum stub set to satisfy a Cinepak-class DLL:GetProcessHeap/HeapAlloc/HeapFree/HeapReAlloc/LocalAlloc/LocalFree/OutputDebugStringA/GetTickCount/InterlockedIncrement/InterlockedDecrement/LoadLibraryA/GetProcAddress.
Round 2 — "Decode one Cinepak frame". Adds:
- [
Sandbox::call_export] — generic stdcall guest-call helper. - [
win32::vfw32] —BITMAPINFOHEADERmarshalling,ICDECOMPRESSlayout, and theIC*host surface (ICOpen,ICClose,ICGetInfo,ICDecompressBegin,ICDecompressQuery,ICDecompress,ICDecompressEnd) that drives the codec DLL'sDriverProcend-to-end. - [
Sandbox::install_codec] / [Sandbox::ic_open] etc — the ergonomic Rust-side wrappers the integration test uses.
Round 3 — "Real-codec smoke against IR32_32.DLL". Adds:
tests/common/mod.rs— fixture-discovery helper:OXIDEAV_VFW_FIXTURE_DIRenv var → Wine prefix → Windows system32 → on-disk cache → HTTPS fetch fromsamples.oxideav.org. CI=true bypasses the cache.- Round-3 m1 test asserted the exact set of 49 Win32 imports (gdi32 / user32 / winmm + 24 extra kernel32) the round-1+2 stub registry did not satisfy — round 4's concrete dispatch budget. Round 4 closed every gap; the m1 test now asserts zero unresolved imports.
tests/m2_indeo3_driverproc.rsretained the synthetic-codec walkthrough; a forward-compatible Indeo 3DllMain → ICOpen → ICGetInfo → ICClosewalkthrough that activated once round 4 closed the import gaps.
Round 4 — "Close the 49 round-3 import gaps". Adds the 49 stubs round 3 surfaced:
- [
win32::gdi32] — 8 fail-soft stubs forBitBlt/CreateCompatibleDC/DeleteDC/GetDeviceCaps/GetNearestColor/GetObjectA/GetSystemPaletteEntries/SelectObject. - [
win32::kernel32] — 24 round-4 stubs covering the CRT init surface (ExitProcess,GetACP/GetOEMCP/GetCPInfo,GetCommandLineA/GetEnvironmentStrings/GetFileType,GetLastError/SetLastError,GetModuleFileNameA/GetModuleHandleA,GetStartupInfoA/GetStdHandle/GetSystemInfo/GetVersion,GlobalAlloc/GlobalFree/GlobalLock/GlobalUnlock,MultiByteToWideChar/WideCharToMultiByte,RtlUnwind,VirtualAlloc/VirtualFree,WriteFile). - [
win32::user32] — 16 fail-soft stubs covering the dialog / paint surface;MessageBoxAlogs to stderr +host.message_box_log;wsprintfAis a real cdecl variadic implementation. - [
win32::winmm] —DefDriverProc(returns 0 / DRVCNF_OK). - [
emulator::mmu::Mmu::unmap] + [emulator::mmu::Mmu::find_free_range] for theVirtualAlloc/VirtualFreefamily.
With round 4 in place, IR32_32.DLL loads cleanly and
DllMain runs until it hits the first ISA opcode our integer
interpreter does not yet decode: ADD AL, imm8 (opcode
0x04) at eip = 0x1000_612A. That was round-4's hand-off
to round 5.
Round 5 — "DllMain + ICOpen + ICGetInfo + ICClose against Intel IR32_32.DLL". Adds:
- The 8-bit primary ALU opcodes (
0x00..=0x05ADD,0x08..=0x0DOR,0x10..=0x15ADC, …,0x38..=0x3DCMP) plusr/m8 imm8group-1 (0x80),r/m8group-3 (0xF6),r/m8group-4 (0xFE). - Group-2
r/m8shifts (0xC0/0xD0/0xD2) plus ther/m321/cl variants (0xD1/0xD3). IMUL r32, r/m32, imm32/imm8(0x69/0x6B),XCHG r/m, r(0x86/0x87),SAHF/LAHF(0x9E/0x9F),CMC(0xF5),PUSHAD/POPAD(0x60/0x61),ENTER(0xC8), the fullMOVS/CMPS/STOS/LODS/SCASfamily with REP / REPE / REPNE prefixes.0F 40..4F CMOVcc,0F A3 BT,0F AB BTS,0F A4..A5 SHLD,0F AC..AD SHRD,0F BAgroup-8 (BT/BTS/BTR/BTC imm8),0F B1 CMPXCHG,0F C1 XADD,0F C8..CF BSWAP.- Per-instruction segment-override prefix routing through
[
emulator::Cpu::set_fs_base] /set_gs_base. The runtime maps a 4 KiB TEB at0x7FFD_E000, primesFS:[0](SEH chain end-of-list =-1) andFS:[0x18](TEB self-pointer), and points FS at it. - Corrected
vfw32::ICM_*numeric values (ICM_GETINFO = 0x5002,ICM_DECOMPRESS = 0x400D, etc). - [
win32::vfw32::ic_open] now stages a real 36-byteICOPENso the codec'sDRV_OPENallocates per-instance state (round 4 passed NULL). - [
win32::vfw32::ic_get_info] falls back to an fcc-derivedszNamewhen the codec leaves it NUL (realvfw32!ICGetInfofills it from the registry). - Bug-fix: round-4's
0xC6(MOV r/m8, imm8) handler fetched the immediate BEFORE resolving the displacement.
Round 6 — "Drive the full IC decode pipeline end-to-end
against Intel IR32_32.DLL".* No new emulator code needed:
round-5's ISA + segment-prefix coverage is sufficient for the
ICDecompressQuery → ICDecompressBegin → ICDecompress → ICDecompressEnd sequence to walk cleanly. The
tests/m2_indeo3_driverproc.rs::indeo3_decompress_one_keyframe
integration test exercises the whole sequence against a
synthetic IV31 keyframe (64×48). The codec accepts the input
/ output formats, sets up internal state, rejects the
synthetic NULL-data-size sync frame at the bitstream-header
validation step (returns ICERR_BADIMAGE = -100), and tears
down cleanly. SPECGAP: the IV5PLAY fixture bundle ships
only DLLs, no .avi payloads, so round-6 cannot exercise a
real keyframe end-to-end. Round 7+ swaps the synthetic input
for a real keyframe once one becomes available.
Round 7 — "Real IV31 keyframe decode through cubes.mov,
plus MMX scaffolding". Twin deliverables:
- Part A — Real keyframe decode. Adds a test-side
QuickTime / ISO BMFF chunk walker
(
tests/common/mov_extractor.rs, ~270 LOC, authored from ISO/IEC 14496-12 alone) that locates sample 0 incubes.mov(160×120 yuv410p, 121 KB) fromsamples.oxideav.org/ffmpeg/V-codecs/IV32/. The newtests/round7_cubes_mov.rs::cubes_mov_first_keyframe_decodes_through_ir32_32_dllfeeds the real 3079-byte IV31 keyframe through the IC* sequence;ICDecompressreturnsICERR_OKand ~30 K of the 57.6 KB RGB24 output is non-zero — the first real pixel decode throughIR32_32.DLL. The bug fix that unblocks this:ICM_DECOMPRESS_BEGINwas atICM_USER + 16 = 0x4010(round-5 typo) — an unmapped slot inIR32_32.DLL's dispatch table. The canonical vfw.h value isICM_USER + 12 = 0x400C. Without the BEGIN handler running,ICDecompressbailed early at a[state2_ptr] != 0sentinel check. While here,ICM_DECOMPRESS_GET_FORMATcorrected from0x4008→0x400A. - Part B — MMX scaffolding for round 8.
[
emulator::Cpu] grows anmmx: [u64; 8]register file (mm0..mm7, per Intel SDM Vol. 1 §9.2.1). A new [emulator::Trap::UnimplementedMmx] variant carries the 2-byte opcode + EIP + an SDM-derived mnemonic hint ("PADDB MMX","PXOR MMX","EMMS", …). The0x0F 0x60..0x6F,0x0F 0x70..0x7F, and0x0F 0xD0..0xFFopcode blocks (per SDM Vol. 2 Appendix A Table A-3) now route throughemulator::isa_int::dispatch_mmxto the structured trap instead of the genericUndefinedOpcode. Round 8 reads the trap log to land MMX semantics opcode-by-opcode.
Round 18 — trace Cargo feature. Resolves the planned
"trace mode" milestone documented in
docs/winmf/winmf-emulator.md §"Trace mode". A new feature
gate trace (off by default) adds #[cfg]'d probe sites at
the four natural choke points: every dispatch_stub call
(kind=win32_call), every guest memory access overlapping
a registered watchpoint (kind=mem_write / kind=mem_read),
every trap that bubbles out of the run loop (kind=trap),
and — under the trace-exec sub-feature plus
[Sandbox::set_exec_trace(true)] — every executed
instruction (kind=exec). Output is JSONL on a sink
configured via OXIDEAV_VFW_TRACE_FILE=<path|2> or
[Sandbox::set_trace_sink]. With the feature off, every
probe compiles away; release builds are bit-identical to
the round-17 baseline. Companion CLI is
oxideav-tracevfw.
Modern codecs (H.264 / HEVC / AV1 / Opus / AAC / …) are decoded natively elsewhere in the workspace; this crate exists for rare/legacy codecs the project would otherwise permanently shelve. Codec DLLs never execute on the host CPU; they run through the bounded-MMU interpreter.
See OxideAV/docs/winmf/winmf-emulator.md (659 lines, 13
sections) for the full design contract.