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); } }