Search › A-Z › A › Apple IIgs (KEGS)
Apple IIgs (KEGS)
This is Kent Dickey's KEGS - a full-featured, cycle-accurate Apple IIgs emulator - rebuilt from its C source to WebAssembly so it runs entirely in the browser. It emulates the WDC 65C816 CPU, the Mega II and the Ensoniq sound chip, the Super Hi-Res display, and the 3.5"/5.25"/SmartPort drives. The build boots the Apple IIgs on its real ROM and a system disk.
Unlike a stock WebAssembly port, this build was rebuilt with a small sampling API over KEGS's own debugger, so the shared in-browser debugger gets genuine live 65C816 registers, side-effect-free memory, real single-instruction step, and real breakpoints and watchpoints - enforced by the emulator core, not faked.
Visit the official KEGS site ↗
Runs on: Web browser
Apple IIgs (KEGS) Online Emulator
Play Apple IIgs (KEGS) using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Apple IIgs · System Disk (ProDOS) | Apple IIgs (KEGS) | Apple IIGS | grey | Open ⛶ | |
| Apple IIgs · X-MAS Demo (Super Hi-Res) | Apple IIgs (KEGS) | Apple IIGS | open | Open ⛶ | |
| Apple IIgs · ROM 01 self-test (no disk) | Apple IIgs (KEGS) | Apple IIGS | grey | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
KEGS is a portable-C Apple IIgs emulator. Its core (the 65C816 interpreter engine_c.c, the Mega II, video and disk) is platform-independent; only a thin driver layer (X11 / Windows / Mac) touches the host. We compile the core with Emscripten and replace that driver layer with a small emsdriver.c - no SDL. KEGS renders each frame into a 32-bit pixel buffer (g_mainwin_kimage.wptr), which JavaScript reads straight out of the WASM heap and blits to a 2D canvas, exactly like the vAmiga port.
var Module = await createKegs({ locateFile: f => SRC + f }); // loads kegs.wasm
Module.FS.writeFile('ROM', rom01Bytes); // the IIgs ROM, as the file KEGS opens
Module.FS.writeFile('config.kegs', 's5d1 = sysdisk.2mg\n'); // slot 5 = 3.5" disk
Module.ccall('wasm_boot', 'number', [], []); // parse_argv + kegs_init: builds the machine
Rendering is manual. Each animation frame we advance the machine by one video field and copy its pixel buffer (KEGS writes 0x00RRGGBB; we add the alpha byte) into the canvas:
var src = new Uint32Array(Module.HEAPU8.buffer, Module._wasm_pixel_buffer(), w*h);
for (var i=0; i<w*h; i++){ var v=src[i]; dst[i] = 0xff000000 | ((v&0xff)<<16) | (v&0xff00) | ((v>>16)&0xff); }
| Export | What it does |
|---|---|
wasm_boot() | Build the machine (after the ROM + config + disk are in the Emscripten FS). |
wasm_dbg_run_frame() | Run exactly one video field of 65C816 time; returns 1 if a breakpoint paused the core. |
wasm_dbg_step() | Run exactly ONE 65C816 instruction (via KEGS's g_stepping). |
wasm_pixel_buffer() | Pointer to the 32-bit ARGB-ish frame buffer in the WASM heap. |
wasm_key(a2code, up) | Inject an Apple ADB key code; drives the physical + on-screen keyboard. |
wasm_dbg_a / _x / _y / _pc / _psr … | Sample one 65C816 register; matching wasm_dbg_set_* write it back. |
wasm_dbg_read / _write(addr) | Side-effect-free read / write of the 24-bit 65C816 bus. |
wasm_dbg_bp_set / _wp_set(addr) | Install a real execution breakpoint / write watchpoint (KEGS guard). |
Debugger integration
This is a WebAssembly core, yet it gets the same live debugger as the pure-JavaScript machines. KEGS already ships an interactive debugger (debugger.c) with a real single-step and a real breakpoint facility; we simply expose that machinery to JavaScript as a handful of sampling functions. They are called ~10×/second while the debugger window is open, and once per Step, never on the emulator's hot path (the project's golden performance rule). The stock KEGS run loop is byte-for-byte unchanged.
1 · Exactly what was changed. KEGS keeps the 65C816 register file in one global, Engine_reg engine (fields acc, xreg, yreg, stack, direct, dbank, psr and kpc, which packs the program-bank register with the 16-bit PC). We added wasmdbg.c, ~40 EMSCRIPTEN_KEEPALIVE functions that read/write that struct and drive KEGS's own control paths:
// wasmdbg.c, sampling only, no hot-path hook
extern Engine_reg engine; // KEGS's live 65C816 register file
u32 wasm_dbg_a(){ return engine.acc & 0xffff; }
u32 wasm_dbg_pc(){ return engine.kpc & 0xffff; } // PC; pbr = (kpc>>16)
u32 wasm_dbg_read(u32 a){ return get_memory_c(a) & 0xff; } // side-effect free
void wasm_dbg_step(){ g_stepping = 1; run_a2_one_vbl(); g_stepping = 0; } // ONE instruction
void wasm_dbg_bp_set(u32 a){ set_bp(a, a, 4); } // KEGS native execute guard
void wasm_dbg_wp_set(u32 a){ set_bp(a, a, 3); } // read|write watchpoint
Single-step is real: KEGS sets the engine's cycle budget to exactly one instruction when g_stepping is set, so run_a2_one_vbl() returns after one opcode. Breakpoints are real: set_bp arms a per-page guard that the engine checks only for pages that carry one (via fixup_brks), not a per-instruction callback, and a hit calls KEGS's halt2_printf, which sets g_halt_sim; the run loop notices and stops.
2 · The Emscripten driver. KEGS's X11 driver (xdriver.c) owns main() and the frame loop and, crucially, sets the display's RGB masks and builds the colour palette. We replaced it with emsdriver.c: a no-op window layer that lets JavaScript own the loop, forwards keys through KEGS's own adb_key_event, and, the one non-obvious step, sets the RGB masks to 0x00RRGGBB and calls video_set_palette() at boot (without it every pixel draws black). Sound / joystick / serial host layers are stubbed (headless in the browser today).
3 · What is REAL here. Live registers A, X, Y, SP, D, DBR, PBR, PC and the P status byte (with the emulation flag E and the native-mode width flags M and X), disassembled with the shared w65816 decoder; the full 24-bit bus read side-effect-free through get_memory_c; single-instruction step; and execution breakpoints + write watchpoints enforced inside the KEGS core. Nothing is faked host-side.
Architecture
KEGS (Kent's Emulated GS) is a full, cycle-accurate Apple IIgs. The whole machine is portable C compiled to one WASM module:
- 65C816 - the 16-bit CPU, interpreted in
engine_c.c; its registers live in the globalEngine_reg engine. This is what the debugger samples. - Mega II + FPI/CYA - the custom glue that makes it a IIgs: bank shadowing, the slow-RAM ($E0/$E1) I/O and video space, and speed control.
- Video - text, lo-res, hi-res, double-hi-res and the 320/640 Super Hi-Res modes, rendered per scanline into the pixel buffer JavaScript blits.
- Ensoniq DOC - the 32-oscillator wavetable sound chip (emulated in-core; audio output is not wired up in this browser build).
- IWM + SmartPort - 3.5" (slot 5) and 5.25" (slot 6) drives and the SmartPort hard-disk interface, reading .2mg / .po / .dsk / .woz images.
- ROM - the Apple IIgs ROM 01 (Apple ©), mapped to banks $FE–$FF; with a system disk it boots ProDOS or GS/OS.
How to build this exact artefact. Toolchain: Homebrew emscripten 6.0.3 (emcc on PATH).
git clone https://github.com/a2kegs/kegs.git # KEGS, GPL-3.0
# add two files to src/: emsdriver.c (Emscripten host driver + main/wasm_boot,
# sets RGB masks + video_set_palette, stubs sound/joystick/serial) and
# wasmdbg.c (the wasm_dbg_* sampling API over Engine_reg + set_bp/g_stepping).
# in debugger.c: reduce PC_LOG_LEN from 2M to 8K (trims ~112MB of static trace
# buffers the web debugger never uses, so the wasm needs little memory).
emcc -O2 src/*.c # (core objects only; NOT xdriver/scc*driver/*snd_driver/joystick_driver)
-sMODULARIZE=1 -sEXPORT_NAME=createKegs -sEXIT_RUNTIME=0 -sALLOW_MEMORY_GROWTH=1
-sFORCE_FILESYSTEM=1 -sEXPORTED_RUNTIME_METHODS=ccall,FS,HEAPU8
-sEXPORTED_FUNCTIONS=_wasm_boot,_wasm_dbg_*,_wasm_key,_wasm_pixel_buffer,...
-o kegs.js # -> kegs.js + kegs.wasm (vendored here)
The only source edits are the two additive files, the PC_LOG_LEN trim, and dropping the platform driver objects - the emulation hot path is untouched. The IIgs ROM 01 dump we ship has its two 64K halves stored high-bank-first; KEGS wants bank $FE then $FF, so the halves are swapped in the vendored apple2gs-rom01.bin.