From dcc2b65627acfae4a954b8020d961ccb598c2fb9 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 15 Sep 2024 20:33:33 -0600 Subject: [PATCH 01/23] add lEEt/OS thanks @SuperMaxusa --- src/browser/main.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/browser/main.js b/src/browser/main.js index 186b0a0a96..0fe6fa2b73 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1195,6 +1195,7 @@ cdrom: { url: host + "nanoshell.iso", size: 6785024, + async: false, }, homepage: "https://github.com/iProgramMC/NanoShellOS", }, @@ -1204,6 +1205,7 @@ cdrom: { url: host + "catkernel.iso", size: 11968512, + async: false, }, homepage: "https://catk.neocities.org/", }, @@ -1224,6 +1226,15 @@ size: 368640, }, }, + { + id: "leetos", + name: "lEEt/OS", + fda: { + url: host + "leetos.img", + size: 1474560, + }, + homepage: "http://sininenankka.dy.fi/leetos/index.php", + }, ]; if(DEBUG) From ca8649f38ec7406f9bec464e72ec6332428d7524 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 15 Sep 2024 22:11:52 -0600 Subject: [PATCH 02/23] mention --disable-web-security for browsing in fetch mode --- docs/networking.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/networking.md b/docs/networking.md index 8a1a51585b..401efe6ee3 100644 --- a/docs/networking.md +++ b/docs/networking.md @@ -29,6 +29,12 @@ hosts). Additionally, NTP, ICMP pings and UDP echo packets are handled to a certain degree. See [#1061](https://github.com/copy/v86/pull/1061) for some technical details. +You can pass the following flags to chromium to allow browsing without +restrictions in `fetch` mode: + `--disable-web-security --user-data-dir=/tmp/test` +Note that this turns off the same-origin policy and should only be used +temporarily. + ### wisp networking v86 also supports the [wisp From 808256f0b7302fcd21280b0c15c14b5be9b2cca4 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 15 Sep 2024 22:14:59 -0600 Subject: [PATCH 03/23] add NewOS thanks @SuperMaxusa --- src/browser/main.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/browser/main.js b/src/browser/main.js index 0fe6fa2b73..857f0b3329 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1235,6 +1235,16 @@ }, 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/", + }, ]; if(DEBUG) From 662cd067f4df2f7f3c4717e159e45a5dc1e95c93 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 15 Sep 2024 22:27:34 -0600 Subject: [PATCH 04/23] add tinyaros/aros-broadway/icaros thanks @SuperMaxusa --- src/browser/main.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/browser/main.js b/src/browser/main.js index 857f0b3329..9f30780b90 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1245,6 +1245,46 @@ }, 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/", + }, ]; if(DEBUG) From 63883143de3f1b23ff89599fd36d06ffc2c17e64 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 16 Sep 2024 00:38:05 -0600 Subject: [PATCH 05/23] remove dead profile --- debug.html | 1 - 1 file changed, 1 deletion(-) diff --git a/debug.html b/debug.html index c2054197ec..2a06ae9cce 100644 --- a/debug.html +++ b/debug.html @@ -72,7 +72,6 @@

Debugger

-
From 12cfffdc40db1fc91ff4b5ef8616fba388b90a5c Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 18 Sep 2024 16:58:23 -0600 Subject: [PATCH 06/23] update readme --- Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.md b/Readme.md index 3ef101304d..02eeaed549 100644 --- a/Readme.md +++ b/Readme.md @@ -208,6 +208,7 @@ repository under their own licenses: - [The jor1k project](https://github.com/s-macke/jor1k) for 9p, filesystem and uart drivers - [WinWorld](https://winworldpc.com/) sources of some old operating systems - [OS/2 Museum](https://www.os2museum.com/) sources of some old operating systems +- [ArchiveOS](https://archiveos.org/) sources of several operating systems ## More questions? From 92b5f82928fab90d944f49e9a550353595425380 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 18 Sep 2024 18:45:38 -0600 Subject: [PATCH 07/23] update helenos to 0.14.1 --- src/browser/main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/browser/main.js b/src/browser/main.js index 9f30780b90..965c9a17c8 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -311,8 +311,10 @@ id: "helenos", memory_size: 256 * 1024 * 1024, cdrom: { - url: host + "HelenOS-0.11.2-ia32.iso", - size: 25765888, + //url: host + "HelenOS-0.11.2-ia32.iso", + //size: 25765888, + url: host + "HelenOS-0.14.1-ia32.iso", + size: 25792512, async: false, }, name: "HelenOS", From 8e768e8630038ff040020b1e4d82ecc9de955776 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 18 Sep 2024 19:19:55 -0600 Subject: [PATCH 08/23] tests: increase timeout --- tests/full/run.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/full/run.js b/tests/full/run.js index ae4a906a2b..744cadc827 100755 --- a/tests/full/run.js +++ b/tests/full/run.js @@ -799,7 +799,7 @@ if(cluster.isMaster) { name: "Crazierl", skip_if_disk_image_missing: true, - timeout: 30, + timeout: 60, memory_size: 256 * 1024 * 1024, multiboot: root_path + "/images/crazierl-elf.img", initrd: root_path + "/images/crazierl-initrd.img", From ee0805c343340f4008d83390e578583e67a99af8 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 18 Sep 2024 23:27:25 -0600 Subject: [PATCH 09/23] add some bootsector games/demos (bootrogue, dino, tetros, bootlogo) suggested by @SuperMaxusa --- src/browser/main.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/browser/main.js b/src/browser/main.js index 965c9a17c8..66ce83b594 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -675,11 +675,20 @@ id: "bootbasic", fda: { url: host + "bootbasic.img", - size: 1474560, + 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: "sectorlisp", fda: { @@ -724,6 +733,33 @@ }, 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: { From 8f79a11cd8da288b7104bdc424a4d9641552a074 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 20 Sep 2024 16:16:35 -0600 Subject: [PATCH 10/23] javascript should be made as simple as possible, but not simpler --- debug.html | 113 ++++++++++++++++++++++++----------------------------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/debug.html b/debug.html index 2a06ae9cce..3caf306b7a 100644 --- a/debug.html +++ b/debug.html @@ -4,67 +4,58 @@ Virtual x86 (debug) - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
From c3aaf7afdfa6c7ce74fa9907cc2e59de6881cfc4 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 20 Sep 2024 16:41:12 -0600 Subject: [PATCH 11/23] dead code --- src/lib.js | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/lib.js b/src/lib.js index 71bb7f3a44..3fd01a6c3b 100644 --- a/src/lib.js +++ b/src/lib.js @@ -160,19 +160,6 @@ else if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x12345) === 15 && Math.clz32(-1) === 0) { - /** - * calculate the integer logarithm base 2 of a byte - * @param {number} x - * @return {number} - */ - v86util.int_log2_byte = function(x) - { - dbg_assert(x > 0); - dbg_assert(x < 0x100); - - return 31 - Math.clz32(x); - }; - /** * calculate the integer logarithm base 2 * @param {number} x @@ -198,19 +185,6 @@ else int_log2_table[i] = b; } - /** - * calculate the integer logarithm base 2 of a byte - * @param {number} x - * @return {number} - */ - v86util.int_log2_byte = function(x) - { - dbg_assert(x > 0); - dbg_assert(x < 0x100); - - return int_log2_table[x]; - }; - /** * calculate the integer logarithm base 2 * @param {number} x From 97d392cdbbb7c34907d491986039e775906dd4ca Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 20 Sep 2024 16:57:06 -0600 Subject: [PATCH 12/23] add v86util.round_up_to_next_power_of_2 and some tests --- src/lib.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/lib.js b/src/lib.js index 3fd01a6c3b..f9cf669cb6 100644 --- a/src/lib.js +++ b/src/lib.js @@ -157,8 +157,7 @@ else (function() { - if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && - Math.clz32(0x12345) === 15 && Math.clz32(-1) === 0) + if(typeof Math.clz32 === "function" && Math.clz32(0) === 32 && Math.clz32(0x12345) === 15 && Math.clz32(-1) === 0) { /** * calculate the integer logarithm base 2 @@ -225,6 +224,27 @@ else }; })(); +v86util.round_up_to_next_power_of_2 = function(x) +{ + dbg_assert(x >= 0); + return x <= 1 ? 1 : 1 << 1 + v86util.int_log2(x - 1); +}; + +if(DEBUG) +{ + dbg_assert(v86util.int_log2(1) === 0); + dbg_assert(v86util.int_log2(2) === 1); + dbg_assert(v86util.int_log2(7) === 2); + dbg_assert(v86util.int_log2(8) === 3); + dbg_assert(v86util.int_log2(123456789) === 26); + + dbg_assert(v86util.round_up_to_next_power_of_2(0) === 1); + dbg_assert(v86util.round_up_to_next_power_of_2(1) === 1); + dbg_assert(v86util.round_up_to_next_power_of_2(2) === 2); + dbg_assert(v86util.round_up_to_next_power_of_2(7) === 8); + dbg_assert(v86util.round_up_to_next_power_of_2(8) === 8); + dbg_assert(v86util.round_up_to_next_power_of_2(123456789) === 134217728); +} /** * @constructor From 0bc1008c93cf29b964c1870e4c1c9b62ab9434ce Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 20 Sep 2024 17:04:14 -0600 Subject: [PATCH 13/23] round vga memory size up to next power of 2 --- src/vga.js | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/vga.js b/src/vga.js index 928b77d847..c8afcff402 100644 --- a/src/vga.js +++ b/src/vga.js @@ -206,6 +206,21 @@ function VGAScreen(cpu, bus, screen, vga_memory_size) this.svga_offset_x = 0; this.svga_offset_y = 0; + if(this.vga_memory_size === undefined || this.vga_memory_size < VGA_MIN_MEMORY_SIZE) + { + this.vga_memory_size = VGA_MIN_MEMORY_SIZE; + } + else if(this.vga_memory_size > VGA_MAX_MEMORY_SIZE) + { + this.vga_memory_size = VGA_MAX_MEMORY_SIZE; + } + else + { + // required for pci code + this.vga_memory_size = v86util.round_up_to_next_power_of_2(this.vga_memory_size); + } + dbg_log("effective vga memory size: " + this.vga_memory_size, LOG_VGA); + const pci_revision = 0; // set to 2 for qemu extended registers // Experimental, could probably need some changes @@ -220,7 +235,7 @@ function VGAScreen(cpu, bus, screen, vga_memory_size) this.pci_id = 0x12 << 3; this.pci_bars = [ { - size: vga_memory_size, + size: this.vga_memory_size, }, ]; @@ -339,23 +354,6 @@ function VGAScreen(cpu, bus, screen, vga_memory_size) io.register_write(0x1CF, this, undefined, this.port1CF_write); io.register_read(0x1CF, this, undefined, this.port1CF_read); - if(this.vga_memory_size === undefined || this.vga_memory_size < VGA_MIN_MEMORY_SIZE) - { - this.vga_memory_size = VGA_MIN_MEMORY_SIZE; - dbg_log("vga memory size rounded up to " + this.vga_memory_size, LOG_VGA); - } - else if(this.vga_memory_size > VGA_MAX_MEMORY_SIZE) - { - this.vga_memory_size = VGA_MAX_MEMORY_SIZE; - dbg_log("vga memory size rounded down to " + this.vga_memory_size, LOG_VGA); - } - else if(this.vga_memory_size & (VGA_BANK_SIZE - 1)) - { - // round up to next 64k - this.vga_memory_size |= VGA_BANK_SIZE - 1; - this.vga_memory_size++; - } - const vga_offset = cpu.svga_allocate_memory(this.vga_memory_size) >>> 0; this.svga_memory = v86util.view(Uint8Array, cpu.wasm_memory, vga_offset, this.vga_memory_size); From 49865ebbc606dc7cc57db6f6ec6a3e7fc9cc5f02 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 20 Sep 2024 17:09:15 -0600 Subject: [PATCH 14/23] refactor frontpage loading of profiles --- debug.html | 4 +- index.html | 4 +- src/browser/main.js | 712 +++++++++++++++++++------------------------- 3 files changed, 305 insertions(+), 415 deletions(-) diff --git a/debug.html b/debug.html index 3caf306b7a..6fd6fa46fa 100644 --- a/debug.html +++ b/debug.html @@ -127,9 +127,9 @@

Debugger

- + diff --git a/index.html b/index.html index 271193b3fc..05da552de8 100644 --- a/index.html +++ b/index.html @@ -70,9 +70,9 @@

Select profile

Setup

- +
- + diff --git a/src/browser/main.js b/src/browser/main.js index 66ce83b594..49ec5a33eb 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -2,25 +2,7 @@ (function() { - /** @const */ - var ON_LOCALHOST = !location.hostname.endsWith("copy.sh"); - - /** - * @return {Object.} - */ - function get_query_arguments() - { - var query = location.search.substr(1).split("&"); - var parameters = {}; - - for(var i = 0; i < query.length; i++) - { - var param = query[i].split("="); - parameters[param[0]] = decodeURIComponent(param.slice(1).join("=")); - } - - return parameters; - } + const ON_LOCALHOST = !location.hostname.endsWith("copy.sh"); function set_title(text) { @@ -47,11 +29,11 @@ } } - var progress_ticks = 0; + let progress_ticks = 0; function show_progress(e) { - var el = $("loading"); + const el = $("loading"); el.style.display = "block"; if(e.file_name.endsWith(".wasm")) @@ -68,7 +50,7 @@ return; } - var line = "Downloading images "; + let line = "Downloading images "; if(typeof e.file_index === "number" && e.file_count) { @@ -99,7 +81,7 @@ return document.getElementById(id); } - // these values are stored in localStorage + // These values were previously stored in localStorage const elements_to_restore = [ "memory_size", "video_memory_size", @@ -108,6 +90,14 @@ "enable_acpi", "boot_order", ]; + for(const item of elements_to_restore) + { + try + { + window.localStorage.removeItem(item); + } + catch(e) {} + } function onload() { @@ -117,84 +107,11 @@ return; } - var settings = {}; - - $("start_emulation").onclick = function() + $("start_emulation").onclick = function(e) { - // Save entered options into the local storage - for(const id of elements_to_restore) - { - const element = $(id); - if(element) - { - if(element.tagName === "SELECT" || element.type !== "checkbox") - { - window.localStorage.setItem(id, element.value); - } - else - { - window.localStorage.setItem(id, element.checked); - } - } - } - - $("boot_options").style.display = "none"; - set_profile("custom"); - - var images = []; - var last_file; - - var bios = $("bios").files[0]; - if(bios) - { - last_file = bios; - settings.bios = { buffer: bios }; - } - - var floppy_file = $("floppy_image").files[0]; - if(floppy_file) - { - last_file = floppy_file; - settings.fda = { buffer: floppy_file }; - } - - var cd_file = $("cd_image").files[0]; - if(cd_file) - { - last_file = cd_file; - settings.cdrom = { buffer: cd_file }; - } - - var hda_file = $("hda_image").files[0]; - if(hda_file) - { - last_file = hda_file; - settings.hda = { buffer: hda_file }; - } - - var hdb_file = $("hdb_image") && $("hdb_image").files[0]; - if(hdb_file) - { - last_file = hdb_file; - settings.hdb = { buffer: hdb_file }; - } - - if($("multiboot_image")) - { - var multiboot_file = $("multiboot_image").files[0]; - if(multiboot_file) - { - last_file = multiboot_file; - settings.multiboot = { buffer: multiboot_file }; - } - } - - if(last_file) - { - set_title(last_file.name); - } - - start_emulation(settings); + start_emulation(null, null); + $("start_emulation").blur(); + e.preventDefault(); }; if(DEBUG) @@ -202,11 +119,17 @@ debug_onload(); } - const query_args = get_query_arguments(); - const host = query_args["cdn"] || (ON_LOCALHOST ? "images/" : "//i.copy.sh/"); + if(DEBUG && ON_LOCALHOST) + { + // don't use online relay in debug mode + $("networking_proxy").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 - var oses = [ + const oses = [ { id: "archlinux", name: "Arch Linux", @@ -1328,7 +1251,7 @@ if(DEBUG) { // see tests/kvm-unit-tests/x86/ - var tests = [ + const tests = [ "realmode", // All tests below require an APIC "cmpxchg8b", @@ -1361,7 +1284,7 @@ } } - var profile = query_args["profile"]; + const profile = query_args.get("profile"); if(!profile && !DEBUG) { @@ -1376,92 +1299,38 @@ link.href = "build/xterm.js"; document.head.appendChild(link); - if(query_args["disable_jit"]) - { - settings.disable_jit = true; - } - - if(query_args["use_bochs_bios"]) - { - settings.use_bochs_bios = true; - } - - const m = parseInt(query_args["m"], 10); - if(m > 0) + for(const os of oses) { - settings.memory_size = Math.max(16, m) * 1024 * 1024; - } - - const vram = parseInt(query_args["vram"], 10); - if(vram > 0) - { - settings.vga_memory_size = vram * 1024 * 1024; - } - - settings.networking_proxy = query_args["networking_proxy"]; - settings.audio = query_args["audio"] !== "0"; - settings.acpi = query_args["acpi"]; - - for(var i = 0; i < oses.length; i++) - { - var infos = oses[i]; - - if(profile === infos.id) + if(profile === os.id) { - start_profile(infos); + start_emulation(os, query_args); return; } - var element = $("start_" + infos.id); + const element = $("start_" + os.id); if(element) { - element.onclick = function(infos, element, e) + element.onclick = e => { e.preventDefault(); - set_profile(infos.id); element.blur(); - - start_profile(infos); - }.bind(this, infos, element); + start_emulation(os, null); + }; } } if(profile === "custom") { - if(query_args["hda.url"]) - { - settings.hda = { - size: parseInt(query_args["hda.size"], 10) || undefined, - url: query_args["hda.url"], - async: true, - }; - } + // TODO: if one of the file form fields has a value (firefox), start here? - if(query_args["cdrom.url"]) + if(query_args.has("hda.url") || query_args.has("cdrom.url") || query_args.has("fda.url")) { - settings.cdrom = { - size: parseInt(query_args["cdrom.size"], 10) || undefined, - url: query_args["cdrom.url"], - async: true, - }; + start_emulation(null, query_args); } - - if(query_args["fda.url"]) - { - settings.fda = { - size: parseInt(query_args["fda.size"], 10) || undefined, - url: query_args["fda.url"], - async: false, - }; - } - - if(settings.fda || settings.cdrom || settings.hda) + else { - $("boot_options").style.display = "none"; - - start_emulation(settings, done); - return; + // TODO: fill input fields with query arg values? } } else if(/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_]+$/g.test(profile)) @@ -1479,7 +1348,7 @@ return o && { url: base + "/" + o["url"], async: o["async"], size: o["size"] }; } - start_profile({ + const profile = { id: p["id"], name: p["name"], memory_size: p["memory_size"], @@ -1492,183 +1361,70 @@ multiboot: handle_image(p["multiboot"]), bzimage: handle_image(p["bzimage"]), initrd: handle_image(p["initrd"]), - }); - }); + }; - return; + start_emulation(profile, query_args); + }); } + } - for(const id of elements_to_restore) - { - const saved_value = window.localStorage.getItem(id); + function debug_onload() + { + // called on window.onload, in debug mode - if(saved_value) - { - const element = $(id); - if(element) - { - if(element.type === "checkbox") - { - element.checked = saved_value === "true" ? true : false; - } - else - { - element.value = saved_value; - } - } - } - } + const log_levels = $("log_levels"); - if(DEBUG && ON_LOCALHOST) + if(!log_levels) { - // don't use online relay in debug mode - $("networking_proxy").value = "ws://localhost:8080/"; + return; } - function start_profile(infos) + for(let i = 0; i < LOG_NAMES.length; i++) { - $("boot_options").style.display = "none"; - set_title(infos.name); + const mask = LOG_NAMES[i][0]; - settings.filesystem = infos.filesystem; - - if(infos.state) - { - $("reset").style.display = "none"; - settings.initial_state = infos.state; - } + if(mask === 1) + continue; - settings.fda = infos.fda; - settings.cdrom = infos.cdrom; - settings.hda = infos.hda; - settings.multiboot = infos.multiboot; - settings.bzimage = infos.bzimage; - settings.initrd = infos.initrd; - settings.cmdline = infos.cmdline; - settings.bzimage_initrd_from_filesystem = infos.bzimage_initrd_from_filesystem; - settings.mac_address_translation = infos.mac_address_translation; - settings.cpuid_level = infos.cpuid_level; + const name = LOG_NAMES[i][1].toLowerCase(); + const input = document.createElement("input"); + const label = document.createElement("label"); - settings.acpi = (!infos.state && settings.acpi !== undefined) ? settings.acpi : infos.acpi; - settings.memory_size = (!infos.state && settings.memory_size) ? settings.memory_size : infos.memory_size; - settings.vga_memory_size = (!infos.state && settings.vga_memory_size) ? settings.vga_memory_size : infos.vga_memory_size; + input.type = "checkbox"; - settings.disable_vga_display = infos.disable_vga_display; + label.htmlFor = input.id = "log_" + name; - settings.id = infos.id; - - if(infos.boot_order !== undefined) + if(LOG_LEVEL & mask) { - settings.boot_order = infos.boot_order; + input.checked = true; } + input.mask = mask; - let chunk_size = parseInt(query_args["chunk_size"], 10); - if(chunk_size >= 0) - { - if(chunk_size) - { - chunk_size = Math.min(4 * 1024 * 1024, Math.max(512, chunk_size)); - chunk_size = 1 << Math.ceil(Math.log2(chunk_size)); - } - else - { - chunk_size = undefined; - } - - if(settings.hda) - { - settings.hda.fixed_chunk_size = chunk_size; - } + label.append(input, v86util.pads(name, 4) + " "); + log_levels.appendChild(label); - if(settings.cdrom) - { - settings.cdrom.fixed_chunk_size = chunk_size; - } - } - - if(!DEBUG && infos.homepage) + if(i === Math.floor(LOG_NAMES.length / 2)) { - $("description").style.display = "block"; - const link = document.createElement("a"); - link.href = infos.homepage; - link.textContent = infos.name; - link.target = "_blank"; - $("description").appendChild(document.createTextNode("Running ")); - $("description").appendChild(link); + log_levels.append("\n"); } - - start_emulation(settings, done); } - function done(emulator) + log_levels.onchange = function(e) { - if(query_args["c"]) - { - setTimeout(function() - { - //emulator.serial0_send(query_args["c"] + "\n"); - emulator.keyboard_send_text(query_args["c"] + "\n"); - }, 25); - } - } - } - - function debug_onload() - { - // called on window.onload, in debug mode + const target = e.target; + const mask = target.mask; - var log_levels = $("log_levels"); - - if(log_levels) - { - for(var i = 0; i < LOG_NAMES.length; i++) + if(target.checked) { - var mask = LOG_NAMES[i][0]; - - if(mask === 1) - continue; - - var name = LOG_NAMES[i][1].toLowerCase(), - input = document.createElement("input"), - label = document.createElement("label"); - - input.type = "checkbox"; - - label.htmlFor = input.id = "log_" + name; - - if(LOG_LEVEL & mask) - { - input.checked = true; - } - input.mask = mask; - - label.appendChild(input); - label.appendChild(document.createTextNode(v86util.pads(name, 4) + " ")); - log_levels.appendChild(label); - - if(i === Math.floor(LOG_NAMES.length / 2)) - { - log_levels.appendChild(document.createTextNode("\n")); - } + LOG_LEVEL |= mask; } - - log_levels.onchange = function(e) + else { - var target = e.target, - mask = target.mask; - - if(target.checked) - { - LOG_LEVEL |= mask; - } - else - { - LOG_LEVEL &= ~mask; - } + LOG_LEVEL &= ~mask; + } - target.blur(); - }; - } + target.blur(); + }; } window.addEventListener("load", onload, false); @@ -1689,145 +1445,269 @@ onload(); } - /** @param {?=} done */ - function start_emulation(settings, done) + // we can get here in various ways: + // - the user clicked on the "start emulation" + // - 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) { - /** @const */ - var MB = 1024 * 1024; - - var memory_size = settings.memory_size; + $("boot_options").style.display = "none"; - if(!memory_size) + if(!query_args) { - memory_size = parseInt($("memory_size").value, 10) * MB; - - if(!memory_size) - { - alert("Invalid memory size - reset to 128MB"); - memory_size = 128 * MB; - } + set_profile(profile?.id || "custom"); } - var vga_memory_size = settings.vga_memory_size; + const settings = {}; - if(!vga_memory_size) + if(profile) { - vga_memory_size = parseInt($("video_memory_size").value, 10) * MB; + if(profile.state) + { + $("reset").style.display = "none"; + } - if(!vga_memory_size) + set_title(profile.name); + + settings.initial_state = profile.state; + settings.filesystem = profile.filesystem; + settings.fda = profile.fda; + settings.cdrom = profile.cdrom; + settings.hda = profile.hda; + 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; + + if(!DEBUG && profile.homepage) { - alert("Invalid video memory size - reset to 8MB"); - vga_memory_size = 8 * MB; + $("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(!settings.bios) + if(query_args) { - var bios = $("bios").files[0]; - if(bios) + // ignore certain settings when using a state image + if(!settings.initial_state) { - settings.bios = { buffer: bios }; + 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 = v86util.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, + }; + } + + 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") ? !!query_args.get("acpi") : undefined; + settings.use_bochs_bios = !!query_args.get("use_bochs_bios"); } + + settings.disable_jit = !!query_args.get("disable_jit"); + settings.networking_proxy = query_args.get("networking_proxy"); + settings.audio = query_args.get("audio") && query_args.get("audio") !== "0"; } - if(!settings.fda) + if(!settings.networking_proxy) { - var floppy_file = $("floppy_image").files[0]; - if(floppy_file) - { - settings.fda = { buffer: floppy_file }; - } + settings.networking_proxy = $("networking_proxy").value; } + settings.disable_audio = $("disable_audio").checked || !settings.audio; - if(!settings.bzimage) + // some settings cannot be overridden when a state image is used + if(!settings.initial_state) { - var bzimage = $("bzimage").files[0]; + const bios = $("bios").files[0]; + if(bios && !settings.initial_state) + { + settings.bios = { buffer: bios }; + } + const fda = $("floppy_image").files[0]; + if(fda) + { + settings.fda = { buffer: fda }; + } + 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 hdb = $("hdb_image")?.files[0]; + if(hdb) + { + settings.hdb = { buffer: hdb }; + } + const multiboot = $("multiboot_image")?.files[0]; + if(multiboot) + { + settings.multiboot = { buffer: multiboot }; + } + const bzimage = $("bzimage").files[0]; if(bzimage) { settings.bzimage = { buffer: bzimage }; } - } - - if(!settings.initrd) - { - var initrd = $("initrd").files[0]; + const initrd = $("initrd").files[0]; if(initrd) { settings.initrd = { buffer: initrd }; } - } - const networking_proxy = settings.networking_proxy === undefined ? $("networking_proxy").value : settings.networking_proxy; - const disable_audio = $("disable_audio").checked || !settings.audio; - const enable_acpi = !settings.initial_state && settings.acpi === undefined ? $("enable_acpi").checked : settings.acpi; + const title = multiboot?.name || hda?.name || cdrom?.name || hdb?.name || fda?.name || bios?.name; + if(title) + { + set_title(title); + } - /** @const */ - var BIOSPATH = "bios/"; + const MB = 1024 * 1024; - if(settings.use_bochs_bios) - { - var biosfile = "bochs-bios.bin"; - var vgabiosfile = "bochs-vgabios.bin"; - } - else - { - var biosfile = DEBUG ? "seabios-debug.bin" : "seabios.bin"; - var vgabiosfile = DEBUG ? "vgabios-debug.bin" : "vgabios.bin"; - } + if(!settings.memory_size) + { + let memory_size = parseInt($("memory_size").value, 10) * MB; + if(!memory_size) + { + alert("Invalid memory size - reset to 128MB"); + memory_size = 128 * MB; + } + settings.memory_size = memory_size; + } + if(!settings.vga_memory_size) + { + let vga_memory_size = parseInt($("video_memory_size").value, 10) * MB; + if(!vga_memory_size) + { + alert("Invalid video memory size - reset to 8MB"); + vga_memory_size = 8 * MB; + } + settings.vga_memory_size = vga_memory_size; + } - var bios; - var vga_bios; + if(!settings.boot_order) + { + settings.boot_order = parseInt($("boot_order").value, 16) || 0; + } - // a bios is only needed if the machine is booted - if(!settings.initial_state) - { - bios = { - url: BIOSPATH + biosfile, - }; - vga_bios = { - url: BIOSPATH + vgabiosfile, - }; - } + if(settings.acpi === undefined) + { + settings.acpi = $("enable_acpi").checked; + } - var emulator = new V86({ - memory_size: memory_size, - vga_memory_size: vga_memory_size, + if(!settings.bios) + { + const BIOSPATH = "bios/"; - screen_container: settings.disable_vga_display ? null : $("screen_container"), + if(settings.use_bochs_bios) + { + var biosfile = "bochs-bios.bin"; + var vgabiosfile = "bochs-vgabios.bin"; + } + else + { + var biosfile = DEBUG ? "seabios-debug.bin" : "seabios.bin"; + var vgabiosfile = DEBUG ? "vgabios-debug.bin" : "vgabios.bin"; + } - boot_order: settings.boot_order || parseInt($("boot_order").value, 16) || 0, + settings.bios = { url: BIOSPATH + biosfile }; + settings.vga_bios = { url: BIOSPATH + vgabiosfile }; + } + } + const emulator = new V86({ + screen_container: $("screen_container"), net_device: { type: "ne2k", - relay_url: networking_proxy, + relay_url: settings.networking_proxy, }, + autostart: true, - bios: settings.bios || bios, - vga_bios: settings.bios ? null : vga_bios, + 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, 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: enable_acpi, + acpi: settings.acpi, disable_jit: settings.disable_jit, initial_state: settings.initial_state, filesystem: settings.filesystem || {}, - disable_speaker: disable_audio, + disable_speaker: settings.disable_audio, mac_address_translation: settings.mac_address_translation, cpuid_level: settings.cpuid_level, - - autostart: true, }); - if(DEBUG) window["emulator"] = emulator; + if(DEBUG) window.emulator = emulator; emulator.add_listener("emulator-ready", function() { @@ -1840,7 +1720,7 @@ { const CLEAR_STATS = false; - var panel = document.createElement("pre"); + const panel = document.createElement("pre"); document.body.appendChild(panel); setInterval(function() @@ -1857,7 +1737,7 @@ }, CLEAR_STATS ? 5000 : 1000); } - if(settings.id === "dsl" || settings.id === "helenos" || settings.id === "android" || settings.id === "android4") + if(["dsl", "helenos", "android", "android4"].includes(profile?.id)) { setTimeout(() => { // hack: Start automatically @@ -1867,7 +1747,21 @@ init_ui(settings, emulator); - done && done(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); + } }); emulator.add_listener("download-progress", function(e) @@ -1877,10 +1771,9 @@ emulator.add_listener("download-error", function(e) { - var el = $("loading"); + const el = $("loading"); el.style.display = "block"; - el.textContent = "Loading " + e.file_name + " failed. Check your connection " + - "and reload the page to try again."; + el.textContent = `Loading ${e.file_name} failed. Check your connection and reload the page to try again.`; }); } @@ -1890,11 +1783,10 @@ */ function init_ui(settings, emulator) { - $("boot_options").style.display = "none"; $("loading").style.display = "none"; $("runtime_options").style.display = "block"; $("runtime_infos").style.display = "block"; - if(!settings.disable_vga_display) $("screen_container").style.display = "block"; + $("screen_container").style.display = "block"; if(settings.filesystem) { @@ -1952,7 +1844,6 @@ $("toggle_mouse").blur(); }; - var last_tick = 0; var running_time = 0; var last_instr_counter = 0; @@ -2547,7 +2438,6 @@ }, 1000); // helps debugging - window.emulator = emulator; window.cpu = cpu; window.dump_file = dump_file; } From ca7fe7542c58ba00ea4cf8edb64ed63161b07f42 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 20 Sep 2024 23:02:13 -0600 Subject: [PATCH 15/23] refactor query parameter handling - create query parameters for memory size, mute, acpi, boot order and networking proxy - allow overwriting memory size, boot and acpi order for profiles --- debug.html | 8 ++--- index.html | 8 ++--- src/browser/main.js | 80 +++++++++++++++++++++++++++------------------ 3 files changed, 56 insertions(+), 40 deletions(-) diff --git a/debug.html b/debug.html index 6fd6fa46fa..d79f84670b 100644 --- a/debug.html +++ b/debug.html @@ -179,9 +179,9 @@

