SearchA-ZW › WebMSX

WebMSX

2016 Freeware · no clear licence Online

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.

Visit the official site ↗

Runs on: Web browser

WebMSX Online Emulator

Play WebMSX using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
MSX BASIC (MSX1)WebMSXPhilips VG-8020greyOpen ⛶
MSX BASIC (MSX2)WebMSXPhilips NMS 8245greyOpen ⛶

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:

MemberKindWhat it does
machine.cpu.busClockPulses(n)methodAdvance the Z80 by n bus clocks. Looping it until PC moves is our single-instruction step.
machine.cpu.saveState()methodSnapshot 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)methodWrite 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)methodRead/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)methodWebMSX's own memory-write hook; we ride it for native write watchpoints.
room.controllersHub.keyDown/keyUp(e)methodWebMSX's char-accurate key mapper. We feed it real events (physical keyboard) and synthetic ones (the on-screen keyboard).
room.mainVideoClock.pause()/go()methodStop / 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:

  • cpu The 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.