浏览代码

Merge pull request #218 from lsileoni/main

WIP: Barebones x86emu skeleton
Nariman Jelveh 10 月之前
父节点
当前提交
c23e8931ab

+ 2 - 0
incubator/x86emu/.gitignore

@@ -0,0 +1,2 @@
+*.bin
+*.wasm

+ 22 - 0
incubator/x86emu/LICENSE

@@ -0,0 +1,22 @@
+Copyright (c) 2012, The v86 contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 91 - 0
incubator/x86emu/imagegen/Dockerfile

@@ -0,0 +1,91 @@
+FROM i386/debian:buster
+
+ENV DEBIAN_FRONTEND noninteractive
+
+# Update and install necessary packages
+RUN apt update && \
+    apt --yes --no-install-recommends install \
+        linux-image-686 grub2 systemd \
+        libterm-readline-perl-perl \
+        gcc make libc6-dev \
+        unzip bzip2 xz-utils \
+        fluxbox \
+        i3-wm \
+		cmake \
+		fuse \
+        xorg xserver-xorg-input-kbd xserver-xorg-input-mouse xserver-xorg-input-evdev \
+        xserver-xorg-video-fbdev xserver-xorg-video-vesa \
+        xserver-xorg x11-xserver-utils xinit dbus-x11 \
+        libgdk-pixbuf2.0 libpango-1.0 libpangocairo-1.0 libgtk2.0-bin \
+        libc-l10n locales \
+        fonts-noto fonts-droid-fallback \
+        strace file xterm vim apt-file \
+        dhcpcd5 \
+		ca-certificates \
+		git \
+        wget curl \
+        net-tools netcat \
+        wmctrl xdotool \
+		libssl-dev \
+        mesa-utils libgl1-mesa-dri \
+    && \
+    touch /root/.Xdefaults && \
+    echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
+    locale-gen && \
+    echo 'LANG="en_US.UTF-8"' > /etc/default/locale && \
+    chsh -s /bin/bash && \
+    echo "root:root" | chpasswd && \
+    mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d/ && \
+    systemctl enable serial-getty@ttyS0.service && \
+    rm /lib/systemd/system/getty.target.wants/getty-static.service && \
+    rm /etc/motd /etc/issue && \
+    systemctl disable systemd-timesyncd.service && \
+    systemctl disable apt-daily.timer && \
+    systemctl disable apt-daily-upgrade.timer && \
+    systemctl disable dhcpcd.service && \
+    echo "tmpfs /tmp tmpfs nodev,nosuid 0 0" >> /etc/fstab && \
+    cd /root/ && \
+    wget http://www.math.utah.edu/~mayer/linux/nbench-byte-2.2.3.tar.gz && \
+    tar xfv nbench-byte-2.2.3.tar.gz && \
+    rm nbench-byte-2.2.3.tar.gz && \
+    mv nbench-byte-2.2.3 bench && \
+    cd bench && \
+    make
+
+# Copy necessary configuration files
+COPY getty-noclear.conf getty-override.conf /etc/systemd/system/getty@tty1.service.d/
+COPY getty-autologin-serial.conf /etc/systemd/system/serial-getty@ttyS0.service.d/
+COPY logind.conf /etc/systemd/logind.conf
+# COPY xinitrc /root/.xinitrc
+COPY xorg.conf /etc/X11/
+COPY networking.sh /root/
+COPY boot-9p /etc/initramfs-tools/scripts/boot-9p
+
+# this needs to be commented out in order to boot from hdd
+RUN printf '%s\n' 9p 9pnet 9pnet_virtio virtio virtio_ring virtio_pci | tee -a /etc/initramfs-tools/modules && \
+    echo 'BOOT=boot-9p' | tee -a /etc/initramfs-tools/initramfs.conf && \
+    update-initramfs -u
+
+# Clean up unnecessary files
+RUN apt-get --yes clean && \
+    rm -r /var/lib/apt/lists/* && \
+    rm -r /usr/share/doc/* && \
+    rm -r /usr/share/man/* && \
+    rm -r /usr/share/locale/?? && \
+    rm /var/log/*.log /var/log/lastlog /var/log/wtmp /var/log/apt/*.log /var/log/apt/*.xz
+
+# Install Tailscale
+RUN curl -fsSL https://tailscale.com/install.sh | sh
+RUN chmod +x /root/networking.sh
+
+# Install Go and puter-fuse
+RUN wget https://go.dev/dl/go1.22.1.linux-386.tar.gz && \
+    tar -C /usr/local -xzf go1.22.1.linux-386.tar.gz && \
+    rm go1.22.1.linux-386.tar.gz && \
+	export PATH=$PATH:/usr/local/go/bin && \
+    go install github.com/HeyPuter/puter-fuse@v1.0.0 && \ 
+	mv /root/go/bin/puter-fuse /usr/local/bin && \
+	rm -rf /root/go && \
+	rm -f /go1.22.1.linux-386.tar.gz
+
+RUN echo "kernel.printk = 3 4 1 3" >>/etc/sysctl.conf

+ 5 - 0
incubator/x86emu/imagegen/config/networking.sh

@@ -0,0 +1,5 @@
+rmmod ne2k-pci && modprobe ne2k-pci
+ifconfig enp0s5 192.168.1.5 netmask 255.255.255.0 up
+route add default gw 192.168.1.1
+echo "nameserver 1.1.1.1" > /etc/resolv.conf
+dhcpcd -w4 enp0s5

+ 20 - 0
incubator/x86emu/make_container/Dockerfile

@@ -0,0 +1,20 @@
+FROM library/node:lts-bookworm
+
+WORKDIR /app
+
+RUN apt-get update && \
+    apt-get install -y nodejs nasm gdb unzip p7zip-full openjdk-17-jre wget python3 qemu-system-x86 git git-core build-essential libc6-dev-i386-cross libc6-dev-i386 clang curl time
+
+RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
+RUN /root/.cargo/bin/rustup toolchain install stable && \
+    /root/.cargo/bin/rustup target add wasm32-unknown-unknown && \
+    /root/.cargo/bin/rustup component add rustfmt-preview && \
+    /root/.cargo/bin/rustup update && /root/.cargo/bin/rustup update nightly
+
+RUN git clone https://github.com/copy/v86
+
+WORKDIR /app/v86
+
+ENV PATH="$PATH:/root/.cargo/bin"
+
+RUN make all

+ 208 - 0
incubator/x86emu/sandbox/.clang-format

@@ -0,0 +1,208 @@
+---
+Language:        Cpp
+# BasedOnStyle:  LLVM
+AccessModifierOffset: 0
+AlignAfterOpenBracket: Align
+AlignArrayOfStructures: None
+AlignConsecutiveAssignments:
+  Enabled:         false
+  AcrossEmptyLines: false
+  AcrossComments:  false
+  AlignCompound:   false
+  PadOperators:    true
+AlignConsecutiveBitFields:
+  Enabled:         false
+  AcrossEmptyLines: false
+  AcrossComments:  false
+  AlignCompound:   false
+  PadOperators:    false
+AlignConsecutiveDeclarations:
+  Enabled:         true
+  AcrossEmptyLines: true
+  AcrossComments:  true
+  AlignCompound:   true
+  PadOperators:    true
+AlignConsecutiveMacros:
+  Enabled:         true
+  AcrossEmptyLines: true
+  AcrossComments:  true
+  AlignCompound:   true
+  PadOperators:    true
+AlignEscapedNewlines: Right
+AlignOperands:   Align
+AlignTrailingComments:
+  Kind:            Always
+  OverEmptyLines:  0
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortEnumsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLambdasOnASingleLine: All
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: MultiLine
+AttributeMacros:
+  - __capability
+BinPackArguments: true
+BinPackParameters: true
+BitFieldColonSpacing: Both
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      false
+  AfterControlStatement: Never
+  AfterEnum:       false
+  AfterExternBlock: false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakAfterAttributes: Never
+BreakAfterJavaFieldAnnotations: false
+BreakArrays:     true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: Always
+BreakBeforeBraces: Allman
+BreakBeforeInlineASMColon: OnlyMultiline
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeColon
+BreakInheritanceList: BeforeColon
+BreakStringLiterals: true
+ColumnLimit:     100
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat:   false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: Always
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IfMacros:
+  - KJ_IF_MAYBE
+IncludeBlocks:   Merge
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: true
+IndentCaseBlocks: false
+IndentCaseLabels: true
+IndentExternBlock: AfterExternBlock
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentRequiresClause: true
+IndentWidth:     4
+IndentWrappedFunctionNames: false
+InsertBraces:    false
+InsertNewlineAtEOF: true
+InsertTrailingCommas: None
+IntegerLiteralSeparator:
+  Binary:          0
+  BinaryMinDigits: 0
+  Decimal:         0
+  DecimalMinDigits: 0
+  Hex:             0
+  HexMinDigits:    0
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+LineEnding:      DeriveLF
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: All
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 2
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PackConstructorInitializers: BinPack
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyIndentedWhitespace: 0
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Left
+PPIndentWidth:   1
+QualifierAlignment: Left
+ReferenceAlignment: Pointer
+ReflowComments:  true
+RemoveBracesLLVM: false
+RemoveSemicolon: false
+RequiresClausePosition: OwnLine
+RequiresExpressionIndentation: OuterScope
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes:    Never
+SortJavaStaticImport: Before
+SortUsingDeclarations: LexicographicNumeric
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeParensOptions:
+  AfterControlStatements: true
+  AfterForeachMacros: true
+  AfterFunctionDefinitionName: false
+  AfterFunctionDeclarationName: false
+  AfterIfMacros:   true
+  AfterOverloadedOperator: false
+  AfterRequiresInClause: false
+  AfterRequiresInExpression: false
+  BeforeNonEmptyParentheses: false
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyBlock: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  Never
+SpacesInContainerLiterals: true
+SpacesInLineCommentPrefix:
+  Minimum:         1
+  Maximum:         -1
+SpacesInSquareBrackets: false
+Standard:        Latest
+StatementAttributeLikeMacros:
+  - Q_EMIT
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseTab:          Never
+WhitespaceSensitiveMacros:
+  - BOOST_PP_STRINGIZE
+  - CF_SWIFT_NAME
+  - NS_SWIFT_NAME
+  - PP_STRINGIZE
+  - STRINGIZE
+...
+

+ 110 - 0
incubator/x86emu/sandbox/canva.html

@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Canvas Manipulation</title>
+<style>
+body {
+    margin: 0;
+    padding: 0;
+    font-family: Arial, sans-serif;
+}
+
+#canvasContainer {
+    position: relative;
+    display: block;
+}
+
+#controls {
+    position: fixed;
+    top: 10px;
+    right: 10px;
+    z-index: 999;
+    background-color: #f1f1f1;
+    padding: 10px;
+    border: 1px solid #ccc;
+    border-radius: 5px;
+}
+
+#canvasContainer canvas {
+    border: 1px solid #000;
+    margin-top: 10px;
+    display: block;
+    position: relative;
+}
+</style>
+</head>
+<body>
+<div id="controls">
+    <label for="width">Width:</label>
+    <input type="number" id="width" min="1">
+    <label for="height">Height:</label>
+    <input type="number" id="height" min="1">
+    <button onclick="createCanvas()">Create Canvas</button>
+</div>
+<div id="canvasContainer">
+</div>
+
+<script>
+    let canvasCount = 0;
+
+    function createCanvas() {
+        const width = document.getElementById("width").value;
+        const height = document.getElementById("height").value;
+
+        if (width <= 0 || height <= 0 || width >= 1080 || height >= 1920) {
+            alert("Please enter valid width and height values.");
+            return;
+        }
+
+        const canvasId = `canvas_${canvasCount}`;
+        const canvasContainer = document.getElementById("canvasContainer");
+        const canvas = document.createElement("canvas");
+        canvas.id = canvasId;
+        canvas.width = width;
+        canvas.height = height;
+        canvasContainer.appendChild(canvas);
+
+        const canvasControls = document.createElement("div");
+        canvasControls.classList.add("canvasControls");
+
+        const clearButton = document.createElement("button");
+        clearButton.textContent = "Clear";
+        clearButton.onclick = function() {
+            const ctx = canvas.getContext("2d");
+            ctx.clearRect(0, 0, canvas.width, canvas.height);
+        };
+
+        const pauseButton = document.createElement("button");
+        pauseButton.textContent = "Pause";
+        pauseButton.onclick = function() {
+            const ctx = canvas.getContext("2d");
+            console.log("Pausing v86");
+        };
+        
+        const consoleButton = document.createElement("button");
+        consoleButton.textContent = "Get console";
+        consoleButton.onclick = function() {
+            const ctx = canvas.getContext("2d");
+            console.log("Getting v86 console");
+            ctx.clearRect(0, 0, canvas.width, canvas.height);
+        };
+
+        const deleteButton = document.createElement("button");
+        deleteButton.textContent = "Delete";
+        deleteButton.onclick = function() {
+            canvas.remove();
+            canvasControls.remove();
+        };
+
+        canvasControls.appendChild(clearButton);
+        canvasControls.appendChild(deleteButton);
+        canvasControls.appendChild(pauseButton);
+        canvasControls.appendChild(consoleButton);
+        canvasContainer.appendChild(canvasControls);
+        canvasCount++;
+    }
+</script>
+</body>
+</html>

+ 82 - 0
incubator/x86emu/sandbox/fifotest.cpp

@@ -0,0 +1,82 @@
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+const char* PIPE_NAME = "/tmp/bash_pipe";
+
+int         main()
+{
+    mkfifo(PIPE_NAME, 0666);
+
+    while (true)
+    {
+        int pipe_fd = open(PIPE_NAME, O_RDONLY);
+        if (pipe_fd == -1)
+        {
+            perror("open");
+            exit(EXIT_FAILURE);
+        }
+
+        std::string command;
+        char        buffer[128];
+        ssize_t     bytes_read;
+        while ((bytes_read = read(pipe_fd, buffer, sizeof(buffer))) > 0)
+        {
+            command.append(buffer, bytes_read);
+        }
+        close(pipe_fd);
+
+        int pipe_to_child[2];
+        int pipe_from_child[2];
+
+        if (pipe(pipe_to_child) == -1 || pipe(pipe_from_child) == -1)
+        {
+            std::cerr << "Failed to create pipes\n";
+            exit(EXIT_FAILURE);
+        }
+
+        pid_t pid = fork();
+
+        if (pid < 0)
+        {
+            std::cerr << "Fork failed\n";
+            exit(EXIT_FAILURE);
+        }
+        else if (pid == 0)
+        {
+            close(pipe_to_child[1]);
+            close(pipe_from_child[0]);
+            dup2(pipe_to_child[0], STDIN_FILENO);
+            dup2(pipe_from_child[1], STDOUT_FILENO);
+            execl("/bin/bash", "/bin/bash", "-i", nullptr);
+            perror("exec");
+            exit(EXIT_FAILURE);
+        }
+        else
+        {
+            close(pipe_to_child[0]);
+            close(pipe_from_child[1]);
+            write(pipe_to_child[1], command.c_str(), command.size());
+            close(pipe_to_child[1]);
+            char          output_buffer[128];
+            ssize_t       output_bytes_read;
+            std::ofstream pipe_out(PIPE_NAME, std::ofstream::trunc);
+            while ((output_bytes_read =
+                        read(pipe_from_child[0], output_buffer, sizeof(output_buffer))) > 0)
+            {
+                pipe_out.write(output_buffer, output_bytes_read);
+            }
+            close(pipe_from_child[0]);
+            pipe_out.close();
+        }
+    }
+    unlink(PIPE_NAME);
+
+    return 0;
+}

+ 2 - 0
incubator/x86emu/sandbox/sound.c

@@ -0,0 +1,2 @@
+// Compile and pipe into aplay in emu for sound check
+main(i){for(i=0;;++i)putchar((i+(i>>2)|(i>>8)|(i>>6)));}

+ 64 - 0
incubator/x86emu/v86starter.sh

@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+
+ROOT_DIR="$(pwd)"
+
+# This has to be something, that isn't wrapped by a repo with a package.json
+VM_DIR="/tmp/vm"
+
+# Goes from 1 to 22
+ZSTD_LEVEL=22
+
+build()
+{
+	mkdir -p "$VM_DIR"
+	rm -rf "$VM_DIR/v86"
+	cd "$ROOT_DIR/make_container" || exit
+	docker build . -t v86builder
+	docker run --name v86build v86builder
+	docker cp v86build:/app/v86 "$VM_DIR"
+	docker rm v86build
+
+	# Overwrite v86'd debian Dockerfile with our own
+	cat "$ROOT_DIR/imagegen/Dockerfile" > "$VM_DIR/v86/tools/docker/debian/Dockerfile"
+
+	# Navigate to the Dockerfile directory
+	cd "$VM_DIR/v86/tools/docker/debian" || exit
+
+	# Build the container which will be used to serve v86
+	chmod +x build-container.sh
+	./build-container.sh
+
+	# Build the state of the VM
+	chmod +x build-state.js
+	./build-state.js
+
+	# Compress the generated state image
+	zstd --ultra -$ZSTD_LEVEL < "$VM_DIR/v86/images/debian-state-base.bin" > "$VM_DIR/v86/images/image.bin"
+
+	# Make project directories
+	mkdir -p "$ROOT_DIR/www/third-party"
+	mkdir -p "$ROOT_DIR/www/static"
+
+	# Copy/move necessary files to deployment directory
+	mv "$VM_DIR/v86/images/image.bin" "$ROOT_DIR/www/static/image.bin"
+	cp "$VM_DIR/v86/build/libv86.js" "$ROOT_DIR/www/third-party/libv86.js"
+	cp "$VM_DIR/v86/build/v86.wasm" "$ROOT_DIR/www/third-party/v86.wasm"
+	cp -r "$VM_DIR/v86/images/debian-9p-rootfs-flat/" "$ROOT_DIR/www/static/9p-rootfs/"
+}
+
+if [ ! -d "$VM_DIR/v86" ]
+then
+	build
+else
+	echo "V86 appears to be built"
+	read -p "Do you want to rebuild V86? (yes/no): " rebuild_choice
+	if [ "$rebuild_choice" = "yes" ]; then
+		echo "Rebuilding v86..."
+		build
+	fi
+fi
+
+# Start a HTTP server
+cd "$ROOT_DIR/www/" || exit
+echo "Opening a server on localhost with port 8080"
+python3 -m http.server -b 127.0.0.1 8080

+ 20 - 0
incubator/x86emu/www/index.html

@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+
+<head>
+	<script src="./third-party/libv86.js"></script>
+	<link rel="stylesheet" href="./third-party/xterm.css" />
+	<script src="./third-party/xterm.js"></script>
+	<script type="module" defer>
+		"use strict";
+		import InstanceManager from "./js/InstanceManager.mjs"
+
+		document.addEventListener("DOMContentLoaded", () => {
+			const manager = new InstanceManager({spawnRoot: document.body, term: true});
+			manager.createInstance({spawnRoot: document.body, term: true});
+		});
+	</script>
+</head>
+<body>
+</body>
+</html>

+ 104 - 0
incubator/x86emu/www/js/Instance.mjs

@@ -0,0 +1,104 @@
+import { V86 } from "./V86Wrapper.mjs";
+/**
+ * Class representing an Instance of an emulator machine.
+ */
+class Instance {
+	/**
+	 * Create an Instance.
+	 * @param {Object} options - Options for configuring the instance.
+	 * @param {boolean} [options.term=true] - Terminal option.
+	 * @param {boolean} [options.screen=false] - Screen option.
+	 * @param {number} [options.memory=1024] - Memory size for the instance; must be power of two.
+	 * @param {HTMLElement} [options.spawnRoot=undefined] - Html element where instance should be spawned.
+	 * @param {boolean} [options.autoStart=true] - Whether to automatically start the instance.
+	 * @param {string} [options.remote="./"] - Remote URL, defaults to origin.
+	 * @param {string} [options.wsUrl=""] - Websocket URL option for communication with the outside world.
+	 * @throws Will throw an error if remote URL does not end with a slash. @throws Will throw an error if the amount of memory provided is not a power of two.
+	 */
+	constructor(options) {
+		const defaultOptions = {
+			term: false,
+			screen: false,
+			memory: 1024,
+			spawnRoot: undefined,
+			autoStart: true,
+			remote: "./",
+			instanceName: [...Array(10)].map(() => Math.random().toString(36)[2]).join(''),
+			wsUrl: "",
+		};
+		const instanceOptions = { ...defaultOptions, ...options };
+
+		if (!instanceOptions.remote.endsWith('/'))
+			throw new Error("Instance ctor: Remote URL must end with a slash");
+		if (typeof self !== 'undefined' && self.crypto) {
+			this.instanceID = self.crypto.randomUUID();
+		} else {
+			this.instanceID = "Node";
+		}
+		this.terminals = [];
+		let v86Options = {
+			wasm_path: instanceOptions.remote + "third-party/v86.wasm",
+			preserve_mac_from_state_image: true,
+			memory_size: instanceOptions.memory * 1024 * 1024,
+			vga_memory_size: 8 * 1024 * 1024,
+			initial_state: { url: instanceOptions.remote + "static/image.bin" },
+			filesystem: { baseurl: instanceOptions.remote + "static/9p-rootfs/" },
+			autostart: instanceOptions.autoStart,
+		};
+		if (!(instanceOptions.wsUrl === ""))
+			v86Options.network_relay_url = instanceOptions.wsUrl;
+		if (!((Math.log(v86Options.memory_size) / Math.log(2)) % 1 === 0))
+			throw new Error("Instance ctor: Amount of memory provided isn't a power of two");
+		if (instanceOptions.screen === true) {
+			if (instanceOptions.spawnRoot === undefined)
+				throw new Error("Instance ctor: spawnRoot is undefined, cannot continue")
+			instanceOptions.spawnRoot.appendChild((() => {
+				const div = document.createElement("div");
+				div.setAttribute("id", instanceOptions.instanceName + '-screen');
+
+				const child_div = document.createElement("div");
+				child_div.setAttribute("style", "white-space: pre; font: 14px monospace; line-height: 14px");
+
+				const canvas = document.createElement("canvas");
+				canvas.setAttribute("style", "display: none");
+
+				div.appendChild(child_div);
+				div.appendChild(canvas);
+				return div;
+			})());
+			v86Options.screen_container = document.getElementById(instanceOptions.instanceName + '-screen');
+		}
+		this.vm = new V86(v86Options);
+		if (instanceOptions.term === true) {
+			if (instanceOptions.spawnRoot === undefined)
+				throw new Error("Instance ctor: spawnRoot is undefined, cannot continue")
+			var term = new Terminal({
+				allowTransparency: true,
+			});
+			instanceOptions.spawnRoot.appendChild((() => {
+				const div = document.createElement("div");
+				div.setAttribute("id", instanceOptions.instanceName + '-terminal');
+				return div;
+			})());
+			term.open(document.getElementById(instanceOptions.instanceName + '-terminal'));
+			term.write("Now booting emu, please stand by ...");
+			this.vm.add_listener("emulator-started", () => {
+				// emulator.serial0_send("\nsh networking.sh > /dev/null 2>&1 &\n\n");
+				// emulator.serial0_send("clear\n");
+				term.write("Welcome to psl!");
+				this.vm.serial0_send("\n");
+			});
+			this.vm.add_listener("serial0-output-byte", (byte) => {
+				var chr = String.fromCharCode(byte);
+				if (chr <= "~") {
+					term.write(chr);
+				}
+			});
+			term.onData(data => {
+				this.vm.serial0_send(data);
+			});
+		}
+	}
+}
+
+export default Instance

