EWM is an emulator for the machines Steve Wozniak built: the Apple 1, the Replica 1 and the Apple ][+ — plus the original and Enhanced Apple //e. It started life many years ago as a tiny 6502 emulator written between christmas and new year, and has since grown into a full emulator with Disk II support, graphics, and sound.
Note: EWM is a hobby project and still under development. Things may be incomplete, quirky, or broken — bug reports and pull requests are welcome. See REWRITE.md for the project's verification gates and a list of known quirks and deliberate divergences.
![]() |
![]() |
| Replica 1 — KRUSADER 1.3 from the Woz Monitor | Apple ][+ — booting the DOS 3.3 sample programs disk |
![]() |
![]() |
| Frogger in color hi-res graphics | Bandits by Sirius Software |
- Apple 1 — 6502, 8KB RAM, Woz Monitor
- Replica 1 — 65C02, 32KB RAM, KRUSADER assembler ROM
- Apple ][ — the original 1978 machine: 6502, 48KB RAM, Integer
BASIC and the non-autostart Monitor in ROM (
builtin:apple2) - Apple ][+ — 6502, 48KB RAM, Apple Language Card, Disk II with two
drives, a slot 7 hard drive for 32MB ProDOS block images (boots
Total Replay!), 40-column text,
low-resolution and high-resolution graphics on a green, amber or white
monochrome monitor or in color (
--set display:monitor=rgb) with an optional scanline effect (--set display:scanlines=light), both switchable at runtime from the command palette, speaker sound, joystick paddles and buttons (game controllers hot-plug — Bluetooth pads connect any time — and the command palette picks between several) - Disk images —
.dsk/.do/.posector images,.nibnibble images, bit-accurate WOZ 1.0 images with copy-protection support (E7, RWTS18, half-tracks, MC3470 weak bits — seenotes/WOZ1.mdfor the compatibility table), and.2mgimages of 400K/800K 3.5" disks in the UniDisk 3.5 Controller ("Liron",{"card": "liron"}), a SmartPort card ProDOS boots from - Apple //e — the original (6502,
builtin:apple2e) and the Enhanced (65C02 + MouseText,builtin:apple2enhanced); 128KB main + auxiliary RAM, the built-in language card and MMU/IOU soft switches, a swappable auxiliary slot (machine.aux): the Extended 80-Column Text Card (64K, default), the plain 80-Column Text Card (1K), or an Applied Engineering RamWorks III with up to 8MB (--set 'machine:aux={"card":"ramworksiii","size":"1m"}'), 40- and 80-column text with lower case (and MouseText on the Enhanced; the original shows inverse upper case there), lo-res / hi-res / double-lo-res / double-hi-res graphics, and the //e keyboard (Open/Solid-Apple keys). Reuses the Disk II, hard drive, clock and sound. - AppleMouse II — an Apple II mouse in any slot (slot 4 by default,
{"card": "mouse"}), both ][+ and //e; the host pointer (the SDL window or a VNC client) drives it through the firmware's eight entry points, with VBL / movement / button interrupts, so MousePaint and GEOS see a mouse. Seenotes/MOUSE.md.
- A Rust toolchain (the pinned version is in
rust-toolchain.toml; rustup picks it up automatically) - SDL2 —
brew install sdl2on macOS,apt install libsdl2-devon Debian/Ubuntu
cargo build --release
Running EWM with no arguments opens the bootloader, a menu where keys 1/2/3 select the machine to start:
cargo run --release
Or start a machine directly:
# Apple ][+ with color graphics and the DOS 3.3 sample programs disk
cargo run --release -- two --set display:monitor=rgb \
--set machine:slots:6:drive1=disks/DOS33-SamplePrograms.dsk
# Apple ][+ booting Total Replay from a ProDOS hard drive image
cargo run --release -- two --set display:monitor=rgb \
--set 'machine:slots:7={"card":"harddrive","image":"disks/Total Replay v6.0.1.hdv"}'
# A copy-protected WOZ 1.0 image (bit-accurate Disk II emulation)
cargo run --release -- two --set display:monitor=rgb \
--set "machine:slots:6:drive1=disks/woz/WOZ 1.0/Commando - Disk 1, Side A.woz"
# Enhanced Apple //e (a built-in config) booting DOS 3.3 (try PR#3 for 80-column lower case)
cargo run --release -- two --config builtin:apple2enhanced \
--set machine:slots:6:drive1=disks/DOS33-SystemMaster.dsk
# Enhanced Apple //e with an 8MB RamWorks III in the auxiliary slot
cargo run --release -- two --config builtin:apple2enhanced \
--set machine:aux:card=ramworksiii \
--set machine:slots:6:drive1=disks/DOS33-SystemMaster.dsk
# An AppleMouse II in slot 4 (the host pointer drives it): MousePaint, GEOS
cargo run --release -- two --config examples/mouse.json
# Replica 1 (Woz Monitor + KRUSADER)
cargo run --release -- one --config builtin:replica1
# Classic Apple 1
cargo run --release -- one --config builtin:apple1
Bare ewm two boots builtin:apple2plus — the default machine is
a built-in config, not a separate in-code layout, so ewm two and
ewm two --config builtin:apple2plus build the identical machine: an
Apple ][+ with the 64K Language Card and a Disk II in slot 6, on a
green monochrome monitor.
The built-in machine configs ship inside the binary —
--config builtin:list lists them, and the same files live in
configs/:
builtin:apple2plus — an Apple ][+ with the 64K Language Card and a
Disk II in slot 6, on a green monochrome monitor. The default machine
minus the clock card:
{
"description": "Apple ][+ — 64K Language Card, Disk II in slot 6, green monitor",
"machine": {
"model": "apple2plus",
"slots": {
"0": { "card": "language" },
"6": { "card": "diskii" }
}
},
"display": { "monitor": "green" }
}cargo run --release -- two --config builtin:apple2plus
builtin:apple2e — the original (unenhanced, 1983) Apple //e: a 6502
with the same layout as builtin:apple2enhanced below (Extended 80-Column
Card, UniDisk 3.5 in slot 5, Disk II in slot 6, RGB monitor), but without the
65C02 and MouseText — the alternate character set shows inverse upper case
where the Enhanced //e shows MouseText:
{
"description": "Apple //e — Extended 80-Column Card, UniDisk 3.5 in slot 5, Disk II in slot 6, RGB monitor",
"machine": {
"model": "apple2e",
"aux": { "card": "ext80col" },
"slots": {
"5": { "card": "liron" },
"6": { "card": "diskii" }
}
},
"display": { "monitor": "rgb" }
}cargo run --release -- two --config builtin:apple2e
builtin:apple2enhanced — an Enhanced Apple //e with the Extended 80-Column
Card (64K) in the auxiliary slot, a UniDisk 3.5 controller in slot 5,
a Disk II in slot 6, and an RGB color monitor:
{
"description": "Enhanced Apple //e — Extended 80-Column Card, UniDisk 3.5 in slot 5, Disk II in slot 6, RGB monitor",
"machine": {
"model": "apple2enhanced",
"aux": { "card": "ext80col" },
"slots": {
"5": { "card": "liron" },
"6": { "card": "diskii" }
}
},
"display": { "monitor": "rgb" }
}cargo run --release -- two --config builtin:apple2enhanced
builtin:apple2 — the original 1978 Apple ][: a 48K machine
with Integer BASIC and the non-autostart Monitor in ROM (distinct
from the ][+'s Applesoft/Autostart), no language card, and a Disk II
in slot 6:
{
"description": "Apple ][ — 48K, Integer BASIC, Disk II in slot 6, green monitor",
"machine": {
"model": "apple2",
"slots": {
"6": { "card": "diskii" }
}
},
"display": { "monitor": "green" }
}cargo run --release -- two --config builtin:apple2
With no Autostart, this machine resets to the Monitor * prompt rather
than booting a disk: Ctrl-B enters Integer BASIC, and a disk in slot
6 boots with C600G (from the Monitor) or PR#6 (from BASIC) — the
1978 experience.
None of the profiles mounts a disk — pair them with a --set override
or an overlay, as in the examples above.
ewm one speaks the same configuration language (same sources, same
built-ins mechanism, same --print-config), and its profiles are the
machine's schematic: the CPU, every RAM bank with its size and address,
and every ROM image with its address, all in the JSON. ROM images ship
inside the binary and mount with builtin:<name> in a region's path
— builtin:WozMon, builtin:apple1-basic, builtin:Krusader-1.3-6502,
builtin:Krusader-1.3-65C02 — or from your own ROM file by path. The
only fixed hardware is the PIA (keyboard in, video out) at $D010.
Bare ewm one boots the Replica 1. (The memory maps and ROM
provenance are in notes/APPLE1.md.)
builtin:apple1 — the classic Apple 1: a 6502, 4KB RAM at
$0000, a second 4KB RAM bank at $E000 preloaded with Woz's Integer
BASIC (the real machine loaded it from cassette every power-on; type
E000R at the monitor to start it), and the 256-byte Woz Monitor ROM
at $FF00:
{
"description": "Classic Apple 1 — 6502, 4KB+4KB RAM, Integer BASIC preloaded, Woz Monitor",
"machine": {
"model": "apple1",
"cpu": "6502",
"memory": [
{ "type": "ram", "address": "0x0000", "size": "4k" },
{ "type": "ram", "address": "0xe000", "path": "builtin:apple1-basic" },
{ "type": "rom", "address": "0xff00", "path": "builtin:WozMon" }
]
}
}cargo run --release -- one --config builtin:apple1
builtin:replica1 — the Replica 1: a 65C02, 32KB RAM, and the
real board's ROM set — Integer BASIC at $E000 and Krusader 1.3 (the
65C02 build, which carries its own monitor page at $FF00) at
$F000. Byte for byte, $E000-$FFFF is the Krusader 1.3
distribution image:
{
"description": "Replica 1 — 65C02, 32KB RAM, Integer BASIC + Krusader 1.3 in ROM",
"machine": {
"model": "replica1",
"cpu": "65C02",
"memory": [
{ "type": "ram", "address": "0x0000", "size": "32k" },
{ "type": "rom", "address": "0xe000", "path": "builtin:apple1-basic" },
{ "type": "rom", "address": "0xf000", "path": "builtin:Krusader-1.3-65C02" }
]
}
}cargo run --release -- one --config builtin:replica1
When a config names machine.memory, it describes the whole board —
absent, the model's built-in board applies — and validation keeps it
honest: regions must not overlap each other or the PIA, and something
must cover the reset vector ($FFFC-$FFFD) or the machine cannot
boot. Beyond the components, an apple1/replica1 config takes only
the keys that exist on the machine — cpu.strict and debug.trace;
anything else (slots, display, remote, …) is rejected by
validation with an error naming the key.
--set <key>=<value> overrides one value in the machine configuration
by its colon-separated key path — any key the JSON config (below)
accepts, so --set display:monitor=amber or --set cpu:speed=3.58mhz
work the same way. Values are JSON when they parse as JSON (numbers,
booleans, whole objects like the hard-drive example above) and plain
strings otherwise.
The machine configuration is fully compositional: three source kinds, one document, merged strictly in command-line order —
--config <source>— a complete machine, from a JSON file or a built-in config; at most one, the base of the document;--config-overlay <source>— a partial config layered on top; repeatable;--set <key>=<value>— single-value overrides.
An overlay describes just the part of the machine it cares about. This
one (examples/drive-with-total-replay.json) adds a hard drive with
Total Replay to whatever machine it lands on:
{
"$schema": "https://raw.githubusercontent.com/st3fan/ewm/main/schema/ewm-config-overlay.schema.json",
"machine": {
"slots": {
"7": { "card": "harddrive", "image": "../disks/Total Replay v6.0.1.hdv" }
}
}
}cargo run --release -- two --config builtin:apple2plus \
--config-overlay examples/drive-with-total-replay.json \
--set display:monitor=amber
Overlays without a --config extend the default machine, so the same
overlay alone means "the default ][+ plus a hard drive in slot 7":
cargo run --release -- two --config-overlay examples/drive-with-total-replay.json
A whole machine also fits in one file (examples/myiie.json):
{
"$schema": "https://raw.githubusercontent.com/st3fan/ewm/main/schema/ewm-config.schema.json",
"machine": {
"model": "apple2enhanced",
"aux": { "card": "ramworksiii", "size": "1m" },
"slots": {
"1": { "card": "thunderclock" },
"5": { "card": "diskii", "drive1": "../disks/work.dsk" },
"6": { "card": "diskii", "drive1": "../disks/DOS33-SystemMaster.dsk" },
"7": { "card": "harddrive", "image": "../disks/Total Replay v6.0.1.hdv" }
}
},
"display": { "monitor": "green", "scanlines": "light" },
"cpu": { "speed": "normal" }
}cargo run --release -- two --config examples/myiie.json
The machine's physical layout lives in slots: any card in any slot,
up to three Disk ][ controllers (six drives), multiple hard drives,
empty slots — the Autostart scan boots the highest populated slot, as
on hardware. On the ][+, slot 0 is the memory-expansion socket: the
default machine has a Language Card (the classic 64K build), and
--set machine:slots:0:card=saturn128 swaps it for a Saturn Systems
128K RAM Board — eight Language-Card-compatible 16K banks. An explicit
slots table is taken literally, so leave out "0" — or pass
--set machine:slots:0:card=empty — for a stock 48K machine.
Relative paths resolve against their file's directory, so a config — or an overlay — travels with its disks.
A disk image can also be an http(s):// URL, so a config travels
without its disks at all:
cargo run --release -- two \
--set 'machine:slots:6:drive1=https://example.com/disks/Frogger.dsk'
The image is downloaded once into $XDG_CACHE_HOME/ewm (default
~/.cache/ewm), keyed by a hash of the URL and keeping its own file
name. Later boots send a conditional request and reuse the cached copy
on a 304 — and if the server is unreachable, the cached copy is used
anyway, so a downloaded machine still boots offline. Downloaded hard
drives mount memory-only: ProDOS can write to them, but the writes
stay in RAM (exactly as floppies behave) so refreshing the cache can
never destroy them.
The committed JSON Schemas
(schema/ewm-config.schema.json for complete configs,
schema/ewm-config-overlay.schema.json for overlays) give editors
validation and autocomplete via the $schema key;
notes/JSON_CONFIG.md has the full plan.
With sources layering, --print-config answers "what machine did I
just describe?" — it prints the final merged configuration (sources
and convenience flags applied) as JSON and exits, nonzero on any
error, so it doubles as a config linter for scripts and CI:
cargo run --release -- two --config examples/myiie.json --set display:monitor=amber --print-config
Each subcommand accepts --help for all options.
| Key | Action | Machines |
|---|---|---|
| Cmd-K | Open or close the Command Palette | all |
| Cmd-R | Reset the machine (warm) | all |
| Cmd-Shift-R | Reboot: power off, power on | ][+ / //e |
| Cmd-P | Pause / unpause | ][+ / //e |
| Cmd-Return | Toggle fullscreen | all |
| Cmd-I | Toggle the status bar with drive lights | ][+ / //e |
While the palette is open it owns the keyboard: type to filter, ↑/↓ to move, Return to run the selected command, Esc (or Cmd-K again) to close. Every other key goes to the emulated machine — including Ctrl-A…Z, which arrive as control characters. Dropping a disk image on a running ][+ / //e window swaps it into drive 1.
There are also headless terminal consoles, handy for quick experiments without a window:
cargo run --release -- one --tty # Woz Monitor / Integer BASIC (Meta-R resets)
cargo run -p ewm --example two -- disks/DOS33-SystemMaster.dsk # AppleSoft / DOS 3.3
cargo test
This runs the full verification suite: the Klaus Dormann 6502 and 65C02
functional tests, golden instruction traces, headless machine boot tests,
a complete DOS 3.3 boot with CATALOG, and a golden screenshot comparison.
Working, but still settling — each has a full working document in
notes/:
- WozBug — a minimal Woz-Monitor-dialect debugger served on a local
TCP port:
--wozbug [port],--break RWTSarms breakpoints at boot, thennc localhost 6502. See DEBUGGING_TOOLS.md. - Remote console (VNC) —
--serve vnc://…(or the configremoteblock) boots headless and serves the machine over RFB to any VNC client, with an optional embedded browser console (noVNC). See REMOTE.md. - Native Mac app —
scripts/make-app.shbuilds a self-contained, double-clickabledist/EWM.app; opening or dropping a disk image on it boots that disk. See MAC_APP.md. - Apple 1 over telnet —
ewm one --ttyis a headless machine whose keyboard and display are stdin/stdout (Meta-R or telnet'ssend brkis the RESET button),--tty-bannergreets callers with a text file of instructions, and inetd-style socket units maketelnet host 6502boot a fresh Apple 1 per connection. Build & server setup guide: scripts/systemd/README.md; design notes: APPLE1.md.
EWM was originally written in C and was ported to Rust in 2026 — the full phase-by-phase plan, parity checklist, and benchmark numbers are preserved in REWRITE.md. The original C implementation lives in the git history.
MIT, as declared in Cargo.toml (the same license the original C carried
in its source headers).


![Apple ][+ booting DOS 3.3](/st3fan/ewm/raw/main/screenshots/Screenshot%202026-07-05%20at%2011.30.59.png)

