Boot real Windows CE and Windows Mobile ROMs on modern x64 Windows. Run unmodified ARM PE binaries — shells, drivers, apps — with the original operating system brought up around them.
Warning
Early stage. Most things don't work yet. Expect crashes, missing APIs, and broken apps. Treat CERF as an experiment, not a usable emulator.
Successor to wcecl.
A full Windows CE system emulator. Under the hood: an ARM CPU emulator, a reimplementation of the CE kernel and its process/MMU model, and a rehosted windowing subsystem — wired together so real ROM binaries boot end-to-end on top of host Windows.
The entire CE backend — the kernel-mode side plus the system services that coredll.dll, gwes.exe, and device.exe sit on — is reimplemented inside cerf.exe. Init sequence, registry, filesystem, handle table, process/MMU model, driver host: all of it lives in CERF. Unmodified ROM binaries — shells, drivers, apps — run on top exactly as they would on a real device. Windowing and driver-facing surfaces ultimately reach host Win32 primitives, but the CE semantics that sit on those primitives live in CERF itself.
- ARM CPU emulator (block JIT). Every
.exeand.dllon the device runs as its original ARM machine code, translated to x64 on the fly by dynarmic. - Full
gwes.exereplacement. The Graphics/Windowing/Events Subsystem is rehosted on native Win32. Host Win32 windows, messages, and input are bridged back into ARM WNDPROCs through a callback executor — the guest sees real CE gwes; the host sees a well-behaved Win32 app. - Pixel-accurate control rendering and theming.
Button,Static,Edit,ListBox,ScrollBar,Dialog, andMenuship as native fallback WNDPROCs that mirror CE5 gwes down to default-message handling and owner-draw quirks. CE themes render. Renderers are strategies; swapping in a Windows Mobile look or a custom theme is a matter of registering another one. - Kernel, process, and MMU emulation. Per-process 32 MB
ProcessSlotoverlays replicate CE's slot-based address space. Multiple CE kernel versions are supported via pluggable strategies — CE5 and CE6 ship different PSL dispatchers, different TCP-stack plumbing, different shell-folder lookups. More can be added the same way. - PSL (Protected Server Library) cross-process dispatch. When an app calls into a driver, CERF switches address spaces, marshals pointer arguments between caller and callee slots, and runs the handler — exactly as real CE does. Nested
MapCallerPtrworks. device.exemock as a real driver host. ARM driver DLLs (AFD, tcpstk, and friends) load into an emulated driver process with its own slot.DeviceIoControlfans out through PSL with correct slot switching.- Hardware-layer thunks with access levels. Calls that would normally talk to silicon are intercepted at the kernel/COREDLL boundary, with privilege and access enforced inside the thunks themselves rather than faked at a higher layer.
- DLL loader that behaves like
kern.exe. Loads ARM PEs into emulated memory, patches IATs, runs per-processDllMain, tracks module refcounts via per-DLL bitmasks the way real CE does. - Emulated registry, virtual filesystem, handle table, critical sections, thread registry. Enough real kernel state for a real boot.
- Init hive boot sequence. Processes come up from
HKLM\initin dependency order, the same wayfilesys.exebrings up a real device. - OOP rewrite (in progress, almost done). No globals, no statics — every subsystem lives on a
CerfEmulatorinstance and is resolved through a service locator. The payoff: twoCerfEmulators inside onecerf.exeshare nothing and can boot different devices side by side.
Device behaviour is selected at boot from a profile under bundled/devices/<name>/ — a directory containing the ROM filesystem, registry hive, and a device-specific cerf.ini. The .ini picks named strategies for subsystems that differ between CE versions:
psl_dispatch = ce5_psl | ce6_psl
sh_thunk = ce5 | wm5_psl
Adding a new device is a config-and-assets drop — no code changes are needed when the strategies it requires already exist.
| Profile | Status |
|---|---|
wince5 |
Largely supported; the primary development target. IE works. Many APIs still missing. MMU and process isolation may have bugs. |
wm5 |
Bootable. WIP. Anything past the today screen is broken. Expected to reach CE5-level stability over time since both share the same base OS. Reconstructed from a B000FF baked-memory ROM via extract-wince-rom into regular PE files with IATs. |
wince6 |
Bootable without drivers. Needs its own PSL/MMU/isolation strategy (the CE7 strategy is likely a drop-in fit). Only basic apps are expected to work at present. |
wince7 |
Bootable without drivers. Needs its own PSL/MMU/isolation strategy. Only basic apps are expected to work at present. Boots with the gwe_api_sets_ce7_service.cpp workaround enabled. |
cerf.exe # Boot default device (WinCE 5 desktop)
cerf.exe --device=wm5 # Boot Windows Mobile 5
cerf.exe --device=wince6 # Boot WinCE 6.0
cerf.exe --device=wince7 # Boot WinCE 7.0
cerf.exe --flush-outputs # Force-flush logs (avoid truncation on crash)
Logs are written to cerf.log by default. On a fatal crash, register state and a top-of-stack snapshot for every other thread is dumped to cerf.crash.log next to it through a lock-free emergency writer (no ucrt, no mutexes) — useful when the reader thread crashes but a different thread is the actual writer. Run cerf.exe --help for the full CLI.
Tip
For fastest runtime, pass --log=none (or at minimum --no-log=trace). CERF ships with every log category on by default because the project is early-stage and logs are the primary debugging tool — every category on the hot path writes to cerf.log through a serialized critical section, which dominates a real workload. Disabling logging is the main per-run performance lever; turn it back on (or narrow it to --log=EMU,API,...) when diagnosing a crash or investigating behaviour.
Requires Visual Studio 2022 or 2026 with the C++ desktop development workload.
Note
First build on a fresh machine takes 1+ hour. libslirp, glib, and their transitive dependencies are compiled from source by vcpkg before CERF itself starts linking. This happens once per machine — subsequent builds reuse the cached vcpkg_installed/ tree and finish in a few minutes. Do not interrupt the first build assuming it's stuck; watch the msbuild output for vcpkg restore progress.
Initialise submodules:
git submodule update --init --recursive
Build via the helper script (preferred — locates MSBuild via vswhere, kills stale build processes, and reports the produced binary):
powershell -ExecutionPolicy Bypass -File build.ps1
Or invoke msbuild directly:
msbuild cerf.sln /p:Configuration=Release /p:Platform=x64
Unit and integration tests use gtest and live under tests/, grouped by subsystem (e.g. tests/mmu/). They build into a single cerf_tests.exe alongside cerf.exe:
build/Release/x64/cerf_tests.exe
Tests share one in-process CerfEmulator brought up with --ephemeral-registry --disable-network, so the suite runs in well under a second and does not touch the device tree.
python e2e_tests/run_all.py
Each test writes its own log file; see the test sources for paths.
Each device profile lives in bundled/devices/<name>/. To target a new platform build, drop in the ROM filesystem and registry hive, write a cerf.ini that picks the matching strategies, and boot. Strategies self-register into the service locator and are selected by name from cerf.ini, so adding one touches no existing files. The sh_thunk subsystem (cerf/coredll/sh_thunks_ce5_service.cpp, cerf/coredll/sh_thunks_wm5_service.cpp) is the reference example to follow when implementing a new strategy.
No formal docs yet. Start with CLAUDE.md and agent_docs/subsystems.md.
CERF depends on two external runtime libraries:
- dynarmic — ARM → x64 JIT that executes the guest's machine code. CERF tracks its own fork of azahar-emu/dynarmic.
- libslirp — user-mode TCP/IP stack behind the virtual NDIS miniport (DHCP, DNS, TCP, UDP, IPv4, IPv6 via SLAAC). No admin or driver install required; pulled in automatically via vcpkg on first build.
CERF is an experimental project. Active development is expected to wind down once these milestones are reached:
- Windows Mobile 5 boots and is usable.
- Networking works in CE5 and WM5, ideally across the other device profiles too.
- DOOM runs.
The entire codebase was generated by Claude via Claude Code with no human-written code. It is not production-grade: there are likely bugs, shortcuts, and possibly fundamental issues in the emulation layers. Style and patterns also drift between files at this scale, with load-bearing invariants that live in prompts rather than asserts — debugging is rougher than a human-written codebase of comparable size. The project is nonetheless held to emulator-grade quality standards: faithful CE behaviour is the point, not a tech demo. The work began as an experiment in what Claude can produce in a real systems-programming setting and as a way to revive the wcecl concept end-to-end.