+ 139 - 0
incubator/x86emu/www/js/InstanceManager.mjs

@@ -0,0 +1,139 @@
+import Instance from "./Instance.mjs"
+
+/**
+ * Class representing the basic interface for managing instances of emulated machines.
+ */
+class InstanceManager {
+	/**
+	 * Create an Instance Manager.
+	 * @param {Object} [options] - Options for configuring the instance manager.
+	 * @param {boolean} [options.screen=true] - Spawn screen option.
+	 * @param {boolean} [options.term=false] - Spawn terminal option.
+	 * @param {string} [options.instanceName="Host"] - Name of the instance.
+	 * @param {number} [options.memory=1024] - Memory size for the instance; must be power of two.
+	 * @param {HTMLElement} [options.spawnRoot=undefined] - Htlm element where instance should be spawned.
+	 * @param {boolean} [options.autoStart=true] - Whether to automatically start the instance.
+	 * @param {string} [options.remote="./"] - Remote URL, defaults to origin.
+	 * @param {string} [options.wsUrl=""] - Websocket URL option.
+	 */
+	constructor(options) {
+		const defaultOptions = {
+			term: false,
+			screen: false,
+			instanceName: "Host",
+			memory: 1024,
+			spawnRoot: undefined,
+			autoStart: true,
+			remote: "./",
+			wsUrl: "",
+		};
+		const instanceOptions = { ...defaultOptions, ...options };
+		this.instances = {};
+		this.instanceNames = [];
+		this.curr_inst = 0;
+		this.instanceNames.push(instanceOptions.instanceName);
+		this.instances[instanceOptions.instanceName] = new Instance(instanceOptions);
+	}
+	/**
+	 * Create an instance with given options and adds it to the pool of instances.
+	 * @param {Object} options - Options for configuring the instance.
+	 * @returns {Promise<Object>} - Resolves with the initialized instance.
+	 */
+	async createInstance(options) {
+		const instance = new Instance(options);
+		this.instanceNames.push(instance.instanceName);
+		this.instances[instance.instanceName] = instance;
+		return instance;
+	}
+	/**
+	 * Continue running a suspended instance.
+	 * @param {string} instName - instName of the instance to continue.
+	 */
+	async continueInstance(instName) {
+		var instance = await this.getInstanceByinstName(instName);
+		if (!instance.vm.cpu_is_running)
+			await instance.vm.run();
+	}
+	/**
+	 * Suspend a running instance.
+	 * @param {string} instName - instName of the instance to suspend.
+	 */
+	async suspendInstance(instName) {
+		var instance = await this.getInstanceByinstName(instName);
+		if (instance.vm.cpu_is_running)
+			await instance.vm.stop();
+	}
+	/**
+	 * Save the state of a running instance.
+	 * @param {string} instName - instName of the instance to save state.
+	 * @returns {Promise} - Promise resolving once state is saved.
+	 */
+	async saveState(instName) {
+		const instance = this.getInstanceByinstName(instName);
+		if (instance.vm.cpu_is_running)
+			return instance.vm.save_state();
+	}
+	/**
+	 * Load the state of a previously saved instance.
+	 * @param {string} instName - instName of the instance to load state.
+	 * @param {any} state - State to load.
+	 * @returns {Promise} - Promise resolving once state is loaded.
+	 */
+	async loadState(instName, state) {
+		const instance = this.getInstanceByinstName(instName);
+		if (instance.vm.cpu_is_running)
+			await instance.vm.save_state(state);
+	}
+	/**
+	 * Connect two instances for communication through NIC's.
+	 * @param {string} destinationinstName - instName of the destination instance.
+	 * @param {string} sourceinstName - instName of the source instance.
+	 */
+	async connectInstances(destinationinstName, sourceinstName) {
+		const destinationInstance = this.getInstanceByinstName(destinationinstName);
+		const sourceInstance = this.getInstanceByinstName(sourceinstName);
+		destinationInstance.add_listener("net0-send", (data) => {
+			source.bus.send("net0-receive", data);
+		});
+		sourceInstance.add_listener("net0-send", (data) => {
+			destination.bus.send("net0-receive", data);
+		});
+	}
+	/**
+	 * Execute a command within an instance.
+	 * @param {Object} inst - Instance object.
+	 * @param {string} cmd - Command to execute.
+	 * @param {Object} env - Environment variables.
+	 * @param {number} [timeout=60] - Timeout for the command execution.
+	 */
+	async exec(inst, cmd, env, timeout = 60) {
+		// TODO: instNamed pipes on the instance would make this super nice so multiple terminals can be had
+	}
+	/**
+	 * Destroy a specific instance.
+	 * @param {string} instName - instName of the instance to destroy.
+	 */
+	async destroyInstance(instName) {
+		await this.getInstanceByinstName(instName).destroy();
+	}
+	/**
+	 * Destroy all instances.
+	 */
+	async destroyInstances() {
+		for (const instance in this.instances)
+			destroyInstance(instance)
+	}
+	/**
+	 * Get an instance by its instName.
+	 * @param {string} instName - instName of the instance.
+	 * @returns {Object} - The instance object.
+	 * @throws Will throw an error if the instance instName is not found.
+	 */
+	async getInstanceByinstName(instName) {
+		if (!(instName in this.instances))
+			throw Error("getInstance: instName not found in instances object");
+		return this.instances[instName];
+	}
+}
+
+export default InstanceManager

