Programming

How to embed a Commodore 64 emulator on your website

Put a working Commodore 64 on a web page with a WebAssembly engine: the bare page, loading programs, keys, joysticks, sound and the WordPress plugin.

How to embed a Commodore 64 emulator on your website

Before the how-to, here is the finished thing. This is the emulator described below, running on this site through a small WordPress plugin:

Loading the emulator…

Drop a .prg or .d64 file on the screen to run your own program.

How to use it
  • Click the screen, then type. The C64 starts in BASIC: try PRINT 2+2 and press RETURN.
  • Joysticks: most games read port 2, which is W A S D with the left Shift to fire. Port 1 is the arrow keys with the left Ctrl to fire; choose Both for a two-player game. Loading a program switches port 2 on and Reset switches it off. While a joystick is on, its keys steer rather than type, so set it to Off to type BASIC. On a touch screen a pad for port 2 appears under the screen.
  • Esc is RUN/STOP, Delete is RESTORE (so Esc and Delete together reset a stuck program), Backspace is INST/DEL and Home is CLR/HOME. Symbols sit where the C64 had them rather than where your keyboard prints them, so the on-screen keyboard is the reliable way to reach them; every key on it is labelled as on the machine.
  • Pick a program and press Load and run, or drop your own .prg or .d64 file on the screen. Reset gives you a fresh machine.

Emulation by Thomas Hochgoetz's C64 Emulator, compiled to WebAssembly. Free for non-commercial use.

There is a full page for it with more programs. Now, how it is done.

A note on the old version

The first version of this article used VICE compiled to JavaScript: one x64.js file of several megabytes dropped into a folder. It worked, but only just. It was slow to load, keys were unreliable, and the plugin that grew around it ended up carrying three engines and jQuery. The current version uses one engine, Thomas Hochgoetz’s C64 Emulator, compiled to WebAssembly. The whole thing is about 600 KB, boots to READY in under two seconds and runs at the real machine’s speed. Everything below describes that version.

The simplest possible page

You need two files from the engine, c64_tiny.js and c64_tiny.wasm, side by side in a folder. Then a page like this:

<!doctype html>
<html lang="en-GB">
<body>
    <!-- The id must be "canvas": the engine looks it up by name -->
    <canvas id="canvas" width="368" height="270"></canvas>

    <script>
        // Emscripten reads this object as the engine starts, so it must exist first
        var Module = {
            canvas: document.getElementById('canvas'),
            locateFile: function (file) { return 'engine/' + file; },
            postRun: [function () { console.log('C64 running'); }]
        };
    </script>
    <script src="engine/c64_tiny.js"></script>
</body>
</html>

Three things matter here. The canvas must have the id canvas: the engine attaches its touch handlers by that name and fails with a “null function” error if it is anything else. The Module object must exist before the engine script runs, because Emscripten builds read it as they start. And the engine expects the .wasm file next to its script, which is what locateFile tells it.

Open the page, click the canvas and type. That is a working Commodore 64 with BASIC V2, the original ROMs and the original pace.

What Module does

Module is the contract between the page and the engine. The engine reads a handful of properties from it and calls a few functions back:

  • canvas is where the VIC-II output is drawn, 368 by 270 pixels. CSS scales it up; keep image-rendering: pixelated so the pixels stay crisp.
  • keyboardListeningElement is the element whose key events the engine reads. Point it at a hidden text input inside the screen rather than the whole document, and the rest of your page keeps its keyboard.
  • locateFile returns the URL of the .wasm file.
  • postRun is called once the machine is running. That is the moment to enable the controls.
  • setStatus, printErr and onAbort are how the engine reports progress and failure.

Talking to the running machine

The engine exports a small set of functions, called through Emscripten’s ccall:

// Load a .prg, .d64, .t64, .tap or .crt from its bytes and run it
function loadBytes(name, bytes) {
    Module.ccall('js_removeDevice', 'number', ['number'], [0]);
    return Module.ccall('js_LoadFile', 'number',
        ['string', 'array', 'number', 'number'], [name, bytes, bytes.length, 1]);
}

// Reset, mute, and choose the joysticks
Module.ccall('js_reset', 'number', ['number'], [1]);
Module.ccall('js_setMute', 'number', ['number'], [1]);
Module.ccall('js_selectJoystick', 'number', ['number', 'number'], [0x00, 0x16]);

So loading a program from a URL is a fetch, an ArrayBuffer and one call:

fetch('programs/hello.prg')
    .then(function (r) { return r.arrayBuffer(); })
    .then(function (buf) { loadBytes('hello.prg', new Uint8Array(buf)); });

The last argument to js_LoadFile is the start-up flag: 1 loads and runs, so the reader sees the program rather than a READY prompt. The joystick call takes a key set for each port: 0x16 is W A S D with the left Shift to fire, 0x69 is the arrow keys with the left Ctrl, and 0 is no joystick. Most games read port 2, so the plugin puts W A S D there when a program loads and takes it away again on Reset, because while a joystick is on those keys steer instead of typing.

Keys, and why an on-screen keyboard is needed

The engine maps a PC keyboard by position, the way a German keyboard sits over the C64’s. Letters and digits are fine, and Shift with a digit gives the C64’s shifted character, so Shift and 2 is a double quote. The symbols are where the C64 had them rather than where your keyboard prints them: on a UK keyboard the key right of P types @, the next one *, and the two right of L type : and ;. Esc is RUN/STOP, Delete is RESTORE, Backspace is INST/DEL and Home is CLR/HOME.

Rather than ask readers to remember that, the plugin draws the real C64 keyboard and sends the right code for each key. A key is an ordinary KeyboardEvent dispatched at the hidden input and held for about 90 milliseconds, because the C64 scans its keyboard sixty times a second and misses a press that begins and ends inside one scan:

function keyEvent(type, code, shift) {
    var e = new KeyboardEvent(type, { bubbles: true, shiftKey: !!shift });
    // keyCode is read-only on a constructed event, and it is what the engine reads
    Object.defineProperty(e, 'keyCode', { get: function () { return code; } });
    input.dispatchEvent(e);
}

function tap(code, shift) {
    if (shift) { keyEvent('keydown', 16, true); }
    keyEvent('keydown', code, shift);
    setTimeout(function () {
        keyEvent('keyup', code, shift);
        if (shift) { keyEvent('keyup', 16, false); }
    }, 90);
}

The codes were found by pressing each key and reading the machine’s screen memory back out of Module.HEAPU8, which is a far more reliable test than looking at pixels. Three keys never answered: £, the up arrow and the Commodore key, so they are left off the keyboard rather than typing the wrong thing.

Sound

Browsers refuse to play audio until the reader has clicked something, so the machine starts muted and a Sound button unmutes it. The engine plays through SDL’s audio context; after the click, open or resume that context, then call js_setMute with 0.

What the WordPress plugin adds

All of the above fits in a single HTML file. The plugin wraps it so it can go on any page with the shortcode [c64_emulator], and adds the things a reader expects:

  • A program picker. Five BASIC programs ship with it, written as plain listings and turned into .prg files by a small Python script that tokenises BASIC V2 the way the machine stores it. Admins add more under Tools → C64 programs: .prg, .d64, .t64, .tap, .crt, .p00, .g64 and .x64 files up to 2 MB, each with a title and a one-line note. Uploads live in a folder the web server refuses to serve and are streamed through a REST route instead.
  • A program on load. [c64_emulator program="hello"] loads a program as soon as the machine has booted, which is how the demo at the top of this article works.
  • Controls. The on-screen keyboard, a joystick choice (Off, Port 2, Port 1 or Both), a touch pad on phones, drag and drop of the reader’s own files, Reset, Sound and Full screen.
  • Assets only where needed. The stylesheet and three scripts are enqueued only on a singular page whose content has the shortcode, deferred, in a dependency chain that fixes the order: the host script that defines Module, then the engine, then the script that wires the page.
wp_enqueue_script( 'c64-host', C64_URL . 'js/c64-host.js', array(), C64_VERSION,
    array( 'strategy' => 'defer', 'in_footer' => true ) );
wp_enqueue_script( 'c64-engine', C64_URL . 'assets/engine/c64_tiny.js', array( 'c64-host' ), C64_VERSION,
    array( 'strategy' => 'defer', 'in_footer' => true ) );
wp_enqueue_script( 'c64-front', C64_URL . 'js/c64-front.js', array( 'c64-engine' ), C64_VERSION,
    array( 'strategy' => 'defer', 'in_footer' => true ) );

It is self-contained: no theme functions, no jQuery, nothing from a CDN. It takes its colours from the theme’s custom properties, with fallbacks, so it looks right in light and dark mode on any site.

Things that will catch you out

  • One emulator per page. The engine can only start once, and it insists on the id canvas, so a theme must not use that id elsewhere.
  • The .wasm content type. Browsers take the fast path only if the file is served as application/wasm. Apache and LiteSpeed need AddType application/wasm .wasm in a .htaccess file; nginx needs it in its mime types. Without it the engine still loads, just more slowly.
  • It pauses without focus. The engine stops when the window is not focused, so keys typed into a background tab go nowhere, and a headless test browser has to pretend to be focused.
  • Copyright. The engine is free for non-commercial use only, and the games are still copyright however old they are. The programs on this site were written for it; anything else needs the author’s permission.

The engine is Thomas Hochgoetz’s C64 Emulator, which he compiles to WebAssembly himself. Download it from his site, read the licence, and keep the two engine files together.