v86 / src /browser /main.js
peterpeter8585's picture
Upload 553 files
8df6da4 verified
import { V86 } from "./starter.js";
import { LOG_NAMES } from "../const.js";
import { SyncBuffer, SyncFileBuffer } from "../buffer.js";
import { h, pad0, pads, hex_dump, dump_file, download, round_up_to_next_power_of_2 } from "../lib.js";
import { log_data, LOG_LEVEL, set_log_level } from "../log.js";
import * as iso9660 from "../iso9660.js";
const ON_LOCALHOST = !location.hostname.endsWith("copy.sh");
const DEFAULT_NETWORKING_PROXIES = ["wss://relay.widgetry.org/", "ws://localhost:8080/"];
const DEFAULT_MEMORY_SIZE = 128;
const DEFAULT_VGA_MEMORY_SIZE = 8;
const DEFAULT_BOOT_ORDER = 0;
const MAX_ARRAY_BUFFER_SIZE_MB = 2000;
function query_append()
{
const version = $("version");
return version ? "?" + version.textContent : "";
}
function set_title(text)
{
document.title = text + " - v86" + (DEBUG ? " - debug" : "");
const description = document.querySelector("meta[name=description]");
description && (description.content = "Running " + text);
}
function bool_arg(x)
{
return !!x && x !== "0";
}
function format_timestamp(time)
{
if(time < 60)
{
return time + "s";
}
else if(time < 3600)
{
return (time / 60 | 0) + "m " + pad0(time % 60, 2) + "s";
}
else
{
return (time / 3600 | 0) + "h " +
pad0((time / 60 | 0) % 60, 2) + "m " +
pad0(time % 60, 2) + "s";
}
}
function read_file(file)
{
return new Promise((resolve, reject) => {
const fr = new FileReader();
fr.onload = () => resolve(fr.result);
fr.onerror = e => reject(e);
fr.readAsArrayBuffer(file);
});
}
let progress_ticks = 0;
function show_progress(e)
{
const el = $("loading");
el.style.display = "block";
if(e.file_name.endsWith(".wasm"))
{
const parts = e.file_name.split("/");
el.textContent = "Fetching " + parts[parts.length - 1] + " ...";
return;
}
if(e.file_index === e.file_count - 1 && e.loaded >= e.total - 2048)
{
// last file is (almost) loaded
el.textContent = "Done downloading. Starting now ...";
return;
}
let line = "Downloading images ";
if(typeof e.file_index === "number" && e.file_count)
{
line += "[" + (e.file_index + 1) + "/" + e.file_count + "] ";
}
if(e.total && typeof e.loaded === "number")
{
var per100 = Math.floor(e.loaded / e.total * 100);
per100 = Math.min(100, Math.max(0, per100));
var per50 = Math.floor(per100 / 2);
line += per100 + "% [";
line += "#".repeat(per50);
line += " ".repeat(50 - per50) + "]";
}
else
{
line += ".".repeat(progress_ticks++ % 50);
}
el.textContent = line;
}
function $(id)
{
return document.getElementById(id);
}
// These values were previously stored in localStorage
const elements_to_restore = [
"memory_size",
"video_memory_size",
"networking_proxy",
"disable_audio",
"enable_acpi",
"boot_order",
];
for(const item of elements_to_restore)
{
try
{
window.localStorage.removeItem(item);
}
catch(e) {}
}
function onload()
{
if(!window.WebAssembly)
{
alert("Your browser is not supported because it doesn't support WebAssembly");
return;
}
$("start_emulation").onclick = function(e)
{
start_emulation(null, null);
$("start_emulation").blur();
e.preventDefault();
};
if(DEBUG)
{
debug_onload();
}
if(DEBUG && ON_LOCALHOST)
{
// don't use online relay in debug mode
$("relay_url").value = "ws://localhost:8080/";
}
const query_args = new URLSearchParams(location.search);
const host = query_args.get("cdn") || (ON_LOCALHOST ? "images/" : "//i.copy.sh/");
// Abandonware OS images are from https://winworldpc.com/library/operating-systems
const oses = [
{
id: "archlinux",
name: "Arch Linux",
memory_size: 512 * 1024 * 1024,
vga_memory_size: 8 * 1024 * 1024,
state: { url: host + "arch_state-v3.bin.zst" },
filesystem: {
baseurl: host + "arch/",
},
net_device_type: "virtio",
},
{
id: "archlinux-boot",
name: "Arch Linux",
memory_size: 512 * 1024 * 1024,
vga_memory_size: 8 * 1024 * 1024,
filesystem: {
baseurl: host + "arch/",
basefs: { url: host + "fs.json" },
},
cmdline: [
"rw apm=off vga=0x344 video=vesafb:ypan,vremap:8",
"root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose",
"mitigations=off audit=0",
"init_on_free=on",
"tsc=reliable",
"random.trust_cpu=on",
"nowatchdog",
"init=/usr/bin/init-openrc net.ifnames=0 biosdevname=0",
].join(" "),
bzimage_initrd_from_filesystem: true,
net_device_type: "virtio",
},
{
id: "copy/skiffos",
name: "SkiffOS",
cdrom: {
url: host + "skiffos/.iso",
size: 124672000,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
memory_size: 512 * 1024 * 1024,
},
{
id: "serenity",
name: "SerenityOS",
hda: {
url: host + "serenity-v3/.img.zst",
size: 734003200,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
memory_size: 512 * 1024 * 1024,
state: { url: host + "serenity_state-v4.bin.zst" },
homepage: "https://serenityos.org/",
mac_address_translation: true,
},
{
id: "serenity-boot",
name: "SerenityOS",
hda: {
url: host + "serenity-v3/.img.zst",
size: 734003200,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
memory_size: 512 * 1024 * 1024,
homepage: "https://serenityos.org/",
},
{
id: "redox",
name: "Redox",
hda: {
url: host + "redox_demo_i686_2024-09-07_1225_harddrive/.img",
size: 671088640,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
memory_size: 1024 * 1024 * 1024,
state: { url: host + "redox_state-v2.bin.zst" },
homepage: "https://www.redox-os.org/",
acpi: true,
},
{
id: "redox-boot",
name: "Redox",
hda: {
url: host + "redox_demo_i686_2024-09-07_1225_harddrive/.img",
size: 671088640,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
memory_size: 1024 * 1024 * 1024,
homepage: "https://www.redox-os.org/",
acpi: true,
},
{
id: "helenos",
memory_size: 256 * 1024 * 1024,
cdrom: {
//url: host + "HelenOS-0.11.2-ia32.iso",
//size: 25765888,
url: host + "HelenOS-0.14.1-ia32.iso",
size: 25792512,
async: false,
},
name: "HelenOS",
homepage: "http://www.helenos.org/",
},
{
id: "fiwix",
memory_size: 256 * 1024 * 1024,
hda: {
url: host + "FiwixOS-3.4-i386/.img",
size: 1024 * 1024 * 1024,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
name: "FiwixOS",
homepage: "https://www.fiwix.org/",
},
{
id: "haiku",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "haiku-v4/.img",
size: 1 * 1024 * 1024 * 1024,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
state: { url: host + "haiku_state-v4.bin.zst" },
name: "Haiku",
homepage: "https://www.haiku-os.org/",
},
{
id: "haiku-boot",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "haiku-v4/.img",
size: 1 * 1024 * 1024 * 1024,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
name: "Haiku",
homepage: "https://www.haiku-os.org/",
},
{
id: "beos",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "beos5/.img",
size: 536870912,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
name: "BeOS 5",
// NOTE: segfaults if 256k bios is used
},
{
id: "msdos",
hda: {
url: host + "msdos622/.img",
size: 64 * 1024 * 1024,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "MS-DOS 6.22",
},
{
id: "msdos4",
fda: {
url: host + "msdos4.img",
size: 1474560,
},
name: "MS-DOS 4",
},
{
id: "freedos",
fda: {
url: host + "freedos722.img",
size: 737280,
},
name: "FreeDOS",
},
{
id: "freegem",
hda: {
url: host + "freegem/.bin",
size: 209715200,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Freedos with FreeGEM",
},
{
id: "xcom",
fda: {
url: host + "xcom144.img",
size: 1440 * 1024,
},
name: "Freedos with Xcom",
homepage: "http://xcom.infora.hu/index.html",
},
{
id: "psychdos",
hda: {
url: host + "psychdos/.img",
size: 549453824,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "PsychDOS",
homepage: "https://psychoslinux.gitlab.io/DOS/INDEX.HTM",
},
{
id: "86dos",
fda: {
url: host + "pc86dos.img",
size: 163840,
},
name: "86-DOS",
homepage: "https://www.os2museum.com/wp/pc-86-dos/",
},
{
id: "oberon",
hda: {
url: host + "oberon.img",
size: 24 * 1024 * 1024,
async: false,
},
name: "Oberon",
},
{
id: "windows1",
fda: {
url: host + "windows101.img",
size: 1474560,
},
name: "Windows 1.01",
},
{
id: "windows2",
hda: {
url: host + "windows2.img",
size: 4177920,
async: false,
},
name: "Windows 2.03",
},
{
id: "linux26",
cdrom: {
url: host + "linux.iso",
size: 6547456,
async: false,
},
name: "Linux",
},
{
id: "linux3",
cdrom: {
url: host + "linux3.iso",
size: 8638464,
async: false,
},
name: "Linux",
},
{
id: "linux4",
cdrom: {
url: host + "linux4.iso",
size: 7731200,
async: false,
},
name: "Linux",
filesystem: {},
},
{
id: "buildroot",
bzimage: {
url: host + "buildroot-bzimage.bin",
size: 5166352,
async: false,
},
name: "Buildroot Linux",
filesystem: {},
cmdline: "tsc=reliable mitigations=off random.trust_cpu=on",
mouse_disabled_default: true,
},
{
id: "buildroot6",
bzimage: {
url: host + "buildroot-bzimage68.bin",
size: 10068480,
async: false,
},
name: "Buildroot Linux 6.8",
filesystem: {},
cmdline: "tsc=reliable mitigations=off random.trust_cpu=on",
},
{
id: "basiclinux",
hda: {
url: host + "bl3-5.img",
size: 104857600,
async: false,
},
name: "BasicLinux",
},
{
id: "xpud",
cdrom: {
url: host + "xpud-0.9.2.iso",
size: 67108864,
async: false,
},
name: "xPUD",
memory_size: 256 * 1024 * 1024,
},
{
id: "elks",
hda: {
url: host + "elks-hd32-fat.img",
size: 32514048,
async: false,
},
name: "ELKS",
homepage: "https://github.com/ghaerr/elks",
},
{
id: "nodeos",
bzimage: {
url: host + "nodeos-kernel.bin",
size: 14452000,
async: false,
},
name: "NodeOS",
cmdline: "tsc=reliable mitigations=off random.trust_cpu=on",
},
{
id: "dsl",
memory_size: 256 * 1024 * 1024,
cdrom: {
url: host + "dsl-4.11.rc2.iso",
size: 52824064,
async: false,
},
name: "Damn Small Linux",
homepage: "http://www.damnsmalllinux.org/",
},
{
id: "xwoaf",
memory_size: 256 * 1024 * 1024,
cdrom: {
url: host + "xwoaf_rebuild4.iso",
size: 2205696,
async: false,
},
name: "xwoaf",
homepage: "https://pupngo.dk/xwinflpy/xwoaf_rebuild.html",
},
{
id: "minix",
name: "Minix",
memory_size: 256 * 1024 * 1024,
cdrom: {
url: host + "minix-3.3.0/.iso",
size: 605581312,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
homepage: "https://www.minix3.org/",
},
{
id: "unix-v7",
name: "Unix V7",
hda: {
url: host + "unix-v7x86-0.8a/.img",
size: 152764416,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
},
{
id: "kolibrios",
fda: {
url: ON_LOCALHOST ?
host + "kolibri.img" :
"//builds.kolibrios.org/en_US/data/data/kolibri.img",
size: 1474560,
},
name: "KolibriOS",
homepage: "https://kolibrios.org/en/",
},
{
id: "kolibrios-fallback",
fda: {
url: host + "kolibri.img",
size: 1474560,
},
name: "KolibriOS",
},
{
id: "mu",
hda: {
url: host + "mu-shell.img",
size: 10321920,
async: false,
},
memory_size: 256 * 1024 * 1024,
name: "Mu",
homepage: "https://github.com/akkartik/mu",
},
{
id: "openbsd",
hda: {
url: host + "openbsd/.img",
size: 1073741824,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
state: { url: host + "openbsd_state-v2.bin.zst" },
memory_size: 256 * 1024 * 1024,
name: "OpenBSD",
},
{
id: "sortix",
cdrom: {
url: host + "sortix-1.0-i686.iso",
size: 71075840,
async: false,
},
memory_size: 512 * 1024 * 1024,
name: "Sortix",
},
{
id: "openbsd-boot",
hda: {
url: host + "openbsd/.img",
size: 1073741824,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
memory_size: 256 * 1024 * 1024,
name: "OpenBSD",
//acpi: true, // doesn't seem to work
},
{
id: "netbsd",
hda: {
url: host + "netbsd/.img",
size: 511000064,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
memory_size: 256 * 1024 * 1024,
name: "NetBSD",
},
{
id: "crazierl",
multiboot: {
url: host + "crazierl-elf.img",
size: 896592,
async: false,
},
initrd: {
url: host + "crazierl-initrd.img",
size: 18448316,
async: false,
},
acpi: true,
cmdline: "kernel /libexec/ld-elf32.so.1",
memory_size: 128 * 1024 * 1024,
name: "Crazierl",
},
{
id: "solos",
fda: {
url: host + "os8.img",
size: 1474560,
},
name: "Sol OS",
homepage: "http://oby.ro/os/",
},
{
id: "bootchess",
fda: {
url: host + "bootchess.img",
size: 1474560,
},
name: "BootChess",
homepage: "http://www.pouet.net/prod.php?which=64962",
},
{
id: "bootbasic",
fda: {
url: host + "bootbasic.img",
size: 512,
},
name: "bootBASIC",
homepage: "https://github.com/nanochess/bootBASIC",
},
{
id: "bootlogo",
fda: {
url: host + "bootlogo.img",
size: 512,
},
name: "bootLogo",
homepage: "https://github.com/nanochess/bootLogo",
},
{
id: "pillman",
fda: {
url: host + "pillman.img",
size: 512,
},
name: "Pillman",
homepage: "https://github.com/nanochess/Pillman",
},
{
id: "invaders",
fda: {
url: host + "invaders.img",
size: 512,
},
name: "Invaders",
homepage: "https://github.com/nanochess/Invaders",
},
{
id: "sectorlisp",
fda: {
url: host + "sectorlisp-friendly.bin",
size: 512,
},
name: "SectorLISP",
homepage: "https://justine.lol/sectorlisp2/",
},
{
id: "sectorforth",
fda: {
url: host + "sectorforth.img",
size: 512,
},
name: "sectorforth",
homepage: "https://github.com/cesarblum/sectorforth",
},
{
id: "floppybird",
fda: {
url: host + "floppybird.img",
size: 1474560,
},
name: "Floppy Bird",
homepage: "http://mihail.co/floppybird",
},
{
id: "stillalive",
fda: {
url: host + "stillalive-os.img",
size: 368640,
},
name: "Still Alive",
homepage: "https://github.com/maniekx86/stillalive-os",
},
{
id: "hello-v86",
fda: {
url: host + "hello-v86.img",
size: 512,
},
name: "Hello v86",
},
{
id: "tetros",
fda: {
url: host + "tetros.img",
size: 512,
},
name: "TetrOS",
homepage: "https://github.com/daniel-e/tetros",
},
{
id: "dino",
fda: {
url: host + "bootdino.img",
size: 512,
},
name: "dino",
homepage: "https://github.com/franeklubi/dino",
},
{
id: "bootrogue",
fda: {
url: host + "bootrogue.img",
size: 512,
},
name: "bootRogue",
homepage: "https://github.com/nanochess/bootRogue",
},
{
id: "duskos",
hda: {
url: host + "duskos.img",
async: false,
size: 8388608,
},
name: "Dusk OS",
homepage: "http://duskos.org/",
},
{
id: "windows2000",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "windows2k-v2/.img",
size: 2 * 1024 * 1024 * 1024,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Windows 2000",
state: { url: host + "windows2k_state-v4.bin.zst" },
mac_address_translation: true,
},
{
id: "windows2000-boot",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "windows2k-v2/.img",
size: 2 * 1024 * 1024 * 1024,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Windows 2000",
},
{
id: "windows-me",
memory_size: 256 * 1024 * 1024,
hda: {
url: host + "windowsme-v2/.img",
size: 834666496,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
state: { url: host + "windows-me_state-v2.bin.zst" },
name: "Windows ME",
},
{
id: "windowsnt4",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "winnt4_noacpi/.img",
size: 523837440,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Windows NT 4.0",
cpuid_level: 2,
},
{
id: "windowsnt35",
memory_size: 256 * 1024 * 1024,
hda: {
url: host + "windowsnt351/.img",
size: 163577856,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Windows NT 3.51",
},
{
id: "windowsnt3",
memory_size: 256 * 1024 * 1024,
hda: {
url: host + "winnt31/.img",
size: 87 * 1024 * 1024,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Windows NT 3.1",
},
{
id: "windows98",
memory_size: 128 * 1024 * 1024,
hda: {
url: host + "windows98/.img",
size: 300 * 1024 * 1024,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Windows 98",
state: { url: host + "windows98_state-v2.bin.zst" },
mac_address_translation: true,
},
{
id: "windows98-boot",
memory_size: 128 * 1024 * 1024,
hda: {
url: host + "windows98/.img",
size: 300 * 1024 * 1024,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Windows 98",
},
{
id: "windows95",
memory_size: 64 * 1024 * 1024,
// old image:
//memory_size: 32 * 1024 * 1024,
//hda: {
// url: host + "w95/.img",
// size: 242049024,
// async: true,
// fixed_chunk_size: 256 * 1024,
// use_parts: true,
//},
//state: { url: host + "windows95_state.bin.zst" },
hda: {
url: host + "windows95-v2/.img",
size: 471859200,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Windows 95",
},
{
id: "windows95-boot",
memory_size: 64 * 1024 * 1024,
hda: {
url: host + "windows95-v2/.img",
size: 471859200,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
name: "Windows 95",
},
{
id: "windows30",
memory_size: 64 * 1024 * 1024,
cdrom: {
url: host + "Win30.iso",
size: 7774208,
async: false,
},
name: "Windows 3.0",
},
{
id: "windows31",
memory_size: 64 * 1024 * 1024,
hda: {
url: host + "win31.img",
async: false,
size: 34463744,
},
name: "Windows 3.1",
},
{
id: "tilck",
memory_size: 128 * 1024 * 1024,
hda: {
url: host + "tilck.img",
async: false,
size: 37748736,
},
name: "Tilck",
homepage: "https://github.com/vvaltchev/tilck",
},
{
id: "littlekernel",
multiboot: {
url: host + "littlekernel-multiboot.img",
async: false,
size: 969580,
},
name: "Little Kernel",
homepage: "https://github.com/littlekernel/lk",
},
{
id: "sanos",
memory_size: 128 * 1024 * 1024,
hda: {
url: host + "sanos-flp.img",
async: false,
size: 1474560,
},
name: "Sanos",
homepage: "http://www.jbox.dk/sanos/",
},
{
id: "freebsd",
memory_size: 256 * 1024 * 1024,
hda: {
url: host + "freebsd/.img",
size: 2147483648,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
state: { url: host + "freebsd_state-v2.bin.zst" },
name: "FreeBSD",
},
{
id: "freebsd-boot",
memory_size: 256 * 1024 * 1024,
hda: {
url: host + "freebsd/.img",
size: 2147483648,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
name: "FreeBSD",
},
{
id: "reactos",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "reactos-v3/.img",
size: 734003200,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
state: { url: host + "reactos_state-v3.bin.zst" },
mac_address_translation: true,
name: "ReactOS",
acpi: true,
net_device_type: "virtio",
homepage: "https://reactos.org/",
},
{
id: "reactos-boot",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "reactos-v2/.img",
size: 681574400,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
name: "ReactOS",
acpi: true,
homepage: "https://reactos.org/",
},
{
id: "skift",
cdrom: {
url: host + "skift-20200910.iso",
size: 64452608,
async: false,
},
name: "Skift",
homepage: "https://skiftos.org/",
},
{
id: "snowdrop",
fda: {
url: host + "snowdrop.img",
size: 1440 * 1024,
},
name: "Snowdrop",
homepage: "http://www.sebastianmihai.com/snowdrop/",
},
{
id: "openwrt",
hda: {
url: host + "openwrt-18.06.1-x86-legacy-combined-squashfs.img",
size: 19846474,
async: false,
},
name: "OpenWrt",
},
{
id: "qnx",
fda: {
url: host + "qnx-demo-network-4.05.img",
size: 1474560,
},
name: "QNX 4.05",
},
{
id: "9front",
memory_size: 128 * 1024 * 1024,
hda: {
url: host + "9front-10931.386/.iso",
size: 489453568,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
state: { url: host + "9front_state-v3.bin.zst" },
acpi: true,
name: "9front",
homepage: "https://9front.org/",
},
{
id: "9front-boot",
memory_size: 128 * 1024 * 1024,
hda: {
url: host + "9front-10931.386/.iso",
size: 489453568,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
acpi: true,
name: "9front",
homepage: "https://9front.org/",
},
{
id: "9legacy",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "9legacy.img",
async: false,
size: 16000000,
},
name: "9legacy",
homepage: "http://www.9legacy.org/",
//net_device_type: "none",
},
{
id: "mobius",
fda: {
url: host + "mobius-fd-release5.img",
size: 1474560,
},
name: "Mobius",
},
{
id: "android",
memory_size: 512 * 1024 * 1024,
cdrom: {
url: host + "android-x86-1.6-r2/.iso",
size: 54661120,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
name: "Android",
},
{
id: "android4",
memory_size: 512 * 1024 * 1024,
cdrom: {
url: host + "android_x86_nonsse3_4.4r1_20140904/.iso",
size: 247463936,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
name: "Android 4",
},
{
id: "tinycore",
memory_size: 256 * 1024 * 1024,
hda: {
url: host + "TinyCore-11.0.iso",
size: 19922944,
async: false,
},
name: "Tinycore",
homepage: "http://www.tinycorelinux.net/",
},
{
id: "slitaz",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "slitaz-rolling-2024.iso",
size: 56573952,
async: false,
},
name: "SliTaz",
homepage: "https://slitaz.org/",
},
{
id: "freenos",
memory_size: 256 * 1024 * 1024,
cdrom: {
url: host + "FreeNOS-1.0.3.iso",
async: false,
size: 11014144,
},
name: "FreeNOS",
acpi: true,
homepage: "http://www.freenos.org/",
},
{
id: "syllable",
memory_size: 512 * 1024 * 1024,
hda: {
url: host + "syllable-destop-0.6.7/.img",
async: true,
size: 500 * 1024 * 1024,
fixed_chunk_size: 512 * 1024,
use_parts: true,
},
name: "Syllable",
homepage: "http://syllable.metaproject.frl/",
},
{
id: "toaruos",
memory_size: 512 * 1024 * 1024,
cdrom: {
url: host + "toaruos-1.6.1-core.iso",
size: 67567616,
async: false,
},
name: "ToaruOS",
acpi: true,
homepage: "https://toaruos.org/",
},
{
id: "nopeos",
cdrom: {
url: host + "nopeos-0.1.iso",
size: 532480,
async: false,
},
name: "Nope OS",
homepage: "https://github.com/d99kris/nopeos",
},
{
id: "soso",
cdrom: {
url: host + "soso.iso",
size: 22546432,
async: false,
},
name: "Soso",
homepage: "https://github.com/ozkl/soso",
},
{
id: "pcmos",
fda: {
url: host + "PCMOS386-9-user-patched.img",
size: 1440 * 1024,
},
name: "PC-MOS/386",
homepage: "https://github.com/roelandjansen/pcmos386v501",
},
{
id: "jx",
fda: {
url: host + "jx-demo.img",
size: 1440 * 1024,
},
name: "JX",
homepage: "https://www4.cs.fau.de/Projects/JX/index.html",
},
{
id: "house",
fda: {
url: host + "hOp-0.8.img",
size: 1440 * 1024,
},
name: "House",
homepage: "https://programatica.cs.pdx.edu/House/",
},
{
id: "bleskos",
name: "BleskOS",
cdrom: {
url: host + "bleskos_2024u32.iso",
size: 1835008,
async: false,
},
homepage: "https://github.com/VendelinSlezak/BleskOS",
},
{
id: "boneos",
name: "BoneOS",
cdrom: {
url: host + "BoneOS.iso",
size: 11429888,
async: false,
},
homepage: "https://amanuel.io/projects/BoneOS/",
},
{
id: "mikeos",
name: "MikeOS",
cdrom: {
url: host + "mikeos.iso",
size: 3311616,
async: false,
},
homepage: "https://mikeos.sourceforge.net/",
},
{
id: "bluejay",
name: "Blue Jay",
fda: {
url: host + "bj050.img",
size: 1474560,
},
homepage: "https://archiveos.org/blue-jay/",
},
{
id: "t3xforth",
name: "T3XFORTH",
fda: {
url: host + "t3xforth.img",
size: 1474560,
},
homepage: "https://t3x.org/t3xforth/",
},
{
id: "nanoshell",
name: "NanoShell",
cdrom: {
url: host + "nanoshell.iso",
size: 6785024,
async: false,
},
homepage: "https://github.com/iProgramMC/NanoShellOS",
},
{
id: "catk",
name: "CatK",
cdrom: {
url: host + "catkernel.iso",
size: 11968512,
async: false,
},
homepage: "https://catk.neocities.org/",
},
{
id: "mcp",
name: "M/CP",
fda: {
url: host + "mcp2.img",
size: 512,
},
homepage: "https://github.com/ybuzoku/MCP",
},
{
id: "ibm-exploring",
name: "Exploring The IBM Personal Computer",
fda: {
url: host + "ibm-exploring.img",
size: 368640,
},
},
{
id: "leetos",
name: "lEEt/OS",
fda: {
url: host + "leetos.img",
size: 1474560,
},
homepage: "http://sininenankka.dy.fi/leetos/index.php",
},
{
id: "newos",
name: "NewOS",
fda: {
url: host + "newos-flp.img",
size: 1474560,
async: false,
},
homepage: "https://newos.org/",
},
{
id: "aros-broadway",
name: "AROS Broadway",
memory_size: 512 * 1024 * 1024,
cdrom: {
url: host + "broadway10/.iso",
size: 742051840,
async: true,
fixed_chunk_size: 512 * 1024,
use_parts: true,
},
homepage: "https://web.archive.org/web/20231109224346/http://www.aros-broadway.de/",
},
{
id: "icaros",
name: "Icaros Desktop",
memory_size: 512 * 1024 * 1024,
cdrom: {
url: host + "icaros-pc-i386-2.3/.iso",
size: 726511616,
async: true,
// NOTE: needs 136MB/287 requests to boot, maybe state image or zst parts?
fixed_chunk_size: 512 * 1024,
use_parts: true,
},
homepage: "http://vmwaros.blogspot.com/",
},
{
id: "tinyaros",
name: "Tiny Aros",
memory_size: 512 * 1024 * 1024,
cdrom: {
url: host + "tinyaros-pc-i386/.iso",
size: 111175680,
async: true,
fixed_chunk_size: 512 * 1024,
use_parts: true,
},
homepage: "https://www.tinyaros.it/",
},
{
id: "dancy",
name: "Dancy",
cdrom: {
url: host + "dancy.iso",
size: 10485760,
async: false,
},
homepage: "https://github.com/Tiihala/Dancy",
},
{
id: "curios",
name: "CuriOS",
hda: {
url: host + "curios.img",
size: 83886080,
async: false,
},
homepage: "https://github.com/h5n1xp/CuriOS",
},
{
id: "os64",
name: "OS64",
cdrom: {
url: host + "os64boot.iso",
size: 5580800,
async: false,
},
homepage: "https://os64.blogspot.com/",
},
{
id: "ipxe",
name: "iPXE",
cdrom: {
url: host + "ipxe.iso",
size: 4194304,
async: false,
},
homepage: "https://ipxe.org/",
},
{
id: "netboot.xyz",
name: "netboot.xyz",
cdrom: {
url: host + "netboot.xyz.iso",
size: 2398208,
async: false,
},
homepage: "https://netboot.xyz/",
net_device_type: "virtio",
},
{
id: "squeaknos",
name: "SqueakNOS",
cdrom: {
url: host + "SqueakNOS.iso",
size: 61171712,
async: false,
},
memory_size: 512 * 1024 * 1024,
homepage: "https://squeaknos.blogspot.com/"
},
{
id: "chokanji4",
name: "Chokanji 4",
hda: {
url: host + "chokanji4/.img.zst",
size: 10737418240,
async: true,
fixed_chunk_size: 256 * 1024,
use_parts: true,
},
memory_size: 512 * 1024 * 1024,
homepage: "https://archive.org/details/brightv4000"
},
{
id: "archhurd",
name: "Arch Hurd",
hda: {
url: host + "archhurd-2018.09.28/.img.zst",
size: 4294967296,
async: true,
fixed_chunk_size: 1024 * 1024,
use_parts: true,
},
memory_size: 512 * 1024 * 1024,
homepage: "https://archhurd.org/",
},
];
if(DEBUG)
{
// see tests/kvm-unit-tests/x86/
const tests = [
"realmode",
// All tests below require an APIC
"cmpxchg8b",
"port80",
"setjmp",
"sieve",
"hypercall", // crashes
"init", // stops execution
"msr", // TODO: Expects 64 bit msrs
"smap", // test stops, SMAP not enabled
"tsc_adjust", // TODO: IA32_TSC_ADJUST
"tsc", // TODO: rdtscp
"rmap_chain", // crashes
"memory", // missing mfence (uninteresting)
"taskswitch", // TODO: Jump
"taskswitch2", // TODO: Call TSS
"eventinj", // Missing #nt
"ioapic",
"apic",
];
for(const test of tests)
{
oses.push({
name: "Test case: " + test,
id: "test-" + test,
memory_size: 128 * 1024 * 1024,
multiboot: { url: "tests/kvm-unit-tests/x86/" + test + ".flat" }
});
}
}
const profile = query_args.get("profile");
if(!profile && !DEBUG)
{
const link = document.createElement("link");
link.rel = "prefetch";
link.href = "build/v86.wasm" + query_append();
document.head.appendChild(link);
}
const link = document.createElement("link");
link.rel = "prefetch";
link.href = "build/xterm.js";
document.head.appendChild(link);
for(const os of oses)
{
if(profile === os.id)
{
start_emulation(os, query_args);
return;
}
const element = $("start_" + os.id);
if(element)
{
element.onclick = e =>
{
if(!e.ctrlKey)
{
e.preventDefault();
element.blur();
start_emulation(os, null);
}
};
}
}
if(profile === "custom")
{
// TODO: if one of the file form fields has a value (firefox), start here?
if(query_args.has("hda.url") || query_args.has("cdrom.url") || query_args.has("fda.url"))
{
start_emulation(null, query_args);
return;
}
}
else if(/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_]+$/g.test(profile))
{
// experimental: server that allows user-uploaded images
const base = "https://v86-user-images.b-cdn.net/" + profile;
fetch(base + "/profile.json")
.catch(e => alert("Profile not found: " + profile))
.then(response => response.json())
.then(p => {
function handle_image(o)
{
return o && { url: base + "/" + o["url"], async: o["async"], size: o["size"] };
}
const profile = {
id: p["id"],
name: p["name"],
memory_size: p["memory_size"],
vga_memory_size: p["vga_memory_size"],
acpi: p["acpi"],
boot_order: p["boot_order"],
hda: handle_image(p["hda"]),
cdrom: handle_image(p["cdrom"]),
fda: handle_image(p["fda"]),
multiboot: handle_image(p["multiboot"]),
bzimage: handle_image(p["bzimage"]),
initrd: handle_image(p["initrd"]),
};
start_emulation(profile, query_args);
});
}
if(query_args.has("m")) $("memory_size").value = query_args.get("m");
if(query_args.has("vram")) $("vga_memory_size").value = query_args.get("vram");
if(query_args.has("relay_url")) $("relay_url").value = query_args.get("relay_url");
if(query_args.has("mute")) $("disable_audio").checked = bool_arg(query_args.get("mute"));
if(query_args.has("acpi")) $("acpi").checked = bool_arg(query_args.get("acpi"));
if(query_args.has("boot_order")) $("boot_order").value = query_args.get("boot_order");
for(const dev of ["fda", "fdb"])
{
const toggle = $(dev + "_toggle_empty_disk");
if(!toggle) continue;
toggle.onclick = function(e)
{
e.preventDefault();
const select = document.createElement("select");
select.id = dev + "_empty_size";
for(const n_sect of [320, 360, 400, 640, 720, 800, 1440, 2400, 2880, 3444, 5760, 7680])
{
const n_bytes = n_sect * 512, kB = 1024, MB = kB * 1000;
const option = document.createElement("option");
if(n_bytes < MB)
{
option.textContent = (n_bytes / kB) + " kB";
}
else
{
option.textContent = (n_bytes / MB).toFixed(2) + " MB";
}
if(n_sect === 2880)
{
option.selected = true;
}
option.value = n_bytes;
select.appendChild(option);
}
// TODO (when closure compiler supports it): parent.parentNode.replaceChildren(...);
const parent = toggle.parentNode;
parent.innerHTML = "";
parent.append("Empty disk of ", select);
};
}
for(const dev of ["hda", "hdb"])
{
const toggle = $(dev + "_toggle_empty_disk");
if(!toggle) continue;
toggle.onclick = function(e)
{
e.preventDefault();
const input = document.createElement("input");
input.id = dev + "_empty_size";
input.type = "number";
input.min = "0";
input.max = String(MAX_ARRAY_BUFFER_SIZE_MB);
input.step = "100";
input.value = "100";
// TODO (when closure compiler supports it): parent.parentNode.replaceChildren(...);
const parent = toggle.parentNode;
parent.innerHTML = "";
parent.append("Empty disk of ", input, " MB");
};
}
const os_info = Array.from(document.querySelectorAll("#oses tbody tr")).map(element =>
{
const [_, size_raw, unit] = element.children[1].textContent.match(/([\d\.]+)\+? (\w+)/);
let size = +size_raw;
if(unit === "MB") size *= 1024 * 1024;
else if(unit === "KB") size *= 1024;
return {
element,
size,
graphical: element.children[2].firstChild.className === "gui_icon",
family: element.children[3].textContent.replace(/-like/, ""),
arch: element.children[4].textContent,
status: element.children[5].textContent,
source: element.children[6].textContent,
languages: new Set(element.children[7].textContent.split(", ")),
medium: element.children[8].textContent,
};
});
const known_filter = [
[ // Family:
{ id: "linux", condition: os => os.family === "Linux" },
{ id: "bsd", condition: os => os.family === "BSD" },
{ id: "windows", condition: os => os.family === "Windows" },
{ id: "unix", condition: os => os.family === "Unix" },
{ id: "dos", condition: os => os.family === "DOS" },
{ id: "custom", condition: os => os.family === "Custom" },
],
[ // UI:
{ id: "graphical", condition: os => os.graphical },
{ id: "text", condition: os => !os.graphical },
],
[ // Medium:
{ id: "floppy", condition: os => os.medium === "Floppy" },
{ id: "cd", condition: os => os.medium === "CD" },
{ id: "hd", condition: os => os.medium === "HD" },
],
[ // Size:
{ id: "bootsector", condition: os => os.size <= 512 },
{ id: "lt5mb", condition: os => os.size <= 5 * 1024 * 1024 },
{ id: "gt5mb", condition: os => os.size > 5 * 1024 * 1024 },
],
[ // Status:
{ id: "modern", condition: os => os.status === "Modern" },
{ id: "historic", condition: os => os.status === "Historic" },
],
[ // License:
{ id: "opensource", condition: os => os.source === "Open-source" },
{ id: "proprietary", condition: os => os.source === "Proprietary" },
],
[ // Arch:
{ id: "16bit", condition: os => os.arch === "16-bit" },
{ id: "32bit", condition: os => os.arch === "32-bit" },
],
[ // Lang:
{ id: "asm", condition: os => os.languages.has("ASM") },
{ id: "c", condition: os => os.languages.has("C") },
{ id: "cpp", condition: os => os.languages.has("C++") },
{ id: "other_lang", condition: os => ["ASM", "C", "C++"].every(lang => !os.languages.has(lang)) },
],
];
const defined_filter = [];
for(const known_category of known_filter)
{
const category = known_category.filter(filter => {
const element = document.getElementById(`filter_${filter.id}`);
if(element)
{
element.onchange = update_filters;
filter.element = element;
}
return element;
});
if(category.length)
{
defined_filter.push(category);
}
}
function update_filters()
{
const conjunction = [];
for(const category of defined_filter)
{
const disjunction = category.filter(filter => filter.element.checked);
if(disjunction.length)
{
conjunction.push(disjunction);
}
}
for(const os of os_info)
{
os.element.style.display = conjunction.every(disjunction => disjunction.some(filter => filter.condition(os))) ? "" : "none";
}
}
function set_proxy_value(id, value)
{
const elem = $(id);
if(elem)
{
elem.onclick = () => $("relay_url").value = value;
}
}
set_proxy_value("network_none", "");
set_proxy_value("network_inbrowser", "inbrowser");
set_proxy_value("network_fetch", "fetch");
set_proxy_value("network_relay", "wss://relay.widgetry.org/");
set_proxy_value("network_wisp", "wisps://wisp.mercurywork.shop/v86/");
}
function debug_onload()
{
// called on window.onload, in debug mode
const log_levels = $("log_levels");
if(!log_levels)
{
return;
}
for(let i = 0; i < LOG_NAMES.length; i++)
{
const mask = LOG_NAMES[i][0];
if(mask === 1)
continue;
const name = LOG_NAMES[i][1].toLowerCase();
const input = document.createElement("input");
const label = document.createElement("label");
input.type = "checkbox";
label.htmlFor = input.id = "log_" + name;
if(LOG_LEVEL & mask)
{
input.checked = true;
}
input.mask = mask;
label.append(input, pads(name, 4) + " ");
log_levels.appendChild(label);
if(i === Math.floor(LOG_NAMES.length / 2))
{
log_levels.append("\n");
}
}
log_levels.onchange = function(e)
{
const target = e.target;
const mask = target.mask;
if(target.checked)
{
set_log_level(LOG_LEVEL | mask);
}
else
{
set_log_level(LOG_LEVEL & ~mask);
}
target.blur();
};
}
window.addEventListener("load", onload, false);
// old webkit fires popstate on every load, fuck webkit
// https://code.google.com/p/chromium/issues/detail?id=63040
window.addEventListener("load", function()
{
setTimeout(function()
{
window.addEventListener("popstate", onpopstate);
}, 0);
});
// works in firefox and chromium
if(document.readyState === "complete")
{
onload();
}
// we can get here in various ways:
// - the user clicked on the "start emulation" button
// - the user clicked on a profile
// - the ?profile= query parameter specified a valid profile
// - the ?profile= query parameter was set to "custom" and at least one disk image was given
function start_emulation(profile, query_args)
{
$("boot_options").style.display = "none";
const new_query_args = new Map();
new_query_args.set("profile", profile?.id || "custom");
const settings = {};
if(profile)
{
if(profile.state)
{
$("reset").style.display = "none";
}
set_title(profile.name);
settings.initial_state = profile.state;
settings.filesystem = profile.filesystem;
settings.fda = profile.fda;
settings.fdb = profile.fdb;
settings.cdrom = profile.cdrom;
settings.hda = profile.hda;
settings.hdb = profile.hdb;
settings.multiboot = profile.multiboot;
settings.bzimage = profile.bzimage;
settings.initrd = profile.initrd;
settings.cmdline = profile.cmdline;
settings.bzimage_initrd_from_filesystem = profile.bzimage_initrd_from_filesystem;
settings.mac_address_translation = profile.mac_address_translation;
settings.cpuid_level = profile.cpuid_level;
settings.acpi = profile.acpi;
settings.memory_size = profile.memory_size;
settings.vga_memory_size = profile.vga_memory_size;
settings.boot_order = profile.boot_order;
settings.net_device_type = profile.net_device_type;
if(!DEBUG && profile.homepage)
{
$("description").style.display = "block";
const link = document.createElement("a");
link.href = profile.homepage;
link.textContent = profile.name;
link.target = "_blank";
$("description").append(document.createTextNode("Running "), link);
}
}
if(query_args)
{
// ignore certain settings when using a state image
if(!settings.initial_state)
{
let chunk_size = parseInt(query_args.get("chunk_size"), 10);
if(chunk_size >= 0)
{
chunk_size = Math.min(4 * 1024 * 1024, Math.max(512, chunk_size));
chunk_size = round_up_to_next_power_of_2(chunk_size);
}
else
{
chunk_size = 256 * 1024;
}
if(query_args.has("hda.url"))
{
settings.hda = {
size: parseInt(query_args.get("hda.size"), 10) || undefined,
// TODO: synchronous if small?
url: query_args.get("hda.url"),
fixed_chunk_size: chunk_size,
async: true,
};
}
else if(query_args.has("hda.empty"))
{
const empty_size = parseInt(query_args.get("hda.empty"), 10);
if(empty_size > 0)
{
settings.hda = { buffer: new ArrayBuffer(empty_size) };
}
}
if(query_args.has("hdb.url"))
{
settings.hdb = {
size: parseInt(query_args.get("hdb.size"), 10) || undefined,
// TODO: synchronous if small?
url: query_args.get("hdb.url"),
fixed_chunk_size: chunk_size,
async: true,
};
}
else if(query_args.has("hdb.empty"))
{
const empty_size = parseInt(query_args.get("hdb.empty"), 10);
if(empty_size > 0)
{
settings.hdb = { buffer: new ArrayBuffer(empty_size) };
}
}
if(query_args.has("cdrom.url"))
{
settings.cdrom = {
size: parseInt(query_args.get("cdrom.size"), 10) || undefined,
url: query_args.get("cdrom.url"),
fixed_chunk_size: chunk_size,
async: true,
};
}
if(query_args.has("fda.url"))
{
settings.fda = {
size: parseInt(query_args.get("fda.size"), 10) || undefined,
url: query_args.get("fda.url"),
async: false,
};
}
const m = parseInt(query_args.get("m"), 10);
if(m > 0)
{
settings.memory_size = Math.max(16, m) * 1024 * 1024;
}
const vram = parseInt(query_args.get("vram"), 10);
if(vram > 0)
{
settings.vga_memory_size = vram * 1024 * 1024;
}
settings.acpi = query_args.has("acpi") ? bool_arg(query_args.get("acpi")) : settings.acpi;
settings.use_bochs_bios = query_args.get("bios") === "bochs";
settings.net_device_type = query_args.get("net_device_type") || settings.net_device_type;
}
settings.relay_url = query_args.get("relay_url");
settings.disable_jit = bool_arg(query_args.get("disable_jit"));
settings.disable_audio = bool_arg(query_args.get("mute"));
}
if(!settings.relay_url)
{
settings.relay_url = $("relay_url").value;
if(!DEFAULT_NETWORKING_PROXIES.includes(settings.relay_url)) new_query_args.set("relay_url", settings.relay_url);
}
if(settings.relay_url.startsWith("fetch:"))
{
settings.cors_proxy = settings.relay_url.slice(6);
settings.relay_url = "fetch";
}
settings.disable_audio = $("disable_audio").checked || settings.disable_audio;
if(settings.disable_audio) new_query_args.set("mute", "1");
// some settings cannot be overridden when a state image is used
if(!settings.initial_state)
{
const bios = $("bios").files[0];
if(bios)
{
settings.bios = { buffer: bios };
}
const vga_bios = $("vga_bios").files[0];
if(vga_bios)
{
settings.vga_bios = { buffer: vga_bios };
}
const fda = $("fda_image")?.files[0];
if(fda)
{
settings.fda = { buffer: fda };
}
const fda_empty_size = +$("fda_empty_size")?.value;
if(fda_empty_size)
{
settings.fda = { buffer: new ArrayBuffer(fda_empty_size) };
}
const fdb = $("fdb_image")?.files[0];
if(fdb)
{
settings.fdb = { buffer: fdb };
}
const fdb_empty_size = +$("fdb_empty_size")?.value;
if(fdb_empty_size)
{
settings.fdb = { buffer: new ArrayBuffer(fdb_empty_size) };
}
const cdrom = $("cdrom_image").files[0];
if(cdrom)
{
settings.cdrom = { buffer: cdrom };
}
const hda = $("hda_image")?.files[0];
if(hda)
{
settings.hda = { buffer: hda };
}
const hda_empty_size = +$("hda_empty_size")?.value;
if(hda_empty_size)
{
const size = Math.min(1, Math.max(MAX_ARRAY_BUFFER_SIZE_MB, hda_empty_size)) * 1024 * 1024;
settings.hda = { buffer: new ArrayBuffer(size) };
new_query_args.set("hda.empty", String(size));
}
const hdb = $("hdb_image")?.files[0];
if(hdb)
{
settings.hdb = { buffer: hdb };
}
const hdb_empty_size = +$("hdb_empty_size")?.value;
if(hdb_empty_size)
{
const size = Math.min(1, Math.max(MAX_ARRAY_BUFFER_SIZE_MB, hdb_empty_size)) * 1024 * 1024;
settings.hdb = { buffer: new ArrayBuffer(hdb_empty_size) };
new_query_args.set("hdb.empty", String(size));
}
const multiboot = $("multiboot_image")?.files[0];
if(multiboot)
{
settings.multiboot = { buffer: multiboot };
}
const bzimage = $("bzimage").files[0];
if(bzimage)
{
settings.bzimage = { buffer: bzimage };
}
const initrd = $("initrd").files[0];
if(initrd)
{
settings.initrd = { buffer: initrd };
}
const title = multiboot?.name || hda?.name || cdrom?.name || hdb?.name || fda?.name || bios?.name;
if(title)
{
set_title(title);
}
const MB = 1024 * 1024;
const memory_size = parseInt($("memory_size").value, 10) || DEFAULT_MEMORY_SIZE;
if(!settings.memory_size || memory_size !== DEFAULT_MEMORY_SIZE)
{
settings.memory_size = memory_size * MB;
}
if(memory_size !== DEFAULT_MEMORY_SIZE) new_query_args.set("m", String(memory_size));
const vga_memory_size = parseInt($("vga_memory_size").value, 10) || DEFAULT_VGA_MEMORY_SIZE;
if(!settings.vga_memory_size || vga_memory_size !== DEFAULT_VGA_MEMORY_SIZE)
{
settings.vga_memory_size = vga_memory_size * MB;
}
if(vga_memory_size !== DEFAULT_VGA_MEMORY_SIZE) new_query_args.set("vram", String(vga_memory_size));
const boot_order = parseInt($("boot_order").value, 16) || DEFAULT_BOOT_ORDER;
if(!settings.boot_order || boot_order !== DEFAULT_BOOT_ORDER)
{
settings.boot_order = boot_order;
}
if(settings.boot_order !== DEFAULT_BOOT_ORDER) new_query_args.set("boot_order", settings.boot_order.toString(16));
if(settings.acpi === undefined)
{
settings.acpi = $("acpi").checked;
if(settings.acpi) new_query_args.set("acpi", "1");
}
const BIOSPATH = "bios/";
if(!settings.bios)
{
settings.bios = { url: BIOSPATH + (DEBUG ? "seabios-debug.bin" : "seabios.bin") };
}
if(!settings.vga_bios)
{
settings.vga_bios = { url: BIOSPATH + (DEBUG ? "vgabios-debug.bin" : "vgabios.bin") };
}
if(settings.use_bochs_bios)
{
settings.bios = { url: BIOSPATH + "bochs-bios.bin" };
settings.vga_bios = { url: BIOSPATH + "bochs-vgabios.bin" };
}
}
if(!query_args)
{
push_state(new_query_args);
}
const emulator = new V86({
wasm_path: "build/" + (DEBUG ? "v86-debug.wasm" : "v86.wasm") + query_append(),
screen: {
container: $("screen_container"),
use_graphical_text: false,
},
net_device: {
type: settings.net_device_type || "ne2k",
relay_url: settings.relay_url,
cors_proxy: settings.cors_proxy
},
autostart: true,
memory_size: settings.memory_size,
vga_memory_size: settings.vga_memory_size,
boot_order: settings.boot_order,
bios: settings.bios,
vga_bios: settings.vga_bios,
fda: settings.fda,
fdb: settings.fdb,
hda: settings.hda,
hdb: settings.hdb,
cdrom: settings.cdrom,
multiboot: settings.multiboot,
bzimage: settings.bzimage,
initrd: settings.initrd,
cmdline: settings.cmdline,
bzimage_initrd_from_filesystem: settings.bzimage_initrd_from_filesystem,
acpi: settings.acpi,
disable_jit: settings.disable_jit,
initial_state: settings.initial_state,
filesystem: settings.filesystem || {},
disable_speaker: settings.disable_audio,
mac_address_translation: settings.mac_address_translation,
cpuid_level: settings.cpuid_level,
});
if(DEBUG) window.emulator = emulator;
emulator.add_listener("emulator-ready", function()
{
if(DEBUG)
{
debug_start(emulator);
}
if(emulator.v86.cpu.wm.exports["profiler_is_enabled"]())
{
const CLEAR_STATS = false;
const panel = document.createElement("pre");
document.body.appendChild(panel);
setInterval(function()
{
if(!emulator.is_running())
{
return;
}
panel.textContent = emulator.get_instruction_stats();
CLEAR_STATS && emulator.v86.cpu.clear_opstats();
}, CLEAR_STATS ? 5000 : 1000);
}
if(["dsl", "helenos", "android", "android4", "redox", "beos", "9legacy"].includes(profile?.id))
{
setTimeout(() => {
// hack: Start automatically
emulator.keyboard_send_text(profile.id === "9legacy" ? "1\n" : "\n");
}, 3000);
}
init_ui(profile, settings, emulator);
if(query_args?.has("c"))
{
setTimeout(function()
{
emulator.keyboard_send_text(query_args.get("c") + "\n");
}, 25);
}
if(query_args?.has("s"))
{
setTimeout(function()
{
emulator.serial0_send(query_args.get("s") + "\n");
}, 25);
}
if(query_args?.has("theatre") && bool_arg(query_args?.get("theatre")))
{
$("toggle_theatre").click();
}
});
emulator.add_listener("emulator-loaded", function()
{
if(!emulator.v86.cpu.devices.cdrom)
{
$("change_cdrom_image").style.display = "none";
}
});
emulator.add_listener("download-progress", function(e)
{
show_progress(e);
});
emulator.add_listener("download-error", function(e)
{
const el = $("loading");
el.style.display = "block";
el.textContent = `Loading ${e.file_name} failed. Check your connection and reload the page to try again.`;
});
}
/**
* @param {Object} settings
* @param {V86} emulator
*/
function init_ui(profile, settings, emulator)
{
$("loading").style.display = "none";
$("runtime_options").style.display = "block";
$("runtime_infos").style.display = "block";
$("screen_container").style.display = "block";
var filesystem_is_enabled = false;
if(settings.filesystem)
{
filesystem_is_enabled = true;
init_filesystem_panel(emulator);
}
else
{
emulator.add_listener("9p-attach", function()
{
filesystem_is_enabled = true;
init_filesystem_panel(emulator);
});
}
$("run").onclick = function()
{
if(emulator.is_running())
{
$("run").value = "Run";
emulator.stop();
}
else
{
$("run").value = "Pause";
emulator.run();
}
$("run").blur();
};
$("exit").onclick = function()
{
emulator.destroy();
const url = new URL(location.href);
url.searchParams.delete("profile");
location.href = url.pathname + url.search;
};
$("lock_mouse").onclick = function()
{
if(!mouse_is_enabled)
{
$("toggle_mouse").onclick();
}
emulator.lock_mouse();
$("lock_mouse").blur();
};
var mouse_is_enabled = true;
$("toggle_mouse").onclick = function()
{
mouse_is_enabled = !mouse_is_enabled;
emulator.mouse_set_enabled(mouse_is_enabled);
$("toggle_mouse").value = (mouse_is_enabled ? "Dis" : "En") + "able mouse";
$("toggle_mouse").blur();
};
if(profile?.mouse_disabled_default)
{
$("toggle_mouse").onclick();
}
var theatre_mode = false;
var theatre_ui = true;
var theatre_zoom_to_fit = false;
function zoom_to_fit()
{
// reset size
emulator.screen_set_scale(1, 1);
const emulator_screen = $("screen_container").getBoundingClientRect();
const emulator_screen_width = emulator_screen.width;
const emulator_screen_height = emulator_screen.height;
const viewport_screen_width = window.innerWidth;
const viewport_screen_height = window.innerHeight;
const n = Math.min(viewport_screen_width / emulator_screen_width, viewport_screen_height / emulator_screen_height);
emulator.screen_set_scale(n, n);
}
/**
* @param {boolean} enabled
*/
function enable_theatre_ui(enabled)
{
theatre_ui = enabled;
$("runtime_options").style.display = theatre_ui ? "block" : "none";
$("runtime_infos").style.display = theatre_ui ? "block" : "none";
$("filesystem_panel").style.display = (filesystem_is_enabled && theatre_ui) ? "block" : "none";
$("toggle_ui").value = (theatre_ui ? "Hide" : "Show") + " UI";
}
/**
* @param {boolean} enabled
*/
function enable_zoom_to_fit(enabled)
{
theatre_zoom_to_fit = enabled;
$("scale").disabled = theatre_zoom_to_fit;
if(theatre_zoom_to_fit)
{
window.addEventListener("resize", zoom_to_fit, true);
emulator.add_listener("screen-set-size", zoom_to_fit);
zoom_to_fit();
}
else
{
window.removeEventListener("resize", zoom_to_fit, true);
emulator.remove_listener("screen-set-size", zoom_to_fit);
const n = parseFloat($("scale").value) || 1;
emulator.screen_set_scale(n, n);
}
$("toggle_zoom_to_fit").value = (theatre_zoom_to_fit ? "Dis" : "En") + "able zoom to fit";
}
/**
* @param {boolean} enabled
*/
function enable_theatre_mode(enabled)
{
theatre_mode = enabled;
if(!theatre_ui)
{
enable_theatre_ui(true);
}
if(!theatre_mode && theatre_zoom_to_fit)
{
enable_zoom_to_fit(false);
}
for(const el of ["screen_container", "runtime_options", "runtime_infos", "filesystem_panel"])
{
$(el).classList.toggle("theatre_" + el);
}
$("theatre_background").style.display = theatre_mode ? "block" : "none";
$("toggle_zoom_to_fit").style.display = theatre_mode ? "inline" : "none";
$("toggle_ui").style.display = theatre_mode ? "block" : "none";
// hide scrolling
document.body.style.overflow = theatre_mode ? "hidden" : "visible";
$("toggle_theatre").value = (theatre_mode ? "Dis" : "En") + "able theatre mode";
}
$("toggle_ui").onclick = function()
{
enable_theatre_ui(!theatre_ui);
$("toggle_ui").blur();
};
$("toggle_theatre").onclick = function()
{
enable_theatre_mode(!theatre_mode);
$("toggle_theatre").blur();
};
$("toggle_zoom_to_fit").onclick = function()
{
enable_zoom_to_fit(!theatre_zoom_to_fit);
$("toggle_zoom_to_fit").blur();
};
var last_tick = 0;
var running_time = 0;
var last_instr_counter = 0;
var interval = null;
var os_uses_mouse = false;
var total_instructions = 0;
function update_info()
{
var now = Date.now();
var instruction_counter = emulator.get_instruction_counter();
if(instruction_counter < last_instr_counter)
{
// 32-bit wrap-around
last_instr_counter -= 0x100000000;
}
var last_ips = instruction_counter - last_instr_counter;
last_instr_counter = instruction_counter;
total_instructions += last_ips;
var delta_time = now - last_tick;
if(delta_time)
{
running_time += delta_time;
last_tick = now;
$("speed").textContent = (last_ips / 1000 / delta_time).toFixed(1);
$("avg_speed").textContent = (total_instructions / 1000 / running_time).toFixed(1);
$("running_time").textContent = format_timestamp(running_time / 1000 | 0);
}
}
emulator.add_listener("emulator-started", function()
{
last_tick = Date.now();
interval = setInterval(update_info, 1000);
});
emulator.add_listener("emulator-stopped", function()
{
update_info();
if(interval !== null)
{
clearInterval(interval);
}
});
var stats_9p = {
read: 0,
write: 0,
files: [],
};
emulator.add_listener("9p-read-start", function(args)
{
const file = args[0];
stats_9p.files.push(file);
$("info_filesystem").style.display = "block";
$("info_filesystem_status").textContent = "Loading ...";
$("info_filesystem_last_file").textContent = file;
});
emulator.add_listener("9p-read-end", function(args)
{
stats_9p.read += args[1];
$("info_filesystem_bytes_read").textContent = stats_9p.read;
const file = args[0];
stats_9p.files = stats_9p.files.filter(f => f !== file);
if(stats_9p.files[0])
{
$("info_filesystem_last_file").textContent = stats_9p.files[0];
}
else
{
$("info_filesystem_status").textContent = "Idle";
}
});
emulator.add_listener("9p-write-end", function(args)
{
stats_9p.write += args[1];
$("info_filesystem_bytes_written").textContent = stats_9p.write;
if(!stats_9p.files[0])
{
$("info_filesystem_last_file").textContent = args[0];
}
});
var stats_storage = {
read: 0,
read_sectors: 0,
write: 0,
write_sectors: 0,
};
$("ide_type").textContent = emulator.disk_images.cdrom ? " (CD-ROM)" : " (hard disk)";
emulator.add_listener("ide-read-start", function()
{
$("info_storage").style.display = "block";
$("info_storage_status").textContent = "Loading ...";
});
emulator.add_listener("ide-read-end", function(args)
{
stats_storage.read += args[1];
stats_storage.read_sectors += args[2];
$("info_storage_status").textContent = "Idle";
$("info_storage_bytes_read").textContent = stats_storage.read;
$("info_storage_sectors_read").textContent = stats_storage.read_sectors;
});
emulator.add_listener("ide-write-end", function(args)
{
stats_storage.write += args[1];
stats_storage.write_sectors += args[2];
$("info_storage_bytes_written").textContent = stats_storage.write;
$("info_storage_sectors_written").textContent = stats_storage.write_sectors;
});
var stats_net = {
bytes_transmitted: 0,
bytes_received: 0,
};
emulator.add_listener("eth-receive-end", function(args)
{
stats_net.bytes_received += args[0];
$("info_network").style.display = "block";
$("info_network_bytes_received").textContent = stats_net.bytes_received;
});
emulator.add_listener("eth-transmit-end", function(args)
{
stats_net.bytes_transmitted += args[0];
$("info_network").style.display = "block";
$("info_network_bytes_transmitted").textContent = stats_net.bytes_transmitted;
});
emulator.add_listener("mouse-enable", function(is_enabled)
{
os_uses_mouse = is_enabled;
$("info_mouse_enabled").textContent = is_enabled ? "Yes" : "No";
});
emulator.add_listener("screen-set-size", function(args)
{
const [w, h, bpp] = args;
$("info_res").textContent = w + "x" + h + (bpp ? "x" + bpp : "");
$("info_vga_mode").textContent = bpp ? "Graphical" : "Text";
});
$("reset").onclick = function()
{
emulator.restart();
$("reset").blur();
};
add_image_download_button(settings.hda, emulator.disk_images.hda, "hda");
add_image_download_button(settings.hdb, emulator.disk_images.hdb, "hdb");
add_image_download_button(settings.fda, emulator.disk_images.fda, "fda");
add_image_download_button(settings.fdb, emulator.disk_images.fdb, "fdb");
add_image_download_button(settings.cdrom, emulator.disk_images.cdrom, "cdrom");
function add_image_download_button(obj, buffer, type)
{
var elem = $("get_" + type + "_image");
if(!obj || obj.async)
{
elem.style.display = "none";
return;
}
elem.onclick = function(e)
{
// XXX: the filename is a bit confusing for empty disks (it chooses the profile name)
const filename = buffer.file && buffer.file.name || ((profile?.id || "v86") + (type === "cdrom" ? ".iso" : ".img"));
if(buffer.get_as_file)
{
var file = buffer.get_as_file(filename);
download(file, filename);
}
else
{
buffer.get_buffer(function(b)
{
if(b)
{
dump_file(b, filename);
}
else
{
alert("The file could not be loaded. Maybe it's too big?");
}
});
}
elem.blur();
};
}
$("change_fda_image").value = settings.fda ? "Eject floppy image" : "Insert floppy image";
$("change_fda_image").onclick = function()
{
if(emulator.get_disk_fda())
{
emulator.eject_fda();
$("change_fda_image").value = "Insert floppy image";
}
else
{
const file_input = document.createElement("input");
file_input.type = "file";
file_input.onchange = async function(e)
{
const file = file_input.files[0];
if(file)
{
await emulator.set_fda({ buffer: file });
$("change_fda_image").value = "Eject floppy image";
}
};
file_input.click();
}
$("change_fda_image").blur();
};
$("change_fdb_image").value = settings.fdb ? "Eject second floppy image" : "Insert second floppy image";
$("change_fdb_image").onclick = function()
{
if(emulator.get_disk_fdb())
{
emulator.eject_fdb();
$("change_fdb_image").value = "Insert second floppy image";
}
else
{
const file_input = document.createElement("input");
file_input.type = "file";
file_input.onchange = async function(e)
{
const file = file_input.files[0];
if(file)
{
await emulator.set_fdb({ buffer: file });
$("change_fdb_image").value = "Eject second floppy image";
}
};
file_input.click();
}
$("change_fdb_image").blur();
};
$("change_cdrom_image").value = settings.cdrom ? "Eject CD image" : "Insert CD image";
$("change_cdrom_image").onclick = function()
{
if(emulator.v86.cpu.devices.cdrom.has_disk())
{
emulator.eject_cdrom();
$("change_cdrom_image").value = "Insert CD image";
}
else
{
const file_input = document.createElement("input");
file_input.type = "file";
file_input.multiple = "multiple";
file_input.onchange = async function(e)
{
const files = file_input.files;
let buffer;
if(files.length === 1 && files[0].name.endsWith(".iso"))
{
buffer = files[0];
}
else if(files.length)
{
const files2 = [];
for(const file of files)
{
files2.push({
name: file.name,
contents: new Uint8Array(await read_file(file)),
});
}
buffer = iso9660.generate(files2).buffer;
}
if(buffer)
{
await emulator.set_cdrom({ buffer });
$("change_cdrom_image").value = "Eject CD image";
}
};
file_input.click();
}
$("change_cdrom_image").blur();
};
$("memory_dump").onclick = function()
{
const mem8 = emulator.v86.cpu.mem8;
dump_file(new Uint8Array(mem8.buffer, mem8.byteOffset, mem8.length), "v86memory.bin");
$("memory_dump").blur();
};
//$("memory_dump_dmp").onclick = function()
//{
// var memory = emulator.v86.cpu.mem8;
// var memory_size = memory.length;
// var page_size = 4096;
// var header = new Uint8Array(4096);
// var header32 = new Int32Array(header.buffer);
// header32[0] = 0x45474150; // 'PAGE'
// header32[1] = 0x504D5544; // 'DUMP'
// header32[0x10 >> 2] = emulator.v86.cpu.cr[3]; // DirectoryTableBase
// header32[0x24 >> 2] = 1; // NumberProcessors
// header32[0xf88 >> 2] = 1; // DumpType: full dump
// header32[0xfa0 >> 2] = header.length + memory_size; // RequiredDumpSpace
// header32[0x064 + 0 >> 2] = 1; // NumberOfRuns
// header32[0x064 + 4 >> 2] = memory_size / page_size; // NumberOfPages
// header32[0x064 + 8 >> 2] = 0; // BasePage
// header32[0x064 + 12 >> 2] = memory_size / page_size; // PageCount
// dump_file([header, memory], "v86memory.dmp");
// $("memory_dump_dmp").blur();
//};
/**
* @this HTMLElement
*/
$("capture_network_traffic").onclick = function()
{
this.value = "0 packets";
let capture = [];
function do_capture(direction, data)
{
capture.push({ direction, time: performance.now() / 1000, hex_dump: hex_dump(data) });
$("capture_network_traffic").value = capture.length + " packets";
}
emulator.emulator_bus.register("net0-receive", do_capture.bind(this, "I"));
emulator.add_listener("net0-send", do_capture.bind(this, "O"));
this.onclick = function()
{
const capture_raw = capture.map(({ direction, time, hex_dump }) => {
// https://www.wireshark.org/docs/wsug_html_chunked/ChIOImportSection.html
// In wireshark: file -> import from hex -> tick direction indication, timestamp %s.%f
return direction + " " + time.toFixed(6) + hex_dump + "\n";
}).join("");
dump_file(capture_raw, "traffic.hex");
capture = [];
this.value = "0 packets";
};
};
$("save_state").onclick = async function()
{
const result = await emulator.save_state();
dump_file(result, "v86state.bin");
$("save_state").blur();
};
$("load_state").onclick = function()
{
$("load_state_input").click();
$("load_state").blur();
};
/**
* @this HTMLElement
*/
$("load_state_input").onchange = async function()
{
var file = this.files[0];
if(!file)
{
return;
}
var was_running = emulator.is_running();
if(was_running)
{
await emulator.stop();
}
var filereader = new FileReader();
filereader.onload = async function(e)
{
try
{
await emulator.restore_state(e.target.result);
}
catch(err)
{
alert("Something bad happened while restoring the state:\n" + err + "\n\n" +
"Note that the current configuration must be the same as the original");
throw err;
}
if(was_running)
{
emulator.run();
}
};
filereader.readAsArrayBuffer(file);
this.value = "";
};
$("ctrlaltdel").onclick = function()
{
emulator.keyboard_send_scancodes([
0x1D, // ctrl
0x38, // alt
0x53, // delete
// break codes
0x1D | 0x80,
0x38 | 0x80,
0x53 | 0x80,
]);
$("ctrlaltdel").blur();
};
$("alttab").onclick = function()
{
emulator.keyboard_send_scancodes([
0x38, // alt
0x0F, // tab
]);
setTimeout(function()
{
emulator.keyboard_send_scancodes([
0x38 | 0x80,
0x0F | 0x80,
]);
}, 100);
$("alttab").blur();
};
/**
* @this HTMLElement
*/
$("scale").onchange = function()
{
var n = parseFloat(this.value);
if(n || n > 0)
{
emulator.screen_set_scale(n, n);
}
};
$("fullscreen").onclick = function()
{
emulator.screen_go_fullscreen();
};
$("screen_container").onclick = function(e)
{
if(emulator.is_running() && emulator.speaker_adapter?.audio_context?.state === "suspended")
{
emulator.speaker_adapter.audio_context.resume();
}
if(mouse_is_enabled && os_uses_mouse)
{
emulator.lock_mouse();
}
// allow text selection
if(window.getSelection().isCollapsed)
{
const phone_keyboard = document.getElementsByClassName("phone_keyboard")[0];
phone_keyboard.style.top = window.scrollY + e.clientY + 20 + "px";
phone_keyboard.style.left = window.scrollX + e.clientX + "px";
// clean after previous input
phone_keyboard.value = "";
phone_keyboard.focus();
}
};
const phone_keyboard = document.getElementsByClassName("phone_keyboard")[0];
phone_keyboard.setAttribute("autocorrect", "off");
phone_keyboard.setAttribute("autocapitalize", "off");
phone_keyboard.setAttribute("spellcheck", "false");
phone_keyboard.tabIndex = 0;
$("take_screenshot").onclick = function()
{
const image = emulator.screen_make_screenshot();
try {
const w = window.open("");
w.document.write(image.outerHTML);
}
catch(e) {}
$("take_screenshot").blur();
};
if(emulator.speaker_adapter)
{
let is_muted = false;
$("mute").onclick = function()
{
if(is_muted)
{
emulator.speaker_adapter.mixer.set_volume(1, undefined);
is_muted = false;
$("mute").value = "Mute";
}
else
{
emulator.speaker_adapter.mixer.set_volume(0, undefined);
is_muted = true;
$("mute").value = "Unmute";
}
$("mute").blur();
};
}
else
{
$("mute").remove();
}
window.addEventListener("keydown", ctrl_w_rescue, false);
window.addEventListener("keyup", ctrl_w_rescue, false);
window.addEventListener("blur", ctrl_w_rescue, false);
function ctrl_w_rescue(e)
{
if(e.ctrlKey)
{
window.onbeforeunload = function()
{
window.onbeforeunload = null;
return "CTRL-W cannot be sent to the emulator.";
};
}
else
{
window.onbeforeunload = null;
}
}
const script = document.createElement("script");
script.src = "build/xterm.js";
script.async = true;
script.onload = function()
{
emulator.set_serial_container_xtermjs($("terminal"));
};
document.body.appendChild(script);
}
function init_filesystem_panel(emulator)
{
$("filesystem_panel").style.display = "block";
/**
* @this HTMLElement
*/
$("filesystem_send_file").onchange = function()
{
Array.prototype.forEach.call(this.files, function(file)
{
var loader = new SyncFileBuffer(file);
loader.onload = function()
{
loader.get_buffer(async function(buffer)
{
await emulator.create_file("/" + file.name, new Uint8Array(buffer));
});
};
loader.load();
}, this);
this.value = "";
this.blur();
};
/**
* @this HTMLElement
*/
$("filesystem_get_file").onkeypress = async function(e)
{
if(e.which !== 13)
{
return;
}
this.disabled = true;
let result;
try
{
result = await emulator.read_file(this.value);
}
catch(err)
{
console.log(err);
}
this.disabled = false;
if(result)
{
var filename = this.value.replace(/\/$/, "").split("/");
filename = filename[filename.length - 1] || "root";
dump_file(result, filename);
this.value = "";
}
else
{
alert("Can't read file");
}
};
}
function debug_start(emulator)
{
if(!emulator.v86)
{
return;
}
// called as soon as soon as emulation is started, in debug mode
const cpu = emulator.v86.cpu;
$("dump_gdt").onclick = cpu.dump_gdt_ldt.bind(cpu);
$("dump_idt").onclick = cpu.dump_idt.bind(cpu);
$("dump_regs").onclick = () => { cpu.dump_regs_short(); cpu.dump_state(); };
$("dump_pt").onclick = cpu.dump_page_structures.bind(cpu);
$("dump_log").onclick = function()
{
dump_file(log_data.join(""), "v86.log");
};
$("debug_panel").style.display = "block";
setInterval(function()
{
$("debug_panel").textContent =
cpu.get_regs_short().join("\n") + "\n" + cpu.debug_get_state();
$("dump_log").value = "Dump log" + (log_data.length ? " (" + log_data.length + " lines)" : "");
}, 1000);
// helps debugging
window.cpu = cpu;
window.h = h;
window.dump_file = dump_file;
}
function onpopstate(e)
{
location.reload();
}
function push_state(params)
{
if(window.history.pushState)
{
let search = "?" + Array.from(params.entries()).map(([key, value]) => key + "=" + value.replace(/[?&=#+]/g, encodeURIComponent)).join("&");
window.history.pushState({ search }, "", search);
}
}