+ 16 - 0
incubator/x86emu/www/js/V86Wrapper.mjs

@@ -0,0 +1,16 @@
+let V86;
+
+if (typeof window !== 'undefined') {
+	V86 = window.V86;
+} else {
+	try {
+		const { createRequire } = await import('module');
+		const require = createRequire(import.meta.url);
+		const NodeV86 = require("../third-party/libv86.js");
+		V86 = NodeV86.V86;
+	} catch (error) {
+		console.error('Failed to load V86 in Node.js environment:', error);
+	}
+}
+
+export { V86 };

+ 44 - 0
incubator/x86emu/www/main.js

@@ -0,0 +1,44 @@
+#!/usr/bin/env node
+"use strict";
+
+import('./js/InstanceManager.mjs').then(module => {
+	const InstanceManager = module.default;
+
+	process.stdin.setRawMode(true);
+	process.stdin.resume();
+	process.stdin.setEncoding("utf8");
+
+	console.log("Now booting, please stand by ...");
+
+	const manager = new InstanceManager({ screen: false, term: false, spawnRoot: undefined });
+	manager.getInstanceByinstName("Host").then(result => {
+		const hostvm = result.vm;
+
+		hostvm.add_listener("emulator-started", () => {
+			process.stdout.write("Welcome to psl!");
+			hostvm.serial0_send("\n");
+		});
+
+		hostvm.add_listener("serial0-output-byte", (byte) => {
+			var chr = String.fromCharCode(byte);
+			if (chr <= "~") {
+				process.stdout.write(chr);
+			}
+		});
+
+		process.stdin.on("data", (c) => {
+			if (c === "\u0004") {
+				hostvm.stop();
+				process.stdin.pause();
+			}
+			else {
+				hostvm.serial0_send(c);
+			}
+		});
+	}).catch(error => {
+		console.error(error);
+		throw Error("Error in getting host inastance, quitting");
+	});
+}).catch(error => {
+	console.error('Error loading InstanceManager:', error);
+});

+ 11 - 0
incubator/x86emu/www/package.json

@@ -0,0 +1,11 @@
+{
+  "name": "x86incubator",
+  "version": "1.0.0",
+  "description": "x86emu experimental repo for puter",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "Puter Technologies Inc.",
+  "license": "AGPL-3.0-only"
+}

+ 0 - 0
incubator/x86emu/www/static/.gitkeep


文件差异内容过多而无法显示
+ 564 - 0
incubator/x86emu/www/third-party/libv86.js


+ 1417 - 0
incubator/x86emu/www/third-party/starter.js

@@ -0,0 +1,1417 @@
+"use strict";
+
+/**
+ * Constructor for emulator instances.
+ *
+ * Usage: `var emulator = new V86(options);`
+ *
+ * Options can have the following properties (all optional, default in parenthesis):
+ *
+ * - `memory_size number` (16 * 1024 * 1024) - The memory size in bytes, should
+ *   be a power of 2.
+ * - `vga_memory_size number` (8 * 1024 * 1024) - VGA memory size in bytes.
+ *
+ * - `autostart boolean` (false) - If emulation should be started when emulator
+ *   is ready.
+ *
+ * - `disable_keyboard boolean` (false) - If the keyboard should be disabled.
+ * - `disable_mouse boolean` (false) - If the mouse should be disabled.
+ *
+ * - `network_relay_url string` (No network card) - The url of a server running
+ *   websockproxy. See [networking.md](networking.md). Setting this will
+ *   enable an emulated network card.
+ *
+ * - `bios Object` (No bios) - Either a url pointing to a bios or an
+ *   ArrayBuffer, see below.
+ * - `vga_bios Object` (No VGA bios) - VGA bios, see below.
+ * - `hda Object` (No hard disk) - First hard disk, see below.
+ * - `fda Object` (No floppy disk) - First floppy disk, see below.
+ * - `cdrom Object` (No CD) - See below.
+ *
+ * - `bzimage Object` - A Linux kernel image to boot (only bzimage format), see below.
+ * - `initrd Object` - A Linux ramdisk image, see below.
+ * - `bzimage_initrd_from_filesystem boolean` - Automatically fetch bzimage and
+ *    initrd from the specified `filesystem`.
+ *
+ * - `initial_state Object` (Normal boot) - An initial state to load, see
+ *   [`restore_state`](#restore_statearraybuffer-state) and below.
+ *
+ * - `filesystem Object` (No 9p filesystem) - A 9p filesystem, see
+ *   [filesystem.md](filesystem.md).
+ *
+ * - `serial_container HTMLTextAreaElement` (No serial terminal) - A textarea
+ *   that will receive and send data to the emulated serial terminal.
+ *   Alternatively the serial terminal can also be accessed programatically,
+ *   see [serial.html](../examples/serial.html).
+ *
+ * - `screen_container HTMLElement` (No screen) - An HTMLElement. This should
+ *   have a certain structure, see [basic.html](../examples/basic.html).
+ *
+ * ***
+ *
+ * There are two ways to load images (`bios`, `vga_bios`, `cdrom`, `hda`, ...):
+ *
+ * - Pass an object that has a url. Optionally, `async: true` and `size:
+ *   size_in_bytes` can be added to the object, so that sectors of the image
+ *   are loaded on demand instead of being loaded before boot (slower, but
+ *   strongly recommended for big files). In that case, the `Range: bytes=...`
+ *   header must be supported on the server.
+ *
+ *   ```javascript
+ *   // download file before boot
+ *   bios: {
+ *       url: "bios/seabios.bin"
+ *   }
+ *   // download file sectors as requested, size is required
+ *   hda: {
+ *       url: "disk/linux.iso",
+ *       async: true,
+ *       size: 16 * 1024 * 1024
+ *   }
+ *   ```
+ *
+ * - Pass an `ArrayBuffer` or `File` object as `buffer` property.
+ *
+ *   ```javascript
+ *   // use <input type=file>
+ *   bios: {
+ *       buffer: document.all.hd_image.files[0]
+ *   }
+ *   // start with empty hard disk
+ *   hda: {
+ *       buffer: new ArrayBuffer(16 * 1024 * 1024)
+ *   }
+ *   ```
+ *
+ * @param {{
+      disable_mouse: (boolean|undefined),
+      disable_keyboard: (boolean|undefined),
+      wasm_fn: (Function|undefined),
+    }} options
+ * @constructor
+ */
+function V86(options)
+{
+    //var worker = new Worker("src/browser/worker.js");
+    //var adapter_bus = this.bus = WorkerBus.init(worker);
+
+    this.cpu_is_running = false;
+    this.cpu_exception_hook = function(n) {};
+
+    const bus = Bus.create();
+    const adapter_bus = this.bus = bus[0];
+    this.emulator_bus = bus[1];
+
+    var cpu;
+    var wasm_memory;
+
+    const wasm_table = new WebAssembly.Table({ element: "anyfunc", initial: WASM_TABLE_SIZE + WASM_TABLE_OFFSET });
+
+    const wasm_shared_funcs = {
+        "cpu_exception_hook": n => this.cpu_exception_hook(n),
+        "run_hardware_timers": function(a, t) { return cpu.run_hardware_timers(a, t); },
+        "cpu_event_halt": () => { this.emulator_bus.send("cpu-event-halt"); },
+        "abort": function() { dbg_assert(false); },
+        "microtick": v86.microtick,
+        "get_rand_int": function() { return v86util.get_rand_int(); },
+        "apic_acknowledge_irq": function() { return cpu.devices.apic.acknowledge_irq(); },
+
+        "io_port_read8": function(addr) { return cpu.io.port_read8(addr); },
+        "io_port_read16": function(addr) { return cpu.io.port_read16(addr); },
+        "io_port_read32": function(addr) { return cpu.io.port_read32(addr); },
+        "io_port_write8": function(addr, value) { cpu.io.port_write8(addr, value); },
+        "io_port_write16": function(addr, value) { cpu.io.port_write16(addr, value); },
+        "io_port_write32": function(addr, value) { cpu.io.port_write32(addr, value); },
+
+        "mmap_read8": function(addr) { return cpu.mmap_read8(addr); },
+        "mmap_read16": function(addr) { return cpu.mmap_read16(addr); },
+        "mmap_read32": function(addr) { return cpu.mmap_read32(addr); },
+        "mmap_write8": function(addr, value) { cpu.mmap_write8(addr, value); },
+        "mmap_write16": function(addr, value) { cpu.mmap_write16(addr, value); },
+        "mmap_write32": function(addr, value) { cpu.mmap_write32(addr, value); },
+        "mmap_write64": function(addr, value0, value1) { cpu.mmap_write64(addr, value0, value1); },
+        "mmap_write128": function(addr, value0, value1, value2, value3) {
+            cpu.mmap_write128(addr, value0, value1, value2, value3);
+        },
+
+        "log_from_wasm": function(offset, len) {
+            const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
+            dbg_log(str, LOG_CPU);
+        },
+        "console_log_from_wasm": function(offset, len) {
+            const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
+            console.error(str);
+        },
+        "dbg_trace_from_wasm": function() {
+            dbg_trace(LOG_CPU);
+        },
+
+        "codegen_finalize": (wasm_table_index, start, state_flags, ptr, len) => {
+            cpu.codegen_finalize(wasm_table_index, start, state_flags, ptr, len);
+        },
+        "jit_clear_func": (wasm_table_index) => cpu.jit_clear_func(wasm_table_index),
+        "jit_clear_all_funcs": () => cpu.jit_clear_all_funcs(),
+
+        "__indirect_function_table": wasm_table,
+    };
+
+    let wasm_fn = options.wasm_fn;
+
+    if(!wasm_fn)
+    {
+        wasm_fn = env =>
+        {
+            return new Promise(resolve => {
+                let v86_bin = DEBUG ? "v86-debug.wasm" : "v86.wasm";
+                let v86_bin_fallback = "v86-fallback.wasm";
+
+                if(options.wasm_path)
+                {
+                    v86_bin = options.wasm_path;
+                    const slash = v86_bin.lastIndexOf("/");
+                    const dir = slash === -1 ? "" : v86_bin.substr(0, slash);
+                    v86_bin_fallback = dir + "/" + v86_bin_fallback;
+                }
+                else if(typeof window === "undefined" && typeof __dirname === "string")
+                {
+                    v86_bin = __dirname + "/" + v86_bin;
+                    v86_bin_fallback = __dirname + "/" + v86_bin_fallback;
+                }
+                else
+                {
+                    v86_bin = "build/" + v86_bin;
+                    v86_bin_fallback = "build/" + v86_bin_fallback;
+                }
+
+                v86util.load_file(v86_bin, {
+                    done: async bytes =>
+                    {
+                        try
+                        {
+                            const { instance } = await WebAssembly.instantiate(bytes, env);
+                            this.wasm_source = bytes;
+                            resolve(instance.exports);
+                        }
+                        catch(err)
+                        {
+                            v86util.load_file(v86_bin_fallback, {
+                                    done: async bytes => {
+                                        const { instance } = await WebAssembly.instantiate(bytes, env);
+                                        this.wasm_source = bytes;
+                                        resolve(instance.exports);
+                                    },
+                                });
+                        }
+                    },
+                    progress: e =>
+                    {
+                        this.emulator_bus.send("download-progress", {
+                            file_index: 0,
+                            file_count: 1,
+                            file_name: v86_bin,
+
+                            lengthComputable: e.lengthComputable,
+                            total: e.total,
+                            loaded: e.loaded,
+                        });
+                    }
+                });
+            });
+        };
+    }
+
+    wasm_fn({ "env": wasm_shared_funcs })
+        .then((exports) => {
+            wasm_memory = exports.memory;
+            exports["rust_init"]();
+
+            const emulator = this.v86 = new v86(this.emulator_bus, { exports, wasm_table });
+            cpu = emulator.cpu;
+
+            this.continue_init(emulator, options);
+        });
+
+    this.zstd_worker = null;
+    this.zstd_worker_request_id = 0;
+}
+
+V86.prototype.continue_init = async function(emulator, options)
+{
+    this.bus.register("emulator-stopped", function()
+    {
+        this.cpu_is_running = false;
+    }, this);
+
+    this.bus.register("emulator-started", function()
+    {
+        this.cpu_is_running = true;
+    }, this);
+
+    var settings = {};
+
+    this.disk_images = {
+        fda: undefined,
+        fdb: undefined,
+        hda: undefined,
+        hdb: undefined,
+        cdrom: undefined,
+    };
+
+    const boot_order =
+        options.boot_order ? options.boot_order :
+        options.fda ? BOOT_ORDER_FD_FIRST :
+        options.hda ? BOOT_ORDER_HD_FIRST : BOOT_ORDER_CD_FIRST;
+
+    settings.acpi = options.acpi;
+    settings.disable_jit = options.disable_jit;
+    settings.load_devices = true;
+    settings.log_level = options.log_level;
+    settings.memory_size = options.memory_size || 64 * 1024 * 1024;
+    settings.vga_memory_size = options.vga_memory_size || 8 * 1024 * 1024;
+    settings.boot_order = boot_order;
+    settings.fastboot = options.fastboot || false;
+    settings.fda = undefined;
+    settings.fdb = undefined;
+    settings.uart1 = options.uart1;
+    settings.uart2 = options.uart2;
+    settings.uart3 = options.uart3;
+    settings.cmdline = options.cmdline;
+    settings.preserve_mac_from_state_image = options.preserve_mac_from_state_image;
+    settings.mac_address_translation = options.mac_address_translation;
+    settings.cpuid_level = options.cpuid_level;
+    settings.virtio_console = options.virtio_console;
+
+    if(options.network_adapter)
+    {
+        this.network_adapter = options.network_adapter(this.bus);
+    }
+    else if(options.network_relay_url)
+    {
+        this.network_adapter = new NetworkAdapter(options.network_relay_url, this.bus);
+    }
+
+    // Enable unconditionally, so that state images don't miss hardware
+    // TODO: Should be properly fixed in restore_state
+    settings.enable_ne2k = true;
+
+    if(!options.disable_keyboard)
+    {
+        this.keyboard_adapter = new KeyboardAdapter(this.bus);
+    }
+    if(!options.disable_mouse)
+    {
+        this.mouse_adapter = new MouseAdapter(this.bus, options.screen_container);
+    }
+
+    if(options.screen_container)
+    {
+        this.screen_adapter = new ScreenAdapter(options.screen_container, this.bus);
+    }
+    else if(options.screen_dummy)
+    {
+        this.screen_adapter = new DummyScreenAdapter(this.bus);
+    }
+
+    if(options.serial_container)
+    {
+        this.serial_adapter = new SerialAdapter(options.serial_container, this.bus);
+        //this.recording_adapter = new SerialRecordingAdapter(this.bus);
+    }
+
+    if(options.serial_container_xtermjs)
+    {
+        this.serial_adapter = new SerialAdapterXtermJS(options.serial_container_xtermjs, this.bus);
+    }
+
+    if(!options.disable_speaker)
+    {
+        this.speaker_adapter = new SpeakerAdapter(this.bus);
+    }
+
+    // ugly, but required for closure compiler compilation
+    function put_on_settings(name, buffer)
+    {
+        switch(name)
+        {
+            case "hda":
+                settings.hda = this.disk_images.hda = buffer;
+                break;
+            case "hdb":
+                settings.hdb = this.disk_images.hdb = buffer;
+                break;
+            case "cdrom":
+                settings.cdrom = this.disk_images.cdrom = buffer;
+                break;
+            case "fda":
+                settings.fda = this.disk_images.fda = buffer;
+                break;
+            case "fdb":
+                settings.fdb = this.disk_images.fdb = buffer;
+                break;
+
+            case "multiboot":
+                settings.multiboot = this.disk_images.multiboot = buffer.buffer;
+                break;
+            case "bzimage":
+                settings.bzimage = this.disk_images.bzimage = buffer.buffer;
+                break;
+            case "initrd":
+                settings.initrd = this.disk_images.initrd = buffer.buffer;
+                break;
+
+            case "bios":
+                settings.bios = buffer.buffer;
+                break;
+            case "vga_bios":
+                settings.vga_bios = buffer.buffer;
+                break;
+            case "initial_state":
+                settings.initial_state = buffer.buffer;
+                break;
+            case "fs9p_json":
+                settings.fs9p_json = buffer;
+                break;
+            default:
+                dbg_assert(false, name);
+        }
+    }
+
+    var files_to_load = [];
+
+    const add_file = (name, file) =>
+    {
+        if(!file)
+        {
+            return;
+        }
+
+        if(file.get && file.set && file.load)
+        {
+            files_to_load.push({
+                name: name,
+                loadable: file,
+            });
+            return;
+        }
+
+        if(name === "bios" || name === "vga_bios" ||
+            name === "initial_state" || name === "multiboot" ||
+            name === "bzimage" || name === "initrd")
+        {
+            // Ignore async for these because they must be available before boot.
+            // This should make result.buffer available after the object is loaded
+            file.async = false;
+        }
+
+        if(file.url && !file.async)
+        {
+            files_to_load.push({
+                name: name,
+                url: file.url,
+                size: file.size,
+            });
+        }
+        else
+        {
+            files_to_load.push({
+                name,
+                loadable: v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this)),
+            });
+        }
+    };
+
+    if(options.state)
+    {
+        console.warn("Warning: Unknown option 'state'. Did you mean 'initial_state'?");
+    }
+
+    add_file("bios", options.bios);
+    add_file("vga_bios", options.vga_bios);
+    add_file("cdrom", options.cdrom);
+    add_file("hda", options.hda);
+    add_file("hdb", options.hdb);
+    add_file("fda", options.fda);
+    add_file("fdb", options.fdb);
+    add_file("initial_state", options.initial_state);
+    add_file("multiboot", options.multiboot);
+    add_file("bzimage", options.bzimage);
+    add_file("initrd", options.initrd);
+
+    if(options.filesystem)
+    {
+        var fs_url = options.filesystem.basefs;
+        var base_url = options.filesystem.baseurl;
+
+        let file_storage = new MemoryFileStorage();
+
+        if(base_url)
+        {
+            file_storage = new ServerFileStorageWrapper(file_storage, base_url);
+        }
+        settings.fs9p = this.fs9p = new FS(file_storage);
+
+        if(fs_url)
+        {
+            dbg_assert(base_url, "Filesystem: baseurl must be specified");
+
+            var size;
+
+            if(typeof fs_url === "object")
+            {
+                size = fs_url.size;
+                fs_url = fs_url.url;
+            }
+            dbg_assert(typeof fs_url === "string");
+
+            files_to_load.push({
+                name: "fs9p_json",
+                url: fs_url,
+                size: size,
+                as_json: true,
+            });
+        }
+    }
+
+    var starter = this;
+    var total = files_to_load.length;
+
+    var cont = function(index)
+    {
+        if(index === total)
+        {
+            setTimeout(done.bind(this), 0);
+            return;
+        }
+
+        var f = files_to_load[index];
+
+        if(f.loadable)
+        {
+            f.loadable.onload = function(e)
+            {
+                put_on_settings.call(this, f.name, f.loadable);
+                cont(index + 1);
+            }.bind(this);
+            f.loadable.load();
+        }
+        else
+        {
+            v86util.load_file(f.url, {
+                done: function(result)
+                {
+                    if(f.url.endsWith(".zst") && f.name !== "initial_state")
+                    {
+                        dbg_assert(f.size, "A size must be provided for compressed images");
+                        result = this.zstd_decompress(f.size, new Uint8Array(result));
+                    }
+
+                    put_on_settings.call(this, f.name, f.as_json ? result : new v86util.SyncBuffer(result));
+                    cont(index + 1);
+                }.bind(this),
+                progress: function progress(e)
+                {
+                    if(e.target.status === 200)
+                    {
+                        starter.emulator_bus.send("download-progress", {
+                            file_index: index,
+                            file_count: total,
+                            file_name: f.url,
+
+                            lengthComputable: e.lengthComputable,
+                            total: e.total || f.size,
+                            loaded: e.loaded,
+                        });
+                    }
+                    else
+                    {
+                        starter.emulator_bus.send("download-error", {
+                            file_index: index,
+                            file_count: total,
+                            file_name: f.url,
+                            request: e.target,
+                        });
+                    }
+                },
+                as_json: f.as_json,
+            });
+        }
+    }.bind(this);
+    cont(0);
+
+    async function done()
+    {
+        //if(settings.initial_state)
+        //{
+        //    // avoid large allocation now, memory will be restored later anyway
+        //    settings.memory_size = 0;
+        //}
+
+        if(settings.fs9p && settings.fs9p_json)
+        {
+            if(!settings.initial_state)
+            {
+                settings.fs9p.load_from_json(settings.fs9p_json);
+            }
+            else
+            {
+                dbg_log("Filesystem basefs ignored: Overridden by state image");
+            }
+
+            if(options.bzimage_initrd_from_filesystem)
+            {
+                const { bzimage_path, initrd_path } = this.get_bzimage_initrd_from_filesystem(settings.fs9p);
+
+                dbg_log("Found bzimage: " + bzimage_path + " and initrd: " + initrd_path);
+
+                const [initrd, bzimage] = await Promise.all([
+                    settings.fs9p.read_file(initrd_path),
+                    settings.fs9p.read_file(bzimage_path),
+                ]);
+                put_on_settings.call(this, "initrd", new v86util.SyncBuffer(initrd.buffer));
+                put_on_settings.call(this, "bzimage", new v86util.SyncBuffer(bzimage.buffer));
+                finish.call(this);
+            }
+            else
+            {
+                finish.call(this);
+            }
+        }
+        else
+        {
+            dbg_assert(
+                !options.bzimage_initrd_from_filesystem,
+                "bzimage_initrd_from_filesystem: Requires a filesystem");
+            finish.call(this);
+        }
+
+        function finish()
+        {
+            this.serial_adapter && this.serial_adapter.show && this.serial_adapter.show();
+
+            this.bus.send("cpu-init", settings);
+
+            if(settings.initial_state)
+            {
+                emulator.restore_state(settings.initial_state);
+
+                // The GC can't free settings, since it is referenced from
+                // several closures. This isn't needed anymore, so we delete it
+                // here
+                settings.initial_state = undefined;
+            }
+
+            if(options.autostart)
+            {
+                this.bus.send("cpu-run");
+            }
+
+            this.emulator_bus.send("emulator-loaded");
+        }
+    }
+};
+
+/**
+ * @param {number} decompressed_size
+ * @param {Uint8Array} src
+ * @return {ArrayBuffer}
+ */
+V86.prototype.zstd_decompress = function(decompressed_size, src)
+{
+    const cpu = this.v86.cpu;
+
+    dbg_assert(!this.zstd_context);
+    this.zstd_context = cpu.zstd_create_ctx(src.length);
+
+    new Uint8Array(cpu.wasm_memory.buffer).set(src, cpu.zstd_get_src_ptr(this.zstd_context));
+
+    const ptr = cpu.zstd_read(this.zstd_context, decompressed_size);
+    const result = cpu.wasm_memory.buffer.slice(ptr, ptr + decompressed_size);
+    cpu.zstd_read_free(ptr, decompressed_size);
+
+    cpu.zstd_free_ctx(this.zstd_context);
+    this.zstd_context = null;
+
+    return result;
+};
+
+/**
+ * @param {number} decompressed_size
+ * @param {Uint8Array} src
+ * @return {Promise<ArrayBuffer>}
+ */
+V86.prototype.zstd_decompress_worker = async function(decompressed_size, src)
+{
+    if(!this.zstd_worker)
+    {
+        function the_worker()
+        {
+            let wasm;
+
+            globalThis.onmessage = function(e)
+            {
+                if(!wasm)
+                {
+                    const env = Object.fromEntries([
+                        "cpu_exception_hook", "run_hardware_timers",
+                        "cpu_event_halt", "microtick", "get_rand_int",
+                        "apic_acknowledge_irq",
+                        "io_port_read8", "io_port_read16", "io_port_read32",
+                        "io_port_write8", "io_port_write16", "io_port_write32",
+                        "mmap_read8", "mmap_read16", "mmap_read32",
+                        "mmap_write8", "mmap_write16", "mmap_write32", "mmap_write64", "mmap_write128",
+                        "codegen_finalize",
+                        "jit_clear_func", "jit_clear_all_funcs",
+                    ].map(f => [f, () => console.error("zstd worker unexpectedly called " + f)]));
+
+                    env["__indirect_function_table"] = new WebAssembly.Table({ element: "anyfunc", initial: 1024 });
+                    env["abort"] = () => { throw new Error("zstd worker aborted"); };
+                    env["log_from_wasm"] = env["console_log_from_wasm"] = (off, len) => {
+                        console.log(String.fromCharCode(...new Uint8Array(wasm.exports.memory.buffer, off, len)));
+                    };
+                    env["dbg_trace_from_wasm"] = () => console.trace();
+
+                    wasm = new WebAssembly.Instance(new WebAssembly.Module(e.data), { "env": env });
+                    return;
+                }
+
+                const { src, decompressed_size, id } = e.data;
+                const exports = wasm.exports;
+
+                const zstd_context = exports["zstd_create_ctx"](src.length);
+                new Uint8Array(exports.memory.buffer).set(src, exports["zstd_get_src_ptr"](zstd_context));
+
+                const ptr = exports["zstd_read"](zstd_context, decompressed_size);
+                const result = exports.memory.buffer.slice(ptr, ptr + decompressed_size);
+                exports["zstd_read_free"](ptr, decompressed_size);
+
+                exports["zstd_free_ctx"](zstd_context);
+
+                postMessage({ result, id }, [result]);
+            };
+        }
+
+        const url = URL.createObjectURL(new Blob(["(" + the_worker.toString() + ")()"], { type: "text/javascript" }));
+        this.zstd_worker = new Worker(url);
+        URL.revokeObjectURL(url);
+        this.zstd_worker.postMessage(this.wasm_source, [this.wasm_source]);
+    }
+
+    return new Promise(resolve => {
+        const id = this.zstd_worker_request_id++;
+        const done = async e =>
+        {
+            if(e.data.id === id)
+            {
+                this.zstd_worker.removeEventListener("message", done);
+                dbg_assert(decompressed_size === e.data.result.byteLength);
+                resolve(e.data.result);
+            }
+        };
+        this.zstd_worker.addEventListener("message", done);
+        this.zstd_worker.postMessage({ src, decompressed_size, id }, [src.buffer]);
+    });
+};
+
+V86.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)
+{
+    const root = (filesystem.read_dir("/") || []).map(x => "/" + x);
+    const boot = (filesystem.read_dir("/boot/") || []).map(x => "/boot/" + x);
+
+    let initrd_path;
+    let bzimage_path;
+
+    for(let f of [].concat(root, boot))
+    {
+        const old = /old/i.test(f) || /fallback/i.test(f);
+        const is_bzimage = /vmlinuz/i.test(f) || /bzimage/i.test(f);
+        const is_initrd = /initrd/i.test(f) || /initramfs/i.test(f);
+
+        if(is_bzimage && (!bzimage_path || !old))
+        {
+            bzimage_path = f;
+        }
+
+        if(is_initrd && (!initrd_path || !old))
+        {
+            initrd_path = f;
+        }
+    }
+
+    if(!initrd_path || !bzimage_path)
+    {
+        console.log("Failed to find bzimage or initrd in filesystem. Files:");
+        console.log(root.join(" "));
+        console.log(boot.join(" "));
+    }
+
+    return { initrd_path, bzimage_path };
+};
+
+/**
+ * Start emulation. Do nothing if emulator is running already. Can be
+ * asynchronous.
+ * @export
+ */
+V86.prototype.run = async function()
+{
+    this.bus.send("cpu-run");
+};
+
+/**
+ * Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
+ * @export
+ */
+V86.prototype.stop = async function()
+{
+    if(!this.cpu_is_running)
+    {
+        return;
+    }
+
+    await new Promise(resolve => {
+        const listener = () => {
+            this.remove_listener("emulator-stopped", listener);
+            resolve();
+        };
+        this.add_listener("emulator-stopped", listener);
+        this.bus.send("cpu-stop");
+    });
+};
+
+/**
+ * @ignore
+ * @export
+ */
+V86.prototype.destroy = async function()
+{
+    await this.stop();
+
+    this.v86.destroy();
+    this.keyboard_adapter && this.keyboard_adapter.destroy();
+    this.network_adapter && this.network_adapter.destroy();
+    this.mouse_adapter && this.mouse_adapter.destroy();
+    this.screen_adapter && this.screen_adapter.destroy();
+    this.serial_adapter && this.serial_adapter.destroy()
+    this.speaker_adapter && this.speaker_adapter.destroy();
+};
+
+/**
+ * Restart (force a reboot).
+ * @export
+ */
+V86.prototype.restart = function()
+{
+    this.bus.send("cpu-restart");
+};
+
+/**
+ * Add an event listener (the emulator is an event emitter). A list of events
+ * can be found at [events.md](events.md).
+ *
+ * The callback function gets a single argument which depends on the event.
+ *
+ * @param {string} event Name of the event.
+ * @param {function(*)} listener The callback function.
+ * @export
+ */
+V86.prototype.add_listener = function(event, listener)
+{
+    this.bus.register(event, listener, this);
+};
+
+/**
+ * Remove an event listener.
+ *
+ * @param {string} event
+ * @param {function(*)} listener
+ * @export
+ */
+V86.prototype.remove_listener = function(event, listener)
+{
+    this.bus.unregister(event, listener);
+};
+
+/**
+ * Restore the emulator state from the given state, which must be an
+ * ArrayBuffer returned by
+ * [`save_state`](#save_statefunctionobject-arraybuffer-callback).
+ *
+ * Note that the state can only be restored correctly if this constructor has
+ * been created with the same options as the original instance (e.g., same disk
+ * images, memory size, etc.).
+ *
+ * Different versions of the emulator might use a different format for the
+ * state buffer.
+ *
+ * @param {ArrayBuffer} state
+ * @export
+ */
+V86.prototype.restore_state = async function(state)
+{
+    dbg_assert(arguments.length === 1);
+    this.v86.restore_state(state);
+};
+
+/**
+ * Asynchronously save the current state of the emulator.
+ *
+ * @return {Promise<ArrayBuffer>}
+ * @export
+ */
+V86.prototype.save_state = async function()
+{
+    dbg_assert(arguments.length === 0);
+    return this.v86.save_state();
+};
+
+/**
+ * @return {number}
+ * @ignore
+ * @export
+ */
+V86.prototype.get_instruction_counter = function()
+{
+    if(this.v86)
+    {
+        return this.v86.cpu.instruction_counter[0] >>> 0;
+    }
+    else
+    {
+        // TODO: Should be handled using events
+        return 0;
+    }
+};
+
+/**
+ * @return {boolean}
+ * @export
+ */
+V86.prototype.is_running = function()
+{
+    return this.cpu_is_running;
+};
+
+/**
+ * Set the image inserted in the floppy drive. Can be changed at runtime, as
+ * when physically changing the floppy disk.
+ * @export
+ */
+V86.prototype.set_fda = async function(file)
+{
+    if(file.url && !file.async)
+    {
+        v86util.load_file(file.url, {
+            done: result =>
+            {
+                this.v86.cpu.devices.fdc.set_fda(new v86util.SyncBuffer(result));
+            },
+        });
+    }
+    else
+    {
+        const image = v86util.buffer_from_object(file, this.zstd_decompress_worker.bind(this));
+        image.onload = () =>
+        {
+            this.v86.cpu.devices.fdc.set_fda(image);
+        };
+        await image.load();
+    }
+};
+
+/**
+ * Eject the floppy drive.
+ * @export
+ */
+V86.prototype.eject_fda = function()
+{
+    this.v86.cpu.devices.fdc.eject_fda();
+};
+
+/**
+ * Send a sequence of scan codes to the emulated PS2 controller. A list of
+ * codes can be found at http://stanislavs.org/helppc/make_codes.html.
+ * Do nothing if there is no keyboard controller.
+ *
+ * @param {Array.<number>} codes
+ * @export
+ */
+V86.prototype.keyboard_send_scancodes = function(codes)
+{
+    for(var i = 0; i < codes.length; i++)
+    {
+        this.bus.send("keyboard-code", codes[i]);
+    }
+};
+
+/**
+ * Send translated keys
+ * @ignore
+ * @export
+ */
+V86.prototype.keyboard_send_keys = function(codes)
+{
+    for(var i = 0; i < codes.length; i++)
+    {
+        this.keyboard_adapter.simulate_press(codes[i]);
+    }
+};
+
+/**
+ * Send text
+ * @ignore
+ * @export
+ */
+V86.prototype.keyboard_send_text = function(string)
+{
+    for(var i = 0; i < string.length; i++)
+    {
+        this.keyboard_adapter.simulate_char(string[i]);
+    }
+};
+
+/**
+ * Download a screenshot.
+ *
+ * @ignore
+ * @export
+ */
+V86.prototype.screen_make_screenshot = function()
+{
+    if(this.screen_adapter)
+    {
+        return this.screen_adapter.make_screenshot();
+    }
+    return null;
+};
+
+/**
+ * Set the scaling level of the emulated screen.
+ *
+ * @param {number} sx
+ * @param {number} sy
+ *
+ * @ignore
+ * @export
+ */
+V86.prototype.screen_set_scale = function(sx, sy)
+{
+    if(this.screen_adapter)
+    {
+        this.screen_adapter.set_scale(sx, sy);
+    }
+};
+
+/**
+ * Go fullscreen.
+ *
+ * @ignore
+ * @export
+ */
+V86.prototype.screen_go_fullscreen = function()
+{
+    if(!this.screen_adapter)
+    {
+        return;
+    }
+
+    var elem = document.getElementById("screen_container");
+
+    if(!elem)
+    {
+        return;
+    }
+
+    // bracket notation because otherwise they get renamed by closure compiler
+    var fn = elem["requestFullScreen"] ||
+            elem["webkitRequestFullscreen"] ||
+            elem["mozRequestFullScreen"] ||
+            elem["msRequestFullScreen"];
+
+    if(fn)
+    {
+        fn.call(elem);
+
+        // This is necessary, because otherwise chromium keyboard doesn't work anymore.
+        // Might (but doesn't seem to) break something else
+        var focus_element = document.getElementsByClassName("phone_keyboard")[0];
+        focus_element && focus_element.focus();
+    }
+
+    try {
+        navigator.keyboard.lock();
+    } catch(e) {}
+
+    this.lock_mouse();
+};
+
+/**
+ * Lock the mouse cursor: It becomes invisble and is not moved out of the
+ * browser window.
+ *
+ * @ignore
+ * @export
+ */
+V86.prototype.lock_mouse = function()
+{
+    var elem = document.body;
+
+    var fn = elem["requestPointerLock"] ||
+                elem["mozRequestPointerLock"] ||
+                elem["webkitRequestPointerLock"];
+
+    if(fn)
+    {
+        fn.call(elem);
+    }
+};
+
+/**
+ * Enable or disable sending mouse events to the emulated PS2 controller.
+ *
+ * @param {boolean} enabled
+ */
+V86.prototype.mouse_set_status = function(enabled)
+{
+    if(this.mouse_adapter)
+    {
+        this.mouse_adapter.emu_enabled = enabled;
+    }
+};
+
+/**
+ * Enable or disable sending keyboard events to the emulated PS2 controller.
+ *
+ * @param {boolean} enabled
+ * @export
+ */
+V86.prototype.keyboard_set_status = function(enabled)
+{
+    if(this.keyboard_adapter)
+    {
+        this.keyboard_adapter.emu_enabled = enabled;
+    }
+};
+
+
+/**
+ * Send a string to the first emulated serial terminal.
+ *
+ * @param {string} data
+ * @export
+ */
+V86.prototype.serial0_send = function(data)
+{
+    for(var i = 0; i < data.length; i++)
+    {
+        this.bus.send("serial0-input", data.charCodeAt(i));
+    }
+};
+
+/**
+ * Send bytes to a serial port (to be received by the emulated PC).
+ *
+ * @param {Uint8Array} data
+ * @export
+ */
+V86.prototype.serial_send_bytes = function(serial, data)
+{
+    for(var i = 0; i < data.length; i++)
+    {
+        this.bus.send("serial" + serial + "-input", data[i]);
+    }
+};
+
+/**
+ * Set the modem status of a serial port.
+ */
+V86.prototype.serial_set_modem_status = function(serial, status)
+{
+    this.bus.send("serial" + serial + "-modem-status-input", status);
+};
+
+/**
+ * Set the carrier detect status of a serial port.
+ */
+V86.prototype.serial_set_carrier_detect = function(serial, status)
+{
+    this.bus.send("serial" + serial + "-carrier-detect-input", status);
+};
+
+/**
+ * Set the ring indicator status of a serial port.
+ */
+V86.prototype.serial_set_ring_indicator = function(serial, status)
+{
+    this.bus.send("serial" + serial + "-ring-indicator-input", status);
+};
+
+/**
+ * Set the data set ready status of a serial port.
+ */
+V86.prototype.serial_set_data_set_ready = function(serial, status)
+{
+    this.bus.send("serial" + serial + "-data-set-ready-input", status);
+};
+
+/**
+ * Set the clear to send status of a serial port.
+ */
+V86.prototype.serial_set_clear_to_send = function(serial, status)
+{
+    this.bus.send("serial" + serial + "-clear-to-send-input", status);
+};
+
+/**
+ * Mount another filesystem to the current filesystem.
+ * @param {string} path Path for the mount point
+ * @param {string|undefined} baseurl
+ * @param {string|undefined} basefs As a JSON string
+ * @param {function(Object)=} callback
+ * @export
+ */
+V86.prototype.mount_fs = async function(path, baseurl, basefs, callback)
+{
+    let file_storage = new MemoryFileStorage();
+
+    if(baseurl)
+    {
+        file_storage = new ServerFileStorageWrapper(file_storage, baseurl);
+    }
+    const newfs = new FS(file_storage, this.fs9p.qidcounter);
+    const mount = () =>
+    {
+        const idx = this.fs9p.Mount(path, newfs);
+        if(!callback)
+        {
+            return;
+        }
+        if(idx === -ENOENT)
+        {
+            callback(new FileNotFoundError());
+        }
+        else if(idx === -EEXIST)
+        {
+            callback(new FileExistsError());
+        }
+        else if(idx < 0)
+        {
+            dbg_assert(false, "Unexpected error code: " + (-idx));
+            callback(new Error("Failed to mount. Error number: " + (-idx)));
+        }
+        else
+        {
+            callback(null);
+        }
+    };
+    if(baseurl)
+    {
+        dbg_assert(typeof basefs === "object", "Filesystem: basefs must be a JSON object");
+        newfs.load_from_json(basefs, () => mount());
+    }
+    else
+    {
+        mount();
+    }
+};
+
+/**
+ * Write to a file in the 9p filesystem. Nothing happens if no filesystem has
+ * been initialized.
+ *
+ * @param {string} file
+ * @param {Uint8Array} data
+ * @export
+ */
+V86.prototype.create_file = async function(file, data)
+{
+    dbg_assert(arguments.length === 2);
+    var fs = this.fs9p;
+
+    if(!fs)
+    {
+        return;
+    }
+
+    var parts = file.split("/");
+    var filename = parts[parts.length - 1];
+
+    var path_infos = fs.SearchPath(file);
+    var parent_id = path_infos.parentid;
+    var not_found = filename === "" || parent_id === -1;
+
+    if(!not_found)
+    {
+        await fs.CreateBinaryFile(filename, parent_id, data);
+    }
+    else
+    {
+        return Promise.reject(new FileNotFoundError());
+    }
+};
+
+/**
+ * Read a file in the 9p filesystem. Nothing happens if no filesystem has been
+ * initialized.
+ *
+ * @param {string} file
+ * @export
+ */
+V86.prototype.read_file = async function(file)
+{
+    dbg_assert(arguments.length === 1);
+    var fs = this.fs9p;
+
+    if(!fs)
+    {
+        return;
+    }
+
+    const result = await fs.read_file(file);
+
+    if(result)
+    {
+        return result;
+    }
+    else
+    {
+        return Promise.reject(new FileNotFoundError());
+    }
+};
+
+V86.prototype.automatically = function(steps)
+{
+    const run = (steps) =>
+    {
+        const step = steps[0];
+
+        if(!step)
+        {
+            return;
+        }
+
+        const remaining_steps = steps.slice(1);
+
+        if(step.sleep)
+        {
+            setTimeout(() => run(remaining_steps), step.sleep * 1000);
+            return;
+        }
+
+        if(step.vga_text)
+        {
+            const screen = this.screen_adapter.get_text_screen();
+
+            for(let line of screen)
+            {
+                if(line.includes(step.vga_text))
+                {
+                    run(remaining_steps);
+                    return;
+                }
+            }
+
+            setTimeout(() => run(steps), 1000);
+            return;
+        }
+
+        if(step.keyboard_send)
+        {
+            if(step.keyboard_send instanceof Array)
+            {
+                this.keyboard_send_scancodes(step.keyboard_send);
+            }
+            else
+            {
+                dbg_assert(typeof step.keyboard_send === "string");
+                this.keyboard_send_text(step.keyboard_send);
+            }
+
+            run(remaining_steps);
+            return;
+        }
+
+        if(step.call)
+        {
+            step.call();
+            run(remaining_steps);
+            return;
+        }
+
+        dbg_assert(false, step);
+    };
+
+    run(steps);
+
+};
+
+/**
+ * Reads data from memory at specified offset.
+ *
+ * @param {number} offset
+ * @param {number} length
+ * @returns
+ */
+V86.prototype.read_memory = function(offset, length)
+{
+    return this.v86.cpu.read_blob(offset, length);
+};
+
+/**
+ * Writes data to memory at specified offset.
+ *
+ * @param {Array.<number>|Uint8Array} blob
+ * @param {number} offset
+ */
+V86.prototype.write_memory = function(blob, offset)
+{
+    this.v86.cpu.write_blob(blob, offset);
+};
+
+V86.prototype.set_serial_container_xtermjs = function(element)
+{
+    this.serial_adapter && this.serial_adapter.destroy && this.serial_adapter.destroy();
+    this.serial_adapter = new SerialAdapterXtermJS(element, this.bus);
+    this.serial_adapter.show();
+};
+
+/**
+ * @ignore
+ * @constructor
+ *
+ * @param {string=} message
+ */
+function FileExistsError(message)
+{
+    this.message = message || "File already exists";
+}
+FileExistsError.prototype = Error.prototype;
+
+/**
+ * @ignore
+ * @constructor
+ *
+ * @param {string=} message
+ */
+function FileNotFoundError(message)
+{
+    this.message = message || "File not found";
+}
+FileNotFoundError.prototype = Error.prototype;
+
+// Closure Compiler's way of exporting
+if(typeof window !== "undefined")
+{
+    window["V86Starter"] = V86;
+    window["V86"] = V86;
+}
+else if(typeof module !== "undefined" && typeof module.exports !== "undefined")
+{
+    module.exports["V86Starter"] = V86;
+    module.exports["V86"] = V86;
+}
+else if(typeof importScripts === "function")
+{
+    // web worker
+    self["V86Starter"] = V86;
+    self["V86"] = V86;
+}

+ 218 - 0
incubator/x86emu/www/third-party/xterm.css

@@ -0,0 +1,218 @@
+/**
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+ * https://github.com/chjj/term.js
+ * @license MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Originally forked from (with the author's permission):
+ *   Fabrice Bellard's javascript vt100 for jslinux:
+ *   http://bellard.org/jslinux/
+ *   Copyright (c) 2011 Fabrice Bellard
+ *   The original design remains. The terminal itself
+ *   has been extended to include xterm CSI codes, among
+ *   other features.
+ */
+
+/**
+ *  Default styles for xterm.js
+ */
+
+.xterm {
+    cursor: text;
+    position: relative;
+    user-select: none;
+    -ms-user-select: none;
+    -webkit-user-select: none;
+}
+
+.xterm.focus,
+.xterm:focus {
+    outline: none;
+}
+
+.xterm .xterm-helpers {
+    position: absolute;
+    top: 0;
+    /**
+     * The z-index of the helpers must be higher than the canvases in order for
+     * IMEs to appear on top.
+     */
+    z-index: 5;
+}
+
+.xterm .xterm-helper-textarea {
+    padding: 0;
+    border: 0;
+    margin: 0;
+    /* Move textarea out of the screen to the far left, so that the cursor is not visible */
+    position: absolute;
+    opacity: 0;
+    left: -9999em;
+    top: 0;
+    width: 0;
+    height: 0;
+    z-index: -5;
+    /** Prevent wrapping so the IME appears against the textarea at the correct position */
+    white-space: nowrap;
+    overflow: hidden;
+    resize: none;
+}
+
+.xterm .composition-view {
+    /* TODO: Composition position got messed up somewhere */
+    background: #000;
+    color: #FFF;
+    display: none;
+    position: absolute;
+    white-space: nowrap;
+    z-index: 1;
+}
+
+.xterm .composition-view.active {
+    display: block;
+}
+
+.xterm .xterm-viewport {
+    /* On OS X this is required in order for the scroll bar to appear fully opaque */
+    background-color: #000;
+    overflow-y: scroll;
+    cursor: default;
+    position: absolute;
+    right: 0;
+    left: 0;
+    top: 0;
+    bottom: 0;
+}
+
+.xterm .xterm-screen {
+    position: relative;
+}
+
+.xterm .xterm-screen canvas {
+    position: absolute;
+    left: 0;
+    top: 0;
+}
+
+.xterm .xterm-scroll-area {
+    visibility: hidden;
+}
+
+.xterm-char-measure-element {
+    display: inline-block;
+    visibility: hidden;
+    position: absolute;
+    top: 0;
+    left: -9999em;
+    line-height: normal;
+}
+
+.xterm.enable-mouse-events {
+    /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
+    cursor: default;
+}
+
+.xterm.xterm-cursor-pointer,
+.xterm .xterm-cursor-pointer {
+    cursor: pointer;
+}
+
+.xterm.column-select.focus {
+    /* Column selection mode */
+    cursor: crosshair;
+}
+
+.xterm .xterm-accessibility:not(.debug),
+.xterm .xterm-message {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    right: 0;
+    z-index: 10;
+    color: transparent;
+    pointer-events: none;
+}
+
+.xterm .xterm-accessibility-tree:not(.debug) *::selection {
+  color: transparent;
+}
+
+.xterm .xterm-accessibility-tree {
+  user-select: text;
+  white-space: pre;
+}
+
+.xterm .live-region {
+    position: absolute;
+    left: -9999px;
+    width: 1px;
+    height: 1px;
+    overflow: hidden;
+}
+
+.xterm-dim {
+    /* Dim should not apply to background, so the opacity of the foreground color is applied
+     * explicitly in the generated class and reset to 1 here */
+    opacity: 1 !important;
+}
+
+.xterm-underline-1 { text-decoration: underline; }
+.xterm-underline-2 { text-decoration: double underline; }
+.xterm-underline-3 { text-decoration: wavy underline; }
+.xterm-underline-4 { text-decoration: dotted underline; }
+.xterm-underline-5 { text-decoration: dashed underline; }
+
+.xterm-overline {
+    text-decoration: overline;
+}
+
+.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
+.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
+.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
+.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
+.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
+
+.xterm-strikethrough {
+    text-decoration: line-through;
+}
+
+.xterm-screen .xterm-decoration-container .xterm-decoration {
+	z-index: 6;
+	position: absolute;
+}
+
+.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
+	z-index: 7;
+}
+
+.xterm-decoration-overview-ruler {
+    z-index: 8;
+    position: absolute;
+    top: 0;
+    right: 0;
+    pointer-events: none;
+}
+
+.xterm-decoration-top {
+    z-index: 2;
+    position: relative;
+}

文件差异内容过多而无法显示
+ 0 - 0
incubator/x86emu/www/third-party/xterm.js


部分文件因为文件数量过多而无法显示