Debugger

- + @@ -204,9 +204,9 @@

Debugger

- + diff --git a/index.html b/index.html index 05da552de8..3697eeaabd 100644 --- a/index.html +++ b/index.html @@ -119,9 +119,9 @@

Setup

- + @@ -144,9 +144,9 @@

Setup

- + diff --git a/src/browser/main.js b/src/browser/main.js index 49ec5a33eb..6553baa9e3 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -4,6 +4,11 @@ { 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; + function set_title(text) { document.title = text + " - Virtual x86" + (DEBUG ? " - debug" : ""); @@ -11,6 +16,11 @@ description && (description.content = "Running " + text); } + function bool_arg(x) + { + return !!x && x !== "0"; + } + function format_timestamp(time) { if(time < 60) @@ -1330,7 +1340,12 @@ } else { - // TODO: fill input fields with query arg values? + 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("networking_proxy")) $("networking_proxy").value = query_args.get("networking_proxy"); + 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"); } } else if(/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_]+$/g.test(profile)) @@ -1454,10 +1469,7 @@ { $("boot_options").style.display = "none"; - if(!query_args) - { - set_profile(profile?.id || "custom"); - } + const new_query_args = new URLSearchParams({ "profile": profile?.id || "custom" }); const settings = {}; @@ -1556,26 +1568,28 @@ settings.vga_memory_size = vram * 1024 * 1024; } - settings.acpi = query_args.has("acpi") ? !!query_args.get("acpi") : undefined; - settings.use_bochs_bios = !!query_args.get("use_bochs_bios"); + settings.acpi = query_args.has("acpi") ? bool_arg(query_args.get("acpi")) : undefined; + settings.use_bochs_bios = query_args.get("bios") === "bochs"; } - settings.disable_jit = !!query_args.get("disable_jit"); settings.networking_proxy = query_args.get("networking_proxy"); - settings.audio = query_args.get("audio") && query_args.get("audio") !== "0"; + settings.disable_jit = bool_arg(query_args.get("disable_jit")); + settings.disable_audio = bool_arg(query_args.get("mute")); } if(!settings.networking_proxy) { settings.networking_proxy = $("networking_proxy").value; + if(!DEFAULT_NETWORKING_PROXIES.includes(settings.networking_proxy)) new_query_args.append("networking_proxy", settings.networking_proxy); } - settings.disable_audio = $("disable_audio").checked || !settings.audio; + settings.disable_audio = $("disable_audio").checked || settings.disable_audio; + if(settings.disable_audio) new_query_args.append("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.initial_state) + if(bios) { settings.bios = { buffer: bios }; } @@ -1623,35 +1637,31 @@ const MB = 1024 * 1024; - if(!settings.memory_size) + const memory_size = parseInt($("memory_size").value, 10) || DEFAULT_MEMORY_SIZE; + if(!settings.memory_size || memory_size !== DEFAULT_MEMORY_SIZE) { - let memory_size = parseInt($("memory_size").value, 10) * MB; - if(!memory_size) - { - alert("Invalid memory size - reset to 128MB"); - memory_size = 128 * MB; - } - settings.memory_size = memory_size; + settings.memory_size = memory_size * MB; } - if(!settings.vga_memory_size) + if(memory_size !== DEFAULT_MEMORY_SIZE) new_query_args.append("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) { - let vga_memory_size = parseInt($("video_memory_size").value, 10) * MB; - if(!vga_memory_size) - { - alert("Invalid video memory size - reset to 8MB"); - vga_memory_size = 8 * MB; - } - settings.vga_memory_size = vga_memory_size; + settings.vga_memory_size = vga_memory_size * MB; } + if(vga_memory_size !== DEFAULT_VGA_MEMORY_SIZE) new_query_args.append("vram", String(vga_memory_size)); - if(!settings.boot_order) + const boot_order = parseInt($("boot_order").value, 16) || DEFAULT_BOOT_ORDER; + if(!settings.boot_order || boot_order !== DEFAULT_BOOT_ORDER) { - settings.boot_order = parseInt($("boot_order").value, 16) || 0; + settings.boot_order = boot_order; } + if(settings.boot_order !== DEFAULT_BOOT_ORDER) new_query_args.append("boot_order", String(settings.boot_order)); if(settings.acpi === undefined) { - settings.acpi = $("enable_acpi").checked; + settings.acpi = $("acpi").checked; + if(settings.acpi) new_query_args.append("acpi", "1"); } if(!settings.bios) @@ -1674,6 +1684,11 @@ } } + if(!query_args) + { + push_state(new_query_args); + } + const emulator = new V86({ screen_container: $("screen_container"), net_device: { @@ -2447,11 +2462,12 @@ location.reload(); } - function set_profile(prof) + function push_state(params) { if(window.history.pushState) { - window.history.pushState({ profile: prof }, "", "?profile=" + prof); + const search = "?" + params.toString(); + window.history.pushState({ search }, "", search); } } From a7563e0d357f5924ce5ffe4601fe50e144424de4 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 20 Sep 2024 23:08:03 -0600 Subject: [PATCH 16/23] s/networking_proxy/relay_url --- debug.html | 4 ++-- index.html | 4 ++-- src/browser/main.js | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/debug.html b/debug.html index d79f84670b..a4e2d20c1a 100644 --- a/debug.html +++ b/debug.html @@ -186,9 +186,9 @@

Debugger

- + diff --git a/index.html b/index.html index 3697eeaabd..ee08215592 100644 --- a/index.html +++ b/index.html @@ -126,9 +126,9 @@

Setup

- + diff --git a/src/browser/main.js b/src/browser/main.js index 6553baa9e3..f952e7e838 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -132,7 +132,7 @@ if(DEBUG && ON_LOCALHOST) { // don't use online relay in debug mode - $("networking_proxy").value = "ws://localhost:8080/"; + $("relay_url").value = "ws://localhost:8080/"; } const query_args = new URLSearchParams(location.search); @@ -1342,7 +1342,7 @@ { 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("networking_proxy")) $("networking_proxy").value = query_args.get("networking_proxy"); + 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"); @@ -1572,15 +1572,15 @@ settings.use_bochs_bios = query_args.get("bios") === "bochs"; } - settings.networking_proxy = query_args.get("networking_proxy"); + 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.networking_proxy) + if(!settings.relay_url) { - settings.networking_proxy = $("networking_proxy").value; - if(!DEFAULT_NETWORKING_PROXIES.includes(settings.networking_proxy)) new_query_args.append("networking_proxy", settings.networking_proxy); + settings.relay_url = $("relay_url").value; + if(!DEFAULT_NETWORKING_PROXIES.includes(settings.relay_url)) new_query_args.append("relay_url", settings.relay_url); } settings.disable_audio = $("disable_audio").checked || settings.disable_audio; if(settings.disable_audio) new_query_args.append("mute", "1"); @@ -1693,7 +1693,7 @@ screen_container: $("screen_container"), net_device: { type: "ne2k", - relay_url: settings.networking_proxy, + relay_url: settings.relay_url, }, autostart: true, From ffb0768edbce76647a051b6ba5f603512e7c2b51 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 21 Sep 2024 11:32:03 -0600 Subject: [PATCH 17/23] late c2rust cleanup (modrm) --- src/rust/cpu/modrm.rs | 191 ++++++++++++++---------------------------- 1 file changed, 61 insertions(+), 130 deletions(-) diff --git a/src/rust/cpu/modrm.rs b/src/rust/cpu/modrm.rs index f8add01e64..af085a0747 100644 --- a/src/rust/cpu/modrm.rs +++ b/src/rust/cpu/modrm.rs @@ -2,76 +2,36 @@ use cpu::cpu::*; use paging::OrPageFault; pub unsafe fn resolve_modrm16(modrm_byte: i32) -> OrPageFault { - Ok(match modrm_byte { - 0 | 8 | 16 | 24 | 32 | 40 | 48 | 56 => { - get_seg_prefix_ds(read_reg16(BX) + read_reg16(SI) & 0xFFFF)? - }, - 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 => { - get_seg_prefix_ds(read_reg16(BX) + read_reg16(SI) + read_imm8s()? & 0xFFFF)? - }, - 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 => { - get_seg_prefix_ds(read_reg16(BX) + read_reg16(SI) + read_imm16()? & 0xFFFF)? - }, - 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 => { - get_seg_prefix_ds(read_reg16(BX) + read_reg16(DI) & 0xFFFF)? - }, - 65 | 73 | 81 | 89 | 97 | 105 | 113 | 121 => { - get_seg_prefix_ds(read_reg16(BX) + read_reg16(DI) + read_imm8s()? & 0xFFFF)? - }, - 129 | 137 | 145 | 153 | 161 | 169 | 177 | 185 => { - get_seg_prefix_ds(read_reg16(BX) + read_reg16(DI) + read_imm16()? & 0xFFFF)? - }, - 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 => { - get_seg_prefix_ss(read_reg16(BP) + read_reg16(SI) & 0xFFFF)? - }, - 66 | 74 | 82 | 90 | 98 | 106 | 114 | 122 => { - get_seg_prefix_ss(read_reg16(BP) + read_reg16(SI) + read_imm8s()? & 0xFFFF)? - }, - 130 | 138 | 146 | 154 | 162 | 170 | 178 | 186 => { - get_seg_prefix_ss(read_reg16(BP) + read_reg16(SI) + read_imm16()? & 0xFFFF)? - }, - 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 => { - get_seg_prefix_ss(read_reg16(BP) + read_reg16(DI) & 0xFFFF)? - }, - 67 | 75 | 83 | 91 | 99 | 107 | 115 | 123 => { - get_seg_prefix_ss(read_reg16(BP) + read_reg16(DI) + read_imm8s()? & 0xFFFF)? - }, - 131 | 139 | 147 | 155 | 163 | 171 | 179 | 187 => { - get_seg_prefix_ss(read_reg16(BP) + read_reg16(DI) + read_imm16()? & 0xFFFF)? - }, - 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 => get_seg_prefix_ds(read_reg16(SI) & 0xFFFF)?, - 68 | 76 | 84 | 92 | 100 | 108 | 116 | 124 => { - get_seg_prefix_ds(read_reg16(SI) + read_imm8s()? & 0xFFFF)? - }, - 132 | 140 | 148 | 156 | 164 | 172 | 180 | 188 => { - get_seg_prefix_ds(read_reg16(SI) + read_imm16()? & 0xFFFF)? - }, - 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 => get_seg_prefix_ds(read_reg16(DI) & 0xFFFF)?, - 69 | 77 | 85 | 93 | 101 | 109 | 117 | 125 => { - get_seg_prefix_ds(read_reg16(DI) + read_imm8s()? & 0xFFFF)? - }, - 133 | 141 | 149 | 157 | 165 | 173 | 181 | 189 => { - get_seg_prefix_ds(read_reg16(DI) + read_imm16()? & 0xFFFF)? - }, - 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 => get_seg_prefix_ds(read_imm16()?)?, - 70 | 78 | 86 | 94 | 102 | 110 | 118 | 126 => { - get_seg_prefix_ss(read_reg16(BP) + read_imm8s()? & 0xFFFF)? - }, - 134 | 142 | 150 | 158 | 166 | 174 | 182 | 190 => { - get_seg_prefix_ss(read_reg16(BP) + read_imm16()? & 0xFFFF)? - }, - 7 | 15 | 23 | 31 | 39 | 47 | 55 | 63 => get_seg_prefix_ds(read_reg16(BX) & 0xFFFF)?, - 71 | 79 | 87 | 95 | 103 | 111 | 119 | 127 => { - get_seg_prefix_ds(read_reg16(BX) + read_imm8s()? & 0xFFFF)? - }, - 135 | 143 | 151 | 159 | 167 | 175 | 183 | 191 => { - get_seg_prefix_ds(read_reg16(BX) + read_imm16()? & 0xFFFF)? - }, + match modrm_byte & !0o070 { + 0o000 => get_seg_prefix_ds(read_reg16(BX) + read_reg16(SI) & 0xFFFF), + 0o100 => get_seg_prefix_ds(read_reg16(BX) + read_reg16(SI) + read_imm8s()? & 0xFFFF), + 0o200 => get_seg_prefix_ds(read_reg16(BX) + read_reg16(SI) + read_imm16()? & 0xFFFF), + 0o001 => get_seg_prefix_ds(read_reg16(BX) + read_reg16(DI) & 0xFFFF), + 0o101 => get_seg_prefix_ds(read_reg16(BX) + read_reg16(DI) + read_imm8s()? & 0xFFFF), + 0o201 => get_seg_prefix_ds(read_reg16(BX) + read_reg16(DI) + read_imm16()? & 0xFFFF), + 0o002 => get_seg_prefix_ss(read_reg16(BP) + read_reg16(SI) & 0xFFFF), + 0o102 => get_seg_prefix_ss(read_reg16(BP) + read_reg16(SI) + read_imm8s()? & 0xFFFF), + 0o202 => get_seg_prefix_ss(read_reg16(BP) + read_reg16(SI) + read_imm16()? & 0xFFFF), + 0o003 => get_seg_prefix_ss(read_reg16(BP) + read_reg16(DI) & 0xFFFF), + 0o103 => get_seg_prefix_ss(read_reg16(BP) + read_reg16(DI) + read_imm8s()? & 0xFFFF), + 0o203 => get_seg_prefix_ss(read_reg16(BP) + read_reg16(DI) + read_imm16()? & 0xFFFF), + 0o004 => get_seg_prefix_ds(read_reg16(SI) & 0xFFFF), + 0o104 => get_seg_prefix_ds(read_reg16(SI) + read_imm8s()? & 0xFFFF), + 0o204 => get_seg_prefix_ds(read_reg16(SI) + read_imm16()? & 0xFFFF), + 0o005 => get_seg_prefix_ds(read_reg16(DI) & 0xFFFF), + 0o105 => get_seg_prefix_ds(read_reg16(DI) + read_imm8s()? & 0xFFFF), + 0o205 => get_seg_prefix_ds(read_reg16(DI) + read_imm16()? & 0xFFFF), + 0o006 => get_seg_prefix_ds(read_imm16()?), + 0o106 => get_seg_prefix_ss(read_reg16(BP) + read_imm8s()? & 0xFFFF), + 0o206 => get_seg_prefix_ss(read_reg16(BP) + read_imm16()? & 0xFFFF), + 0o007 => get_seg_prefix_ds(read_reg16(BX) & 0xFFFF), + 0o107 => get_seg_prefix_ds(read_reg16(BX) + read_imm8s()? & 0xFFFF), + 0o207 => get_seg_prefix_ds(read_reg16(BX) + read_imm16()? & 0xFFFF), _ => { dbg_assert!(false); 0 }, - }) + } } pub unsafe fn resolve_modrm32_(modrm_byte: i32) -> OrPageFault { @@ -105,17 +65,16 @@ pub unsafe fn resolve_modrm32_(modrm_byte: i32) -> OrPageFault { }) } unsafe fn resolve_sib(with_imm: bool) -> OrPageFault { - let s; - let sib_byte = read_imm8()? as u8; - let r = (sib_byte as i32 & 7) as u8; - let m = (sib_byte as i32 >> 3 & 7) as u8; + let sib_byte = read_imm8()?; + let r = sib_byte & 7; + let m = sib_byte >> 3 & 7; let base; let seg; - if r as i32 == 4 { + if r == 4 { base = read_reg32(ESP); seg = SS } - else if r as i32 == 5 { + else if r == 5 { if with_imm { base = read_reg32(EBP); seg = SS @@ -126,77 +85,49 @@ unsafe fn resolve_sib(with_imm: bool) -> OrPageFault { } } else { - base = read_reg32(r as i32); + base = read_reg32(r); seg = DS } let offset; - if m as i32 == 4 { + if m == 4 { offset = 0 } else { - s = (sib_byte as i32 >> 6 & 3) as u8; - offset = read_reg32(m as i32) << s as i32 + let s = sib_byte >> 6 & 3; + offset = read_reg32(m) << s } Ok(get_seg_prefix(seg)? + base + offset) } pub unsafe fn resolve_modrm32(modrm_byte: i32) -> OrPageFault { - Ok(match modrm_byte { - 0 | 8 | 16 | 24 | 32 | 40 | 48 | 56 => get_seg_prefix_ds(read_reg32(EAX))?, - 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 => { - get_seg_prefix_ds(read_reg32(EAX) + read_imm8s()?)? - }, - 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 => { - get_seg_prefix_ds(read_reg32(EAX) + read_imm32s()?)? - }, - 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 => get_seg_prefix_ds(read_reg32(ECX))?, - 65 | 73 | 81 | 89 | 97 | 105 | 113 | 121 => { - get_seg_prefix_ds(read_reg32(ECX) + read_imm8s()?)? - }, - 129 | 137 | 145 | 153 | 161 | 169 | 177 | 185 => { - get_seg_prefix_ds(read_reg32(ECX) + read_imm32s()?)? - }, - 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 => get_seg_prefix_ds(read_reg32(EDX))?, - 66 | 74 | 82 | 90 | 98 | 106 | 114 | 122 => { - get_seg_prefix_ds(read_reg32(EDX) + read_imm8s()?)? - }, - 130 | 138 | 146 | 154 | 162 | 170 | 178 | 186 => { - get_seg_prefix_ds(read_reg32(EDX) + read_imm32s()?)? - }, - 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 => get_seg_prefix_ds(read_reg32(EBX))?, - 67 | 75 | 83 | 91 | 99 | 107 | 115 | 123 => { - get_seg_prefix_ds(read_reg32(EBX) + read_imm8s()?)? - }, - 131 | 139 | 147 | 155 | 163 | 171 | 179 | 187 => { - get_seg_prefix_ds(read_reg32(EBX) + read_imm32s()?)? - }, - 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 => resolve_sib(false)?, - 68 | 76 | 84 | 92 | 100 | 108 | 116 | 124 => resolve_sib(true)? + read_imm8s()?, - 132 | 140 | 148 | 156 | 164 | 172 | 180 | 188 => resolve_sib(true)? + read_imm32s()?, - 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 => get_seg_prefix_ds(read_imm32s()?)?, - 69 | 77 | 85 | 93 | 101 | 109 | 117 | 125 => { - get_seg_prefix_ss(read_reg32(EBP) + read_imm8s()?)? - }, - 133 | 141 | 149 | 157 | 165 | 173 | 181 | 189 => { - get_seg_prefix_ss(read_reg32(EBP) + read_imm32s()?)? - }, - 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 => get_seg_prefix_ds(read_reg32(ESI))?, - 70 | 78 | 86 | 94 | 102 | 110 | 118 | 126 => { - get_seg_prefix_ds(read_reg32(ESI) + read_imm8s()?)? - }, - 134 | 142 | 150 | 158 | 166 | 174 | 182 | 190 => { - get_seg_prefix_ds(read_reg32(ESI) + read_imm32s()?)? - }, - 7 | 15 | 23 | 31 | 39 | 47 | 55 | 63 => get_seg_prefix_ds(read_reg32(EDI))?, - 71 | 79 | 87 | 95 | 103 | 111 | 119 | 127 => { - get_seg_prefix_ds(read_reg32(EDI) + read_imm8s()?)? - }, - 135 | 143 | 151 | 159 | 167 | 175 | 183 | 191 => { - get_seg_prefix_ds(read_reg32(EDI) + read_imm32s()?)? - }, + match modrm_byte & !0o070 { + 0o000 => get_seg_prefix_ds(read_reg32(EAX)), + 0o100 => get_seg_prefix_ds(read_reg32(EAX) + read_imm8s()?), + 0o200 => get_seg_prefix_ds(read_reg32(EAX) + read_imm32s()?), + 0o001 => get_seg_prefix_ds(read_reg32(ECX)), + 0o101 => get_seg_prefix_ds(read_reg32(ECX) + read_imm8s()?), + 0o201 => get_seg_prefix_ds(read_reg32(ECX) + read_imm32s()?), + 0o002 => get_seg_prefix_ds(read_reg32(EDX)), + 0o102 => get_seg_prefix_ds(read_reg32(EDX) + read_imm8s()?), + 0o202 => get_seg_prefix_ds(read_reg32(EDX) + read_imm32s()?), + 0o003 => get_seg_prefix_ds(read_reg32(EBX)), + 0o103 => get_seg_prefix_ds(read_reg32(EBX) + read_imm8s()?), + 0o203 => get_seg_prefix_ds(read_reg32(EBX) + read_imm32s()?), + 0o004 => resolve_sib(false), + 0o104 => Ok(resolve_sib(true)? + read_imm8s()?), + 0o204 => Ok(resolve_sib(true)? + read_imm32s()?), + 0o005 => get_seg_prefix_ds(read_imm32s()?), + 0o105 => get_seg_prefix_ss(read_reg32(EBP) + read_imm8s()?), + 0o205 => get_seg_prefix_ss(read_reg32(EBP) + read_imm32s()?), + 0o006 => get_seg_prefix_ds(read_reg32(ESI)), + 0o106 => get_seg_prefix_ds(read_reg32(ESI) + read_imm8s()?), + 0o206 => get_seg_prefix_ds(read_reg32(ESI) + read_imm32s()?), + 0o007 => get_seg_prefix_ds(read_reg32(EDI)), + 0o107 => get_seg_prefix_ds(read_reg32(EDI) + read_imm8s()?), + 0o207 => get_seg_prefix_ds(read_reg32(EDI) + read_imm32s()?), _ => { dbg_assert!(false); 0 }, - }) + } } From c64bbb0251ef49ea807d9ad6a69625af446513bf Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 21 Sep 2024 11:35:08 -0600 Subject: [PATCH 18/23] use std::hint::unreachable_unchecked to reduce code size --- src/rust/cpu/modrm.rs | 4 ++-- src/rust/jit.rs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/rust/cpu/modrm.rs b/src/rust/cpu/modrm.rs index af085a0747..67fe2da721 100644 --- a/src/rust/cpu/modrm.rs +++ b/src/rust/cpu/modrm.rs @@ -29,7 +29,7 @@ pub unsafe fn resolve_modrm16(modrm_byte: i32) -> OrPageFault { 0o207 => get_seg_prefix_ds(read_reg16(BX) + read_imm16()? & 0xFFFF), _ => { dbg_assert!(false); - 0 + std::hint::unreachable_unchecked() }, } } @@ -127,7 +127,7 @@ pub unsafe fn resolve_modrm32(modrm_byte: i32) -> OrPageFault { 0o207 => get_seg_prefix_ds(read_reg32(EDI) + read_imm32s()?), _ => { dbg_assert!(false); - 0 + std::hint::unreachable_unchecked() }, } } diff --git a/src/rust/jit.rs b/src/rust/jit.rs index 8dd5d8a6f9..b1d782b331 100644 --- a/src/rust/jit.rs +++ b/src/rust/jit.rs @@ -310,7 +310,15 @@ pub struct JitContext<'a> { pub instruction_counter: WasmLocal, } impl<'a> JitContext<'a> { - pub fn reg(&self, i: u32) -> WasmLocal { self.register_locals[i as usize].unsafe_clone() } + pub fn reg(&self, i: u32) -> WasmLocal { + match self.register_locals.get(i as usize) { + Some(x) => x.unsafe_clone(), + None => { + dbg_assert!(false); + unsafe { std::hint::unreachable_unchecked() } + }, + } + } } pub const JIT_INSTR_BLOCK_BOUNDARY_FLAG: u32 = 1 << 0; From e736e635f40c4fe36a0269349a3b0d8b83455683 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 21 Sep 2024 14:46:21 -0600 Subject: [PATCH 19/23] use virtio-net for arch profile --- src/browser/main.js | 6 +++++- src/virtio_net.js | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/browser/main.js b/src/browser/main.js index f952e7e838..d48b9056b8 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -149,6 +149,7 @@ filesystem: { baseurl: host + "arch/", }, + net_device_type: "virtio", }, { id: "archlinux-boot", @@ -170,6 +171,7 @@ "init=/usr/bin/init-openrc net.ifnames=0 biosdevname=0", ].join(" "), bzimage_initrd_from_filesystem: true, + net_device_type: "virtio", }, { id: "copy/skiffos", @@ -1498,6 +1500,7 @@ 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) { @@ -1570,6 +1573,7 @@ settings.acpi = query_args.has("acpi") ? bool_arg(query_args.get("acpi")) : undefined; settings.use_bochs_bios = query_args.get("bios") === "bochs"; + settings.net_device_type = query_args.get("net_device_type") === "virtio" ? "virtio" : "ne2k"; } settings.relay_url = query_args.get("relay_url"); @@ -1692,7 +1696,7 @@ const emulator = new V86({ screen_container: $("screen_container"), net_device: { - type: "ne2k", + type: settings.net_device_type || "ne2k", relay_url: settings.relay_url, }, autostart: true, diff --git a/src/virtio_net.js b/src/virtio_net.js index b8e5a4f79c..eb972986a3 100644 --- a/src/virtio_net.js +++ b/src/virtio_net.js @@ -101,6 +101,7 @@ function VirtioNet(cpu, bus, preserve_mac_from_state_image) const buffer = new Uint8Array(bufchain.length_readable); bufchain.get_next_blob(buffer); this.bus.send("net" + this.id + "-send", buffer.subarray(12)); + this.bus.send("eth-transmit-end", [buffer.length - 12]); this.virtio.queues[queue_id].push_reply(bufchain); } this.virtio.queues[queue_id].flush_replies(); @@ -188,6 +189,7 @@ function VirtioNet(cpu, bus, preserve_mac_from_state_image) }); this.bus.register("net" + this.id + "-receive", data => { + this.bus.send("eth-receive-end", [data.length]); const with_header = new Uint8Array(12 + data.byteLength); const view = new DataView(with_header.buffer, with_header.byteOffset, with_header.byteLength); view.setInt16(10, 1); From c758d6da40c79d8ee648e00ff66ee66e44b33501 Mon Sep 17 00:00:00 2001 From: chschnell Date: Sat, 21 Sep 2024 16:12:00 -0600 Subject: [PATCH 20/23] vga: handle character map select register and writes to font data --- src/vga.js | 69 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/src/vga.js b/src/vga.js index c8afcff402..5b1e63656f 100644 --- a/src/vga.js +++ b/src/vga.js @@ -276,6 +276,7 @@ function VGAScreen(cpu, bus, screen, vga_memory_size) this.sequencer_memory_mode = 0; this.clocking_mode = 0; this.graphics_index = -1; + this.character_map_select = 0; this.plane_read = 0; // value 0-3, which plane to read this.planar_mode = 0; @@ -323,23 +324,22 @@ function VGAScreen(cpu, bus, screen, vga_memory_size) io.register_read(0x3CC, this, this.port3CC_read); - io.register_write(0x3D4, this, this.port3D4_write, value => { - this.port3D4_write(value & 0xFF); - this.port3D5_write(value >> 8 & 0xFF); - }); - io.register_write(0x3D5, this, this.port3D5_write, value => { - dbg_log("16-bit write to 3D5: " + h(value, 4), LOG_VGA); - this.port3D5_write(value & 0xFF); - }); + io.register_write(0x3D4, this, this.port3D4_write, this.port3D4_write16); + io.register_write(0x3D5, this, this.port3D5_write, this.port3D5_write16); io.register_read(0x3D4, this, this.port3D4_read); - io.register_read(0x3D5, this, this.port3D5_read, () => { - dbg_log("Warning: 16-bit read from 3D5", LOG_VGA); - return this.port3D5_read(); - }); + io.register_read(0x3D5, this, this.port3D5_read, this.port3D5_read16); + + // use same handlers for monochrome text-mode's alternate port addresses 0x3B4/0x3B5 as for the regular addresses (0x3D4/0x3D5) + io.register_write(0x3B4, this, this.port3D4_write, this.port3D4_write16); + io.register_write(0x3B5, this, this.port3D5_write, this.port3D5_write16); + + io.register_read(0x3B4, this, this.port3D4_read); + io.register_read(0x3B5, this, this.port3D5_read, this.port3D5_read16); io.register_read(0x3CA, this, function() { dbg_log("3CA read", LOG_VGA); return 0; }); + // use same handler for monochrome text-mode's alternate port address 0x3BA as for its regular address (0x3DA) io.register_read(0x3DA, this, this.port3DA_read); io.register_read(0x3BA, this, this.port3DA_read); @@ -447,6 +447,7 @@ VGAScreen.prototype.get_state = function() state[60] = this.line_compare; state[61] = this.pixel_buffer; state[62] = this.dac_mask; + state[63] = this.character_map_select; return state; }; @@ -516,6 +517,7 @@ VGAScreen.prototype.set_state = function(state) this.line_compare = state[60]; state[61] && this.pixel_buffer.set(state[61]); this.dac_mask = state[62] === undefined ? 0xFF : state[62]; + this.character_map_select = state[63] === undefined ? 0 : state[63]; this.screen.set_mode(this.graphical_mode); @@ -600,8 +602,9 @@ VGAScreen.prototype.vga_memory_read = function(addr) var plane = this.plane_read; if(!this.graphical_mode) { - // We currently put all text data linearly - plane = 0; + // We store all text data linearly and font data in plane 2. + // TODO: works well for planes 0 and 2, but what about plane 1? + plane &= 0x3; } else if(this.sequencer_memory_mode & 0x8) { @@ -645,7 +648,7 @@ VGAScreen.prototype.vga_memory_write = function(addr, value) { if(!(this.plane_write_bm & 0x3)) { - // Ignore writes to font planes. + this.plane2[addr] = value; return; } this.vga_memory_write_text_mode(addr, value); @@ -1520,7 +1523,20 @@ VGAScreen.prototype.port3C5_write = function(value) break; case 0x02: dbg_log("plane write mask: " + h(value), LOG_VGA); + var previous_plane_write_bm = this.plane_write_bm; this.plane_write_bm = value; + if(this.graphical_text && previous_plane_write_bm !== 0xf && (previous_plane_write_bm & 0x4) && !(this.plane_write_bm & 0x4)) + { + // End of font plane 2 write access (initial value of plane_write_bm assumed to be 0xf) + } + break; + case 0x03: + dbg_log("character map select: " + h(value), LOG_VGA); + var previous_character_map_select = this.character_map_select; + this.character_map_select = value; + if(this.graphical_text && previous_character_map_select !== this.character_map_select) + { + } break; case 0x04: dbg_log("sequencer memory mode: " + h(value), LOG_VGA); @@ -1541,6 +1557,8 @@ VGAScreen.prototype.port3C5_read = function() return this.clocking_mode; case 0x02: return this.plane_write_bm; + case 0x03: + return this.character_map_select; case 0x04: return this.sequencer_memory_mode; case 0x06: @@ -1759,6 +1777,12 @@ VGAScreen.prototype.port3D4_write = function(register) this.index_crtc = register; }; +VGAScreen.prototype.port3D4_write16 = function(register) +{ + this.port3D4_write(register & 0xFF); + this.port3D5_write(register >> 8 & 0xFF); +}; + VGAScreen.prototype.port3D4_read = function() { dbg_log("3D4 read / crtc index: " + this.index_crtc, LOG_VGA); @@ -1816,12 +1840,13 @@ VGAScreen.prototype.port3D5_write = function(value) break; case 0x9: dbg_log("3D5 / max scan line write: " + h(value), LOG_VGA); + var previous_max_scan_line = this.max_scan_line; this.max_scan_line = value; this.line_compare = (this.line_compare & 0x1FF) | (value << 3 & 0x200); var previous_vertical_blank_start = this.vertical_blank_start; this.vertical_blank_start = (this.vertical_blank_start & 0x1FF) | (value << 4 & 0x200); - if(previous_vertical_blank_start !== this.vertical_blank_start) + if(((previous_max_scan_line ^ this.max_scan_line) & 0x9F) || previous_vertical_blank_start !== this.vertical_blank_start) { this.update_vga_size(); } @@ -1956,6 +1981,12 @@ VGAScreen.prototype.port3D5_write = function(value) }; +VGAScreen.prototype.port3D5_write16 = function(register) +{ + dbg_log("16-bit write to 3D5: " + h(register, 4), LOG_VGA); + this.port3D5_write(register & 0xFF); +}; + VGAScreen.prototype.port3D5_read = function() { dbg_log("3D5 read " + h(this.index_crtc), LOG_VGA); @@ -2011,6 +2042,12 @@ VGAScreen.prototype.port3D5_read = function() } }; +VGAScreen.prototype.port3D5_read16 = function() +{ + dbg_log("Warning: 16-bit read from 3D5", LOG_VGA); + return this.port3D5_read(); +}; + VGAScreen.prototype.port3DA_read = function() { dbg_log("3DA read - status 1 and clear attr index", LOG_VGA); From c1ef7144542d9f03a2150bad27e7525b481a136c Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 21 Sep 2024 16:14:50 -0600 Subject: [PATCH 21/23] serial test: log data if test times out --- tests/api/serial.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/api/serial.js b/tests/api/serial.js index 84132b8964..88ab4c3a79 100755 --- a/tests/api/serial.js +++ b/tests/api/serial.js @@ -34,6 +34,7 @@ setTimeout(async () => }, 1000); const timeout = setTimeout(() => { + console.log(serial_data); throw new Error("Timeout"); }, 60 * 1000); From f92e6b4b55c3f6e5a327b836375df20fedd45fa4 Mon Sep 17 00:00:00 2001 From: chschnell Date: Sat, 21 Sep 2024 19:08:38 -0600 Subject: [PATCH 22/23] Add VGA graphical text mode --- Makefile | 2 +- debug.html | 1 + src/browser/main.js | 5 +- src/browser/screen.js | 18 +- src/browser/starter.js | 4 +- src/cpu.js | 2 +- src/vga.js | 110 +++++++- src/vga_text.js | 628 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 747 insertions(+), 23 deletions(-) create mode 100644 src/vga_text.js diff --git a/Makefile b/Makefile index 892b1fe07e..bba402d2a4 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ CARGO_FLAGS_SAFE=\ CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory -C target-feature=+multivalue -C target-feature=+simd128 CORE_FILES=const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \ - memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js \ + memory.js dma.js pit.js vga.js vga_text.js ps2.js rtc.js uart.js \ acpi.js apic.js ioapic.js \ state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js \ bus.js log.js cpu.js debug.js \ diff --git a/debug.html b/debug.html index a4e2d20c1a..57594cb869 100644 --- a/debug.html +++ b/debug.html @@ -20,6 +20,7 @@ + diff --git a/src/browser/main.js b/src/browser/main.js index d48b9056b8..bdabbb2256 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -1694,7 +1694,10 @@ } const emulator = new V86({ - screen_container: $("screen_container"), + screen: { + container: $("screen_container"), + use_graphical_text: false, + }, net_device: { type: settings.net_device_type || "ne2k", relay_url: settings.relay_url, diff --git a/src/browser/screen.js b/src/browser/screen.js index df8945139d..176ae429e3 100644 --- a/src/browser/screen.js +++ b/src/browser/screen.js @@ -37,7 +37,7 @@ function ScreenAdapter(options, screen_fill_buffer) changed_rows, // are we in graphical mode now? - is_graphical = false, + is_graphical = !!options.use_graphical_text, // Index 0: ASCII code // Index 1: Blinking @@ -134,9 +134,19 @@ function ScreenAdapter(options, screen_fill_buffer) this.init = function() { - // not necessary, because this gets initialized by the bios early, - // but nicer to look at - this.set_size_text(80, 25); + // initialize with mode and size presets as expected by the bios + // to avoid flickering during early startup + this.set_mode(is_graphical); + + if(is_graphical) + { + // assume 80x25 with 9x16 font + this.set_size_graphical(720, 400, 720, 400); + } + else + { + this.set_size_text(80, 25); + } this.timer(); }; diff --git a/src/browser/starter.js b/src/browser/starter.js index 154ae3e4f4..5813c1fa97 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -305,6 +305,7 @@ V86.prototype.continue_init = async function(emulator, options) settings.cpuid_level = options.cpuid_level; settings.virtio_console = options.virtio_console; settings.virtio_net = options.virtio_net; + settings.screen_options = options.screen_options; const relay_url = options.network_relay_url || options.net_device && options.net_device.relay_url; if(relay_url) @@ -345,13 +346,14 @@ V86.prototype.continue_init = async function(emulator, options) if(screen_options.container) { - this.screen_adapter = new ScreenAdapter(screen_options, () => this.v86.cpu.devices.vga.screen_fill_buffer()); + this.screen_adapter = new ScreenAdapter(screen_options, () => this.v86.cpu.devices.vga && this.v86.cpu.devices.vga.screen_fill_buffer()); } else { this.screen_adapter = new DummyScreenAdapter(); } settings.screen = this.screen_adapter; + settings.screen_options = screen_options; if(options.serial_container) { diff --git a/src/cpu.js b/src/cpu.js index c81759fd42..c1b6210ed0 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -940,7 +940,7 @@ CPU.prototype.init = function(settings, device_bus) this.devices.dma = new DMA(this); - this.devices.vga = new VGAScreen(this, device_bus, settings.screen, settings.vga_memory_size || 8 * 1024 * 1024); + this.devices.vga = new VGAScreen(this, device_bus, settings.screen, settings.vga_memory_size || 8 * 1024 * 1024, settings.screen_options || {}); this.devices.ps2 = new PS2(this, device_bus); diff --git a/src/vga.js b/src/vga.js index 5b1e63656f..2f34ccb738 100644 --- a/src/vga.js +++ b/src/vga.js @@ -50,8 +50,9 @@ const VGA_HOST_MEMORY_SPACE_SIZE = Uint32Array.from([ * @param {BusConnector} bus * @param {ScreenAdapter|DummyScreenAdapter} screen * @param {number} vga_memory_size + * @param {Object} options */ -function VGAScreen(cpu, bus, screen, vga_memory_size) +function VGAScreen(cpu, bus, screen, vga_memory_size, options) { this.cpu = cpu; @@ -166,7 +167,6 @@ function VGAScreen(cpu, bus, screen, vga_memory_size) /** @type {boolean} */ this.graphical_mode = false; - this.screen.set_mode(this.graphical_mode); /* * VGA palette containing 256 colors for video mode 13, svga 8bpp, etc. @@ -377,9 +377,41 @@ function VGAScreen(cpu, bus, screen, vga_memory_size) (addr, value) => this.vga_memory_write(addr, value), ); + if(options.use_graphical_text) + { + this.graphical_text = new GraphicalText(this); + } + cpu.devices.pci.register_device(this); } +VGAScreen.prototype.grab_text_content = function(keep_whitespace) +{ + var addr = this.start_address << 1; + const split_screen_row = this.scan_line_to_screen_row(this.line_compare); + const row_offset = Math.max(0, (this.offset_register * 2 - this.max_cols) * 2); + const text_rows = []; + + for(var row = 0; row < this.max_rows; row++) + { + if(row === split_screen_row) + { + addr = 0; + } + + let line = ""; + for(var col = 0; col < this.max_cols; col++, addr += 2) + { + line += String.fromCodePoint(this.vga_memory[addr]); + } + + text_rows.push(keep_whitespace ? line : line.trimEnd()); + addr += row_offset; + } + + return text_rows; +}; + VGAScreen.prototype.get_state = function() { var state = []; @@ -519,7 +551,7 @@ VGAScreen.prototype.set_state = function(state) this.dac_mask = state[62] === undefined ? 0xFF : state[62]; this.character_map_select = state[63] === undefined ? 0 : state[63]; - this.screen.set_mode(this.graphical_mode); + this.screen.set_mode(this.graphical_mode || !!this.graphical_text); if(this.graphical_mode) { @@ -823,6 +855,11 @@ VGAScreen.prototype.apply_bitmask = function(data_dword, bitmask_dword) VGAScreen.prototype.text_mode_redraw = function() { + if(this.graphical_text) + { + return; + } + const split_screen_row = this.scan_line_to_screen_row(this.line_compare); const row_offset = Math.max(0, (this.offset_register * 2 - this.max_cols) * 2); const blink_flag = this.attribute_mode & 1 << 3; @@ -898,15 +935,23 @@ VGAScreen.prototype.vga_memory_write_text_mode = function(addr, value) chr = value; color = this.vga_memory[addr | 1]; } + const blink_flag = this.attribute_mode & 1 << 3; const blinking = blink_flag && (color & 1 << 7); const bg_color_mask = blink_flag ? 7 : 0xF; this.bus.send("screen-put-char", [row, col, chr]); - this.screen.put_char(row, col, chr, blinking, - this.vga256_palette[this.dac_mask & this.dac_map[color >> 4 & bg_color_mask]], - this.vga256_palette[this.dac_mask & this.dac_map[color & 0xF]]); + if(this.graphical_text) + { + this.graphical_text.invalidate_row(row); + } + else + { + this.screen.put_char(row, col, chr, blinking, + this.vga256_palette[this.dac_mask & this.dac_map[color >> 4 & bg_color_mask]], + this.vga256_palette[this.dac_mask & this.dac_map[color & 0xF]]); + } }; VGAScreen.prototype.update_cursor = function() @@ -927,9 +972,16 @@ VGAScreen.prototype.update_cursor = function() } dbg_assert(row >= 0 && col >= 0); - // NOTE: is allowed to be out of bounds - this.screen.update_cursor(row, col); + + if(this.graphical_text) + { + this.graphical_text.set_cursor_pos(row, col); + } + else + { + this.screen.update_cursor(row, col); + } }; VGAScreen.prototype.complete_redraw = function() @@ -1122,8 +1174,16 @@ VGAScreen.prototype.set_size_text = function(cols_count, rows_count) this.max_cols = cols_count; this.max_rows = rows_count; - this.screen.set_size_text(cols_count, rows_count); this.bus.send("screen-set-size", [cols_count, rows_count, 0]); + + if(this.graphical_text) + { + this.graphical_text.set_size(rows_count, cols_count); + } + else + { + this.screen.set_size_text(cols_count, rows_count); + } }; VGAScreen.prototype.set_size_graphical = function(width, height, virtual_width, virtual_height, bpp) @@ -1341,7 +1401,15 @@ VGAScreen.prototype.update_cursor_scanline = function() const start = Math.min(max, this.cursor_scanline_start & 0x1F); const end = Math.min(max, this.cursor_scanline_end & 0x1F); const visible = !disabled && start < end; - this.screen.update_cursor_scanline(start, end, visible); + + if(this.graphical_text) + { + this.graphical_text.set_cursor_attr(start, end, visible); + } + else + { + this.screen.update_cursor_scanline(start, end, visible); + } }; /** @@ -1388,11 +1456,11 @@ VGAScreen.prototype.port3C0_write = function(value) var previous_mode = this.attribute_mode; this.attribute_mode = value; - var is_graphical = (value & 0x1) > 0; + const is_graphical = (value & 0x1) !== 0; if(!this.svga_enabled && this.graphical_mode !== is_graphical) { this.graphical_mode = is_graphical; - this.screen.set_mode(this.graphical_mode); + this.screen.set_mode(this.graphical_mode || !!this.graphical_text); } if((previous_mode ^ value) & 0x40) @@ -1528,6 +1596,7 @@ VGAScreen.prototype.port3C5_write = function(value) if(this.graphical_text && previous_plane_write_bm !== 0xf && (previous_plane_write_bm & 0x4) && !(this.plane_write_bm & 0x4)) { // End of font plane 2 write access (initial value of plane_write_bm assumed to be 0xf) + this.graphical_text.invalidate_font_shape(); } break; case 0x03: @@ -1536,6 +1605,7 @@ VGAScreen.prototype.port3C5_write = function(value) this.character_map_select = value; if(this.graphical_text && previous_character_map_select !== this.character_map_select) { + this.graphical_text.set_character_map(this.character_map_select); } break; case 0x04: @@ -2425,9 +2495,19 @@ VGAScreen.prototype.screen_fill_buffer = function() if(!this.graphical_mode) { // text mode - // Update retrace behaviour anyway - programs waiting for signal before - // changing to graphical mode - this.update_vertical_retrace(); + if(this.graphical_text) + { + const image_data = this.graphical_text.render(); + this.screen.update_buffer([{ + image_data: image_data, + screen_x: 0, + screen_y: 0, + buffer_x: 0, + buffer_y: 0, + buffer_width: image_data.width, + buffer_height: image_data.height + }]); + } return; } diff --git a/src/vga_text.js b/src/vga_text.js new file mode 100644 index 0000000000..5b5949b038 --- /dev/null +++ b/src/vga_text.js @@ -0,0 +1,628 @@ +/* +vga_text.js + +Renders text to image buffer using VGA fonts and attributes. +*/ +"use strict"; + +/** + * @constructor + * @param {VGAScreen} vga + */ +function GraphicalText(vga) +{ + this.vga = vga; + + /** + * Number of text columns + * @type {number} + */ + this.txt_width = 80; + + /** + * Number of text rows + * @type {number} + */ + this.txt_height = 25; + + /** + * If true then at least one row in txt_row_dirty is marked as modified + * @type{number} + */ + this.txt_dirty = 0; + + /** + * One bool per row, row was modified if its entry is != 0 + */ + this.txt_row_dirty = new Uint8Array(this.txt_height); + + /** + * Font bitmaps in VGA memory were changed if true + * @type{boolean} + */ + this.font_data_dirty = false; + + /** + * Font width in pixel (8, 9 or 16) + * @type {number} + */ + this.font_width = 9; + + /** + * Font height in pixel (0...32) + * @type {number} + */ + this.font_height = 16; + + /** + * Duplicate 8th to 9th column in horizontal line drawing characters if true (Line Graphics Enable) + * @type{boolean} + */ + this.font_lge = false; + + /** + * Flat bitmap of 8 fonts, array of size: 8 * 256 * font_width * font_height + * @type{Uint8ClampedArray} + */ + this.font_bitmap = new Uint8ClampedArray(8 * 256 * this.font_width * this.font_height); + + /** + * True: blink when msb (0x80) of text attribute is set (8 background colors) + * False: msb selects background intensity (16 background colors) + * @type{boolean} + */ + this.font_blink_enabled = false; + + /** + * Active index (0...7) of font A + * @type {number} + */ + this.font_index_A = 0; + + /** + * Active index (0...7) of font B (TODO) + * @type {number} + */ + this.font_index_B = 0; + + /** + * If true then cursor_enabled_latch, cursor_top_latch and cursor_bottom_latch were overwritten since last call to render(). + * @type{boolean} + */ + this.cursor_attr_dirty = false; + + /** + * Latest value for cursor_enabled if cursor_attr_dirty is true + * @type{boolean} + */ + this.cursor_enabled_latch = false; + + /** + * Latest value for cursor_top_latch if cursor_attr_dirty is true + * @type {number} + */ + this.cursor_top_latch = 0; + + /** + * Latest value for cursor_bottom_latch if cursor_attr_dirty is true + * @type {number} + */ + this.cursor_bottom_latch = 0; + + /** + * If true then cursor_row_latch and cursor_col_latch were overwritten since last call to render(). + * @type{boolean} + */ + this.cursor_pos_dirty = false; + + /** + * Latest value for cursor_row if cursor_pos_dirty is true + * @type {number} + */ + this.cursor_row_latch = 0; + + /** + * Latest value for cursor_col if cursor_pos_dirty is true + * @type {number} + */ + this.cursor_col_latch = 0; + + /** + * Emulate cursor if true, else disable cursor + * @type{boolean} + */ + this.cursor_enabled = false; + + /** + * Cursor position's row (0...txt_height-1) + * @type {number} + */ + this.cursor_row = 0; + + /** + * Cursor position's column (0...txt_width-1) + * @type {number} + */ + this.cursor_col = 0; + + /** + * Cursor box's top scanline (0...font_height) + * @type {number} + */ + this.cursor_top = 0; + + /** + * Cursor box's bottom scanline (0...font_height, inclusive) + * @type {number} + */ + this.cursor_bottom = 0; + + /** + * Tracked value of register vga.attribute_mode + * @type {number} + */ + this.vga_attribute_mode = 0; + + /** + * Tracked value of register vga.clocking_mode + * @type {number} + */ + this.vga_clocking_mode = 0; + + /** + * Tracked value of register vga.max_scan_line + * @type {number} + */ + this.vga_max_scan_line = 0; + + /** + * Width of graphics canvas in pixel (txt_width * font_width) + * @type {number} + */ + this.gfx_width = this.txt_width * this.font_width; + + /** + * Height of graphics canvas in pixel (txt_height * font_height) + * @type {number} + */ + this.gfx_height = this.txt_height * this.font_height; + + /** + * Local screen bitmap buffer, array of size: gfx_width * gfx_height * 4 + * @type{Uint8ClampedArray} + */ + this.gfx_data = new Uint8ClampedArray(this.gfx_width * this.gfx_height * 4); + + /** + * Image container of local screen bitmap buffer gfx_data + * @type{ImageData} + */ + this.image_data = new ImageData(this.gfx_data, this.gfx_width, this.gfx_height); + + /** + * Show cursor and blinking text now if true (controlled by framerate counter) + * @type{boolean} + */ + this.blink_visible = false; + + /** + * Frame counter to control blink rate of type Uint32 + * @type {number} + */ + this.frame_count = 0; +} + +GraphicalText.prototype.rebuild_font_bitmap = function(width_9px, width_double) +{ + const font_height = this.font_height; + const font_lge = this.font_lge; + const src_bitmap = this.vga.plane2; + const dst_bitmap = new Uint8ClampedArray(8 * 256 * this.font_width * font_height); + const vga_inc_chr = 32 - font_height; + + let i_dst = 0; + const copy_bit = width_double ? + function(value) + { + dst_bitmap[i_dst++] = value; + dst_bitmap[i_dst++] = value; + } : + function(value) + { + dst_bitmap[i_dst++] = value; + }; + + let i_src = 0; + for(let i_font = 0; i_font < 8; ++i_font) + { + for(let i_chr = 0; i_chr < 256; ++i_chr, i_src += vga_inc_chr) + { + for(let i_line = 0; i_line < font_height; ++i_line) + { + const line_bits = src_bitmap[i_src++]; + for(let i_bit = 0x80; i_bit > 0; i_bit >>= 1) + { + copy_bit(line_bits & i_bit ? 1 : 0); + } + if(width_9px) + { + copy_bit(font_lge && i_chr >= 0xC0 && i_chr <= 0xDF && line_bits & 1 ? 1 : 0); + } + } + } + } + + return dst_bitmap; +}; + +GraphicalText.prototype.resize_canvas = function() +{ + this.txt_dirty = 1; + this.txt_row_dirty.fill(1); +}; + +GraphicalText.prototype.rebuild_image_data = function() +{ + const gfx_size = this.gfx_width * this.gfx_height * 4; + const gfx_data = new Uint8ClampedArray(gfx_size); + for(let i = 3; i < gfx_size; i += 4) + { + gfx_data[i] = 0xff; + } + this.gfx_data = gfx_data; + this.image_data = new ImageData(this.gfx_data, this.gfx_width, this.gfx_height); + this.resize_canvas(); +}; + +GraphicalText.prototype.mark_blinking_rows_dirty = function() +{ + const vga_memory = this.vga.vga_memory; + const txt_row_dirty = this.txt_row_dirty; + const txt_width = this.txt_width; + const txt_height = this.txt_height; + const txt_row_size = txt_width * 2; + const txt_row_step = Math.max(0, (this.vga.offset_register * 2 - txt_width) * 2); + const split_screen_row = this.vga.scan_line_to_screen_row(this.vga.line_compare); + let row, col, txt_i = this.vga.start_address << 1; + + for(row = 0; row < txt_height; ++row, txt_i += txt_row_step) + { + if(row === split_screen_row) + { + txt_i = 0; + } + + if(txt_row_dirty[row]) + { + txt_i += txt_row_size; + continue; + } + + for(col = 0; col < txt_width; ++col, txt_i += 2) + { + if(vga_memory[txt_i | 1] & 0x80) + { + txt_row_dirty[row] = this.txt_dirty = 1; + txt_i += txt_row_size - col * 2; + break; + } + } + } +}; + +GraphicalText.prototype.render_dirty_rows = function() +{ + const vga = this.vga; + const vga_memory = vga.vga_memory; + const txt_width = this.txt_width; + const txt_height = this.txt_height; + const txt_row_dirty = this.txt_row_dirty; + const gfx_data = this.gfx_data; + const font_bitmap = this.font_bitmap; + const font_size = this.font_width * this.font_height; + const font_A_offset = this.font_index_A * 256; + const font_B_offset = this.font_index_B * 256; + const font_AB_enabled = font_A_offset !== font_B_offset; + const font_blink_enabled = this.font_blink_enabled; + //const blink_visible = this.blink_visible; + const blink_visible = true; + const cursor_visible = this.cursor_enabled && blink_visible; + const cursor_top = this.cursor_top; + const cursor_height = this.cursor_bottom - cursor_top + 1; + + const split_screen_row = vga.scan_line_to_screen_row(vga.line_compare); + const bg_color_mask = font_blink_enabled ? 0x7 : 0xF; + const palette = new Int32Array(16); + for(let i = 0; i < 16; ++i) + { + palette[i] = vga.vga256_palette[vga.dac_mask & vga.dac_map[i]]; + } + + const txt_row_size = txt_width * 2; + const txt_row_step = Math.max(0, (vga.offset_register * 2 - txt_width) * 2); + + const gfx_col_size = this.font_width * 4; // column size in gfx_data (tuple of 4 RGBA items) + const gfx_line_size = this.gfx_width * 4; // line size in gfx_data + const gfx_row_size = gfx_line_size * this.font_height; // row size in gfx_data + const gfx_col_step = (this.font_width - this.font_height * this.gfx_width) * 4; // move from end of current column to start of next in gfx_data + const gfx_line_step = (this.gfx_width - this.font_width) * 4; // move forward to start of column's next line in gfx_data + + // int, current cursor linear position in canvas coordinates (top left of row/col) + const cursor_gfx_i = (this.cursor_row * this.gfx_width * this.font_height + this.cursor_col * this.font_width) * 4; + + let txt_i, chr, chr_attr, chr_bg_rgba, chr_fg_rgba, chr_blinking, chr_font_ofs; + let fg, bg, fg_r=0, fg_g=0, fg_b=0, bg_r=0, bg_g=0, bg_b=0; + let gfx_i, gfx_end_y, gfx_end_x, glyph_i; + let draw_cursor, gfx_ic; + let row, col; + + txt_i = vga.start_address << 1; + + for(row = 0; row < txt_height; ++row, txt_i += txt_row_step) + { + if(row === split_screen_row) + { + txt_i = 0; + } + + if(! txt_row_dirty[row]) + { + txt_i += txt_row_size; + continue; + } + + gfx_i = row * gfx_row_size; + + for(col = 0; col < txt_width; ++col, txt_i += 2, gfx_i += gfx_col_step) + { + chr = vga_memory[txt_i]; + chr_attr = vga_memory[txt_i | 1]; + chr_blinking = font_blink_enabled && chr_attr & 0x80; + chr_font_ofs = font_AB_enabled ? (chr_attr & 0x8 ? font_A_offset : font_B_offset) : font_A_offset; + chr_bg_rgba = palette[chr_attr >> 4 & bg_color_mask]; + chr_fg_rgba = palette[chr_attr & 0xF]; + + if(bg !== chr_bg_rgba) + { + bg = chr_bg_rgba; + bg_r = bg >> 16; + bg_g = (bg >> 8) & 0xff; + bg_b = bg & 0xff; + } + + if(chr_blinking && ! blink_visible) + { + if(fg !== bg) { + fg = bg; + fg_r = bg_r; + fg_g = bg_g; + fg_b = bg_b; + } + } + else if(fg !== chr_fg_rgba) + { + fg = chr_fg_rgba; + fg_r = fg >> 16; + fg_g = (fg >> 8) & 0xff; + fg_b = fg & 0xff; + } + + draw_cursor = cursor_visible && cursor_gfx_i === gfx_i; + + glyph_i = (chr_font_ofs + chr) * font_size; + + gfx_end_y = gfx_i + gfx_row_size; + for(; gfx_i < gfx_end_y; gfx_i += gfx_line_step) + { + gfx_end_x = gfx_i + gfx_col_size; + for(; gfx_i < gfx_end_x; gfx_i += 4) + { + if(font_bitmap[glyph_i++]) + { + gfx_data[gfx_i] = fg_r; + gfx_data[gfx_i+1] = fg_g; + gfx_data[gfx_i+2] = fg_b; + } + else + { + gfx_data[gfx_i] = bg_r; + gfx_data[gfx_i+1] = bg_g; + gfx_data[gfx_i+2] = bg_b; + } + } + } + + if(draw_cursor) + { + gfx_ic = cursor_gfx_i + cursor_top * gfx_line_size; + gfx_end_y = gfx_ic + cursor_height * gfx_line_size; + for(; gfx_ic < gfx_end_y; gfx_ic += gfx_line_step) + { + gfx_end_x = gfx_ic + gfx_col_size; + for(; gfx_ic < gfx_end_x; gfx_ic += 4) + { + gfx_data[gfx_ic] = fg_r; + gfx_data[gfx_ic+1] = fg_g; + gfx_data[gfx_ic+2] = fg_b; + } + } + } + } + } +}; + +// +// Public methods +// + +GraphicalText.prototype.mark_dirty = function() +{ + this.txt_row_dirty.fill(1); + this.txt_dirty = 1; +}; + +GraphicalText.prototype.invalidate_row = function(row) +{ + if(row >= 0 && row < this.txt_height) + { + this.txt_row_dirty[row] = this.txt_dirty = 1; + } +}; + +GraphicalText.prototype.invalidate_font_shape = function() +{ + this.font_data_dirty = true; +}; + +GraphicalText.prototype.set_size = function(rows, cols) +{ + if(rows > 0 && rows < 256 && cols > 0 && cols < 256) + { + this.txt_width = cols; + this.txt_height = rows; + + this.gfx_width = this.txt_width * this.font_width; + this.gfx_height = this.txt_height * this.font_height; + + this.txt_row_dirty = new Uint8Array(this.txt_height); + this.vga.screen.set_size_graphical(this.gfx_width, this.gfx_height, this.gfx_width, this.gfx_height); + this.mark_dirty(); + this.rebuild_image_data(); + } +}; + +GraphicalText.prototype.set_character_map = function(char_map_select) +{ + // bits 2, 3 and 5 (LSB to MSB): VGA font page index of font A + // bits 0, 1 and 4: VGA font page index of font B + // linear_index_map[] maps VGA's non-liner font page index to linear index + const linear_index_map = [0, 2, 4, 6, 1, 3, 5, 7]; + const vga_index_A = ((char_map_select & 0b1100) >> 2) | ((char_map_select & 0b100000) >> 3); + const vga_index_B = (char_map_select & 0b11) | ((char_map_select & 0b10000) >> 2); + const font_index_A = linear_index_map[vga_index_A]; + const font_index_B = linear_index_map[vga_index_B]; + + if(this.font_index_A !== font_index_A || this.font_index_B !== font_index_B) + { + this.font_index_A = font_index_A; + this.font_index_B = font_index_B; + this.mark_dirty(); + } +}; + +GraphicalText.prototype.set_cursor_pos = function(row, col) +{ + this.cursor_pos_dirty = true; + this.cursor_row_latch = row; + this.cursor_col_latch = col; +}; + +GraphicalText.prototype.set_cursor_attr = function(start, end, visible) +{ + this.cursor_attr_dirty = true; + this.cursor_enabled_latch = !! visible; + this.cursor_top_latch = start; + this.cursor_bottom_latch = end; +}; + +GraphicalText.prototype.render = function() +{ + // increment Uint32 frame counter + this.frame_count = (this.frame_count + 1) >>> 0; + + // apply changes to font_width, font_height, font_lge, font_bitmap and font_blink_enabled + const curr_clocking_mode = this.vga.clocking_mode & 0b00001001; + const curr_attribute_mode = this.vga.attribute_mode & 0b00001100; + const curr_max_scan_line = this.vga.max_scan_line & 0b10011111; + if(this.font_data_dirty || + this.vga_clocking_mode !== curr_clocking_mode || + this.vga_attribute_mode !== curr_attribute_mode || + this.vga_max_scan_line !== curr_max_scan_line) + { + const width_9px = ! (curr_clocking_mode & 0x01); + const width_double = !! (curr_clocking_mode & 0x08); + const curr_font_width = (width_9px ? 9 : 8) * (width_double ? 2 : 1); + const curr_font_blink_enabled = !! (curr_attribute_mode & 0b00001000); + const curr_font_lge = !! (curr_attribute_mode & 0b00000100); + const curr_font_height = (curr_max_scan_line & 0b00011111) + 1; + + const font_data_changed = this.font_data_dirty || this.font_lge !== curr_font_lge; + const font_size_changed = this.font_width !== curr_font_width || this.font_height !== curr_font_height; + + this.font_data_dirty = false; + this.font_width = curr_font_width; + this.font_height = curr_font_height; + this.font_blink_enabled = curr_font_blink_enabled; + this.font_lge = curr_font_lge; + + this.vga_clocking_mode = curr_clocking_mode; + this.vga_attribute_mode = curr_attribute_mode; + this.vga_max_scan_line = curr_max_scan_line; + + if(font_data_changed || font_size_changed) + { + if(font_size_changed) + { + this.gfx_width = this.txt_width * this.font_width; + this.gfx_height = this.txt_height * this.font_height; + this.rebuild_image_data(); + } + this.font_bitmap = this.rebuild_font_bitmap(width_9px, width_double); + } + this.mark_dirty(); + } + + // apply changes to cursor position + if(this.cursor_pos_dirty) + { + this.cursor_pos_dirty = false; + this.cursor_row_latch = Math.min(this.cursor_row_latch, this.txt_height-1); + this.cursor_col_latch = Math.min(this.cursor_col_latch, this.txt_width-1); + if(this.cursor_row !== this.cursor_row_latch || this.cursor_col !== this.cursor_col_latch) + { + this.txt_row_dirty[this.cursor_row] = this.txt_row_dirty[this.cursor_row_latch] = this.txt_dirty = 1; + this.cursor_row = this.cursor_row_latch; + this.cursor_col = this.cursor_col_latch; + } + } + + // apply changes to cursor_enabled, cursor_top and cursor_bottom + if(this.cursor_attr_dirty) + { + this.cursor_attr_dirty = false; + if(this.cursor_enabled !== this.cursor_enabled_latch || + this.cursor_top !== this.cursor_top_latch || + this.cursor_bottom !== this.cursor_bottom_latch) + { + this.cursor_enabled = this.cursor_enabled_latch; + this.cursor_top = this.cursor_top_latch; + this.cursor_bottom = this.cursor_bottom_latch; + this.txt_row_dirty[this.cursor_row] = this.txt_dirty = 1; + } + } + + // toggle cursor and blinking character visibility at a frequency of ~3.75hz (every 16th frame at 60fps) + // TODO: make framerate independant + //if(this.frame_count % 16 === 0) + //{ + // this.blink_visible = ! this.blink_visible; + // if(this.font_blink_enabled) + // { + // this.mark_blinking_rows_dirty(); + // } + // if(this.cursor_enabled) + // { + // this.txt_row_dirty[this.cursor_row] = this.txt_dirty = 1; + // } + //} + + // render changed rows + if(this.txt_dirty) + { + this.render_dirty_rows(); + this.txt_dirty = 0; + this.txt_row_dirty.fill(0); + } + + return this.image_data; +}; From a9a538fe899fc8f372bf93ab16793c20ac327436 Mon Sep 17 00:00:00 2001 From: SuperMaxusa <41739128+SuperMaxusa@users.noreply.github.com> Date: Fri, 27 Sep 2024 00:10:00 +0300 Subject: [PATCH 23/23] ci: set 1 hour timeout for "Build and test" job --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2ea9e7988..20d02d3656 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: test: runs-on: ubuntu-latest name: Build and test + timeout-minutes: 60 steps: - name: Checkout repository uses: actions/checkout@v4
- +
- MB
+ MB
-
+
- MB
+ MB
-
+
- +
- +