WebMSX
WebMSX is an MSX emulator written in JavaScript by Paulo Peccin that runs in any modern web browser and can be installed as an offline web app on desktop and mobile. It emulates MSX, MSX2 and MSX2+ systems with sound and video extensions.
Runs on: Web browser
WebMSX Online Emulator
Play WebMSX using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| MSX BASIC (MSX1) | WebMSX | Philips VG-8020 | grey | Open ⛶ | |
| MSX BASIC (MSX2) | WebMSX | Philips NMS 8245 | grey | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
WebMSX ships as a single bundled script (wmsx.js, the "embedded" build) that carries the free MSX system ROMs, MSX BIOS and MSX BASIC, inside it, so it self-hosts completely and boots offline. We drop a target <div id="wmsx-screen">, load wmsx.js, and set the machine BEFORE it auto-starts:
// runs synchronously right after wmsx.js defines the global WMSX,
// and before DOMContentLoaded triggers WebMSX's own boot
WMSX.MACHINE = 'MSX1'; // boot an MSX1 to MSX BASIC
WMSX.SCREEN_ELEMENT_ID = 'wmsx-screen';
WMSX.AUTO_POWER_ON_DELAY = 0; // power on immediately
WMSX.FAST_BOOT = 1; // 10x speed through the boot
WMSX.SCREEN_RESIZE_DISABLED = true;
We drive the loop. WebMSX runs itself from an internal clock (room.mainVideoClock) that cannot be paused or stepped from outside, which the debugger needs. So once the machine has powered on we pause() that clock and pump frames from a loop WE own, calling WebMSX's own per-frame entry point:
var room = WMSX.room, machine = room.machine;
room.mainVideoClock.pause(); // stop WebMSX's own loop
(function frame(){
room.mainVideoClockPulse(); // one video frame: video + Z80 + INT + audio
if (bps.size && bps.has(pc())) running = false; // frame-granular breakpoint
if (running) requestAnimationFrame(frame);
})();
Reaching into the running machine. WebMSX exposes the live objects the debugger needs off WMSX.room.machine:
| Member | Kind | What it does |
|---|---|---|
machine.cpu.busClockPulses(n) | method | Advance the Z80 by n bus clocks. Looping it until PC moves is our single-instruction step. |
machine.cpu.saveState() | method | Snapshot every Z80 register into a plain object whose keys (PC, SP, A, F, B, C, DE, HL, IX, IY, AF2 …) survive minification, how the debugger reads the registers. |
machine.cpu.loadState(s) | method | Write registers back. Editing one field of a saveState() snapshot and re-loading pokes a single register without disturbing the rest. |
machine.bus.read(a) / write(a,v) | method | Read/write the slot-paged 64K CPU address space. MSX I/O is port-mapped, so a memory read is side-effect-free, safe for an auto-polling hex view. |
machine.bus.setWriteMonitor(fn) | method | WebMSX's own memory-write hook; we ride it for native write watchpoints. |
room.controllersHub.keyDown/keyUp(e) | method | WebMSX's char-accurate key mapper. We feed it real events (physical keyboard) and synthetic ones (the on-screen keyboard). |
room.mainVideoClock.pause()/go() | method | Stop / start WebMSX's own clock. We stop it and drive frames ourselves. |
Debugger integration
Plugging WebMSX into the shared debugger meant wrapping a self-driving emulator without forking it.
1 · A driven loop, because WebMSX owns its clock. WebMSX's Clock runs the machine from an internal requestAnimationFrame that cannot be paused or stepped from outside. After the machine powers on we pause() that clock and pump room.mainVideoClockPulse() from our own loop, so pause, resume and frame-step are ours to control.
2 · Registers through save-state. The Z80 registers are closure-private locals, but cpu.saveState() returns them in an object whose KEYS are fixed strings, so they read correctly even from the minified build. We read via saveState() and write by editing one field of a snapshot and calling loadState(), which never corrupts the rest of the CPU state.
3 · Memory, the Z80 and keyboard. The hex/disassembly views read bus.read() (side-effect-free for MSX memory); the shared z80 disassembler decodes it. Single-step loops cpu.busClockPulses(1) until PC advances. Watchpoints ride WebMSX's own bus.setWriteMonitor(). Keys go through WebMSX's own char-accurate mapper (controllersHub.keyDown/keyUp), so the physical keyboard and the on-screen MSX keyboard both produce the right symbols.
Limitation, breakpoint granularity. On MSX the Z80 is clocked from inside the VDP each frame, and the minified build caches that CPU-clock reference where it cannot be interposed. So there is no seam to check the PC at every instruction while running at speed. Execution breakpoints are therefore honoured at frame granularity during a run (the loop checks the PC once per frame), and exactly, instruction-by-instruction, while single-stepping. Pause, resume, frame-step, instruction-step, register read/write, memory read/write and watchpoints are all exact.
Architecture
WebMSX is a full MSX line emulator by Paulo Peccin (MSX1, MSX2, MSX2+, turbo R; NTSC or PAL). The whole machine hangs off WMSX.room.machine:
cpuThe Zilog Z80 interpreter (an R800 core is added for turbo R).busClockPulses()clocks it;saveState()/loadState()expose the registers.bus- the slot/subslot memory map: BIOS, RAM, cartridges and mappers paged into the 64K address space, plus the port-mapped I/O devices.vdp- the video chip (V9918 on MSX1; V9938/V9958 on MSX2+), which also clocks the CPU and raises the 60/50Hz interrupt each frame; it rasters into a canvas.- PSG (AY-3-8910) and the optional SCC, MSX-MUSIC (OPLL), PCM and OPL4 sound devices.
- system ROMs, the MSX BIOS and MSX BASIC, embedded in the build as base64 and paged into slot 0.
Because the CPU, bus and registers are reachable as ordinary objects, the debugger reads and drives the live machine each refresh without any change to the emulator core.