|
@@ -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;
|
|
|
+}
|