js-dos
js-dos is a WebAssembly build of DOSBox with a small JavaScript API, used to embed playable DOS games and utilities directly in a web page. It is the technology behind the Internet Archive’s in-browser MS-DOS collection.
Runs on: Web browser
js-dos Online Emulator
Play js-dos using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| DOS prompt | js-dos | IBM PC/AT | DOS (MS-DOS / DR-DOS) | open | Open ⛶ |
Machines emulated
Operating systems
Chips
Notes
Embedding
js-dos is DOSBox compiled to WebAssembly with Emscripten. It is one ordinary Emscripten Module: you give it a <canvas>, optional command-line arguments, and it fetches dosbox.js + dosbox.wasm and boots. We self-host everything (no CDN); there is no external ROM because DOSBox ships its own DOS kernel and BIOS, so the whole thing is GPL-2 and freely redistributable.
var Module = {
canvas: document.getElementById('canvas'),
arguments: ['-c', 'mount c /c', '-c', 'c:'], // land at C:\>
preRun: [ function(){ Module.FS.mkdir('/c'); } ],
onRuntimeInitialized: boot // wasm ready -> publish EMU_BOOT
};
// <script src="/emulator/js-dos/src/dosbox.js"></script> instantiates it.
| Member | Kind | What it does |
|---|---|---|
Module._emudbg_pause(1|0) | hook | Pause / resume by swapping DOSBox's frame loop (see the debugger note). Our transport's pause / resume. |
Module._emudbg_step() | hook | Queue exactly one CPU instruction; the paused frame loop runs it. Our step / stepInsn. |
Module._emudbg_reg(i) / _emudbg_set_reg(i,v) | hook | Read / write one x86 register by index (added to DOSBox - see below). |
Module._emudbg_read(addr) | hook | One side-effect-free guest byte. Drives the memory + disassembly views. |
Module.canvas | element | The SDL2 render target. The debugger re-homes it between the play view and its Screen window. |
Physical keys work through SDL2's own window key handling once the canvas has focus. The on-screen keyboard dispatches synthetic KeyboardEvents (keyed on event.code) that SDL2 receives the same way.
Debugger integration
This is the Tier-4 point: giving a COMPILED wasm core the same debugger as the pure-JS emulators by rebuilding it with hooks. em-dosbox keeps its whole CPU state inside the wasm sandbox (no views leak out) so we added ONE source file, src/emudbg.cpp, whose functions SAMPLE that state on demand. They are called ~10×/second by the debugger refresh and once per Step; nothing is added to DOSBox's per-instruction hot path (the golden performance rule).
| What the debugger needs | The hook, and the DOSBox internal it reads |
|---|---|
| EAX ECX EDX EBX ESP EBP ESI EDI, EIP | emudbg_reg(i) / emudbg_set_reg(i,v) read/write reg_eax…reg_edi, reg_eip - the cpu_regs struct in include/regs.h. Freely writable. |
| CS DS SS ES FS GS, EFLAGS | emudbg_reg(i) reads SegValue(cs..gs) (the Segs struct) and reg_flags. Shown read-only. |
| Program counter for disasm | emudbg_pc() returns the real-mode linear address (SegValue(cs)<<4)+reg_ip. |
| Physical RAM | emudbg_read(addr) / emudbg_read_block(addr,len) call phys_readb() (from include/mem.h) - the raw MemBase backing store, so a read never touches an I/O device. |
Pause / resume. DOSBox drives emulation through a swappable loop pointer (DOSBOX_SetLoop). emudbg_pause(1) installs our own Emudbg_Pause_Loop; emudbg_pause(0) restores Normal_Loop. The pause loop holds the CPU still and yields a frame to the browser, so while paused the whole page, and the debugger, stays fully responsive.
Single-step, a real one-instruction step. DOSBox's own debugger steps by running the core with a one-instruction budget: CPU_Cycles = 1; (*cpudecoder)();. emudbg_step() queues that, and the paused frame loop executes exactly one instruction, then stops again. So unlike v86 (whose JIT-in-wasm core only exposes a whole time-slice), Step here advances the CPU by a single x86 instruction and you watch EIP and the registers move one op at a time.
Breakpoints / watchpoints are surfaced but best-effort: normal execution runs inside DOSBox's own loop and DOSBox's native execution-breakpoint facility is a separate DEBUG build, so a host-side PC check would mean a per-instruction hook - which the golden rule forbids. They are honestly labelled coarse; true PC breakpoints would need DOSBox's C_DEBUG core wired to the hooks.
The honest headless caveat. DOSBox waits for keyboard input with a host-side idle loop that (by design, so its cooperative Asyncify threading stays valid) does not yield mid-instruction. At an idle DOS prompt that keeps the wasm thread busy. It is irrelevant in a real browser (compositing is off-thread) and it disappears the moment you PAUSE, which is exactly when the debugger is used, because the pause loop yields every frame.
Architecture
DOSBox is a full-system PC emulator: an x86 real-mode CPU (8086/286-class), plus PIC, PIT, DMA, VGA/CGA/EGA, SB16, and its own DOS kernel and BIOS. em-dosbox compiles the whole thing to WebAssembly with Emscripten Asyncify, so DOSBox's blocking, cooperative code runs unchanged in the browser.
dosbox.js- the Emscripten runtime + glue that instantiates the wasm, owns the canvas, and routes SDL2 input/audio.dosbox.wasm- DOSBox itself: the CPU cores, the device models, the DOS/BIOS, and ouremudbg.cpphooks.
The default machine is a real-mode DOS PC, so the shared 16-bit x86 disassembler drives the disasm view correctly. DOS software that flips into 32-bit protected mode will disassemble approximately (the decoder is an 8086/16-bit model).