<NME> starter.js
<BEF> "use strict";

/**
 * Constructor for emulator instances.
 *
 * Usage: `var emulator = new V86Starter(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 drive) - 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 drive
 *   hda: {
 *       buffer: new ArrayBuffer(16 * 1024 * 1024)
 *   }
 *   ```
 *
 * ***
 *
 * @param {Object} options Options to initialize the emulator with.
 * @constructor
 */
function V86Starter(options)
{
    //var worker = new Worker("src/browser/worker.js");
    //var adapter_bus = this.bus = WorkerBus.init(worker);

    this.cpu_is_running = false;

    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) => {
            return this["cpu_exception_hook"] && this["cpu_exception_hook"](n);
        },
        "hlt_op": function() { return cpu.hlt_op(); },
        "abort": function() { dbg_assert(false); },
        "microtick": v86.microtick,
        "get_rand_int": function() { return v86util.get_rand_int(); },

        "pic_acknowledge": function() { cpu.pic_acknowledge(); },

        "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);
                            resolve(instance.exports);
                        }
                        catch(err)
                        {
                            v86util.load_file(v86_bin_fallback, {
                                    done: async bytes => {
                                        const { instance } = await WebAssembly.instantiate(bytes, env);
                                        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);
        });
}

V86Starter.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,
    };

    settings.acpi = options["acpi"];
    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 = options["boot_order"] || 0x213;
    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"];

    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 = [];

    function 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.buffer instanceof ArrayBuffer)
        {
            var buffer = new v86util.SyncBuffer(file.buffer);
            files_to_load.push({
                name: name,
                loadable: buffer,
            });
        }
        else if(typeof File !== "undefined" && file.buffer instanceof File)
        {
            // SyncFileBuffer:
            // - loads the whole disk image into memory, impossible for large files (more than 1GB)
            // - can later serve get/set operations fast and synchronously
            // - takes some time for first load, neglectable for small files (up to 100Mb)
            //
            // AsyncFileBuffer:
            // - loads slices of the file asynchronously as requested
            // - slower get/set

            // Heuristics: If file is larger than or equal to 256M, use AsyncFileBuffer
            if(file.async === undefined)
            {
                file.async = file.buffer.size >= 256 * 1024 * 1024;
            }

            if(file.async)
            {
                var buffer = new v86util.AsyncFileBuffer(file.buffer);
            }
            else
            {
                var buffer = new v86util.SyncFileBuffer(file.buffer);
            }

            files_to_load.push({
                name: name,
                loadable: buffer,
            });
        }
        else if(file.url)
        {
            if(file.async)
            {
                let buffer;

                if(file.use_parts)
                {
                    buffer = new v86util.AsyncXHRPartfileBuffer(file.url, file.size, file.fixed_chunk_size);
                }
                else
                {
                    buffer = new v86util.AsyncXHRBuffer(file.url, file.size);
                }

                files_to_load.push({
                    name: name,
                    loadable: buffer,
                });
            }
            else
            {
                files_to_load.push({
                    name: name,
                    url: file.url,
                    size: file.size,
                });
            }
        }
        else
        {
            dbg_log("Ignored file: url=" + file.url + " buffer=" + file.buffer);
        }
    }

    if(options["state"])
    {
        console.warn("Warning: Unknown option 'state'. Did you mean 'initial_state'?");
    }

    var image_names = [
        "bios", "vga_bios",
        "cdrom", "hda", "hdb", "fda", "fdb",
        "initial_state", "multiboot",
        "bzimage", "initrd",
    ];

    for(var i = 0; i < image_names.length; i++)
    {
        add_file(image_names[i], options[image_names[i]]);
    }

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

V86Starter.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
 */
V86Starter.prototype.run = async function()
{
    this.bus.send("cpu-run");
};

/**
 * Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
 * @export
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.prototype.add_listener = function(event, listener)
{
    this.bus.register(event, listener, this);
};

/**
 * Remove an event listener.
 *
 * @param {string} event
 * @param {function(*)} listener
 * @export
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.prototype.save_state = async function()
{
    dbg_assert(arguments.length === 0);
    return this.v86.save_state();
};

/**
 * Return an object with several statistics. Return value looks similar to
 * (but can be subject to change in future versions or different
 * configurations, so use defensively):
 *
 * ```javascript
 * {
 *     "cpu": {
 *         "instruction_counter": 2821610069
 *     },
 *     "hda": {
 *         "sectors_read": 95240,
 *         "sectors_written": 952,
 *         "bytes_read": 48762880,
 *         "bytes_written": 487424,
 *         "loading": false
 *     },
 *     "cdrom": {
 *         "sectors_read": 0,
 *         "sectors_written": 0,
 *         "bytes_read": 0,
 *         "bytes_written": 0,
 *         "loading": false
 *     },
 *     "mouse": {
 *         "enabled": true
 *     },
 *     "vga": {
 *         "is_graphical": true,
 *         "res_x": 800,
 *         "res_y": 600,
 *         "bpp": 32
 *     }
 * }
 * ```
 *
 * @deprecated
 * @return {Object}
 * @export
 */
V86Starter.prototype.get_statistics = function()
{
    if(this.v86)
    {
        return this.v86.cpu.timestamp_counter[0];
    }
    else
    {
    };

    if(!this.v86)
    {
        return stats;
    }

    var devices = this.v86.cpu.devices;

    if(devices.hda)
    {
        stats.hda = devices.hda.stats;
    }

    if(devices.cdrom)
    {
        stats.cdrom = devices.cdrom.stats;
    }

    if(devices.ps2)
    {
        stats["mouse"] = {
            "enabled": devices.ps2.use_mouse,
        };
    }

    if(devices.vga)
    {
        stats["vga"] = {
            "is_graphical": devices.vga.stats.is_graphical,
        };
    }

    return stats;
};

/**
 * @return {number}
 * @ignore
 * @export
 */
V86Starter.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
 */
V86Starter.prototype.is_running = function()
{
    return this.cpu_is_running;
};

/**
 * 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
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.prototype.screen_make_screenshot = function()
{
    if(this.screen_adapter)
    {
        this.screen_adapter.make_screenshot();
    }
};

/**
 * Set the scaling level of the emulated screen.
 *
 * @param {number} sx
 * @param {number} sy
 *
 * @ignore
 * @export
 */
V86Starter.prototype.screen_set_scale = function(sx, sy)
{
    if(this.screen_adapter)
    {
        this.screen_adapter.set_scale(sx, sy);
    }
};

/**
 * Go fullscreen.
 *
 * @ignore
 * @export
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.prototype.serial_send_bytes = function(serial, data)
{
    for(var i = 0; i < data.length; i++)
    {
        this.bus.send("serial" + serial + "-input", data[i]);
    }
};

/**
 * 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
 */
V86Starter.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
 */
V86Starter.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
 */
V86Starter.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());
    }
};

V86Starter.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
 */
V86Starter.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
 */
V86Starter.prototype.write_memory = function(blob, offset)
{
    this.v86.cpu.write_blob(blob, offset);
};

/**
 * @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"] = V86Starter;
    window["V86"] = V86Starter;
}
else if(typeof module !== "undefined" && typeof module.exports !== "undefined")
{
    module.exports["V86Starter"] = V86Starter;
    module.exports["V86"] = V86Starter;
}
else if(typeof importScripts === "function")
{
    // web worker
    self["V86Starter"] = V86Starter;
    self["V86"] = V86Starter;
}

<MSG> Fix instruction counter wrap-around

<DFF> @@ -844,7 +844,7 @@ V86Starter.prototype.get_instruction_counter = function()
 {
     if(this.v86)
     {
-        return this.v86.cpu.timestamp_counter[0];
+        return this.v86.cpu.timestamp_counter[0] >>> 0;
     }
     else
     {

{'commit': 'diff --git a/src/browser/main.js b/src/browser/main.js\nindex 68dc2d46..abe92978 100644\n--- a/src/browser/main.js\n+++ b/src/browser/main.js\n@@ -907,22 +907,30 @@\n         var last_instr_counter = 0;\n         var interval;\n         var os_uses_mouse = false;\n+        var total_instructions = 0;\n \n         function update_info()\n         {\n             var now = Date.now();\n \n             var instruction_counter = emulator.get_instruction_counter();\n-            var last_ips = instruction_counter - last_instr_counter;\n \n+            if(instruction_counter < last_instr_counter)\n+            {\n+                // 32-bit wrap-around\n+                last_instr_counter -= 0x100000000;\n+            }\n+\n+            var last_ips = instruction_counter - last_instr_counter;\n             last_instr_counter = instruction_counter;\n+            total_instructions += last_ips;\n \n             var delta_time = now - last_tick;\n             running_time += delta_time;\n             last_tick = now;\n \n             $("speed").textContent = last_ips / delta_time | 0;\n-            $("avg_speed").textContent = instruction_counter / running_time | 0;\n+            $("avg_speed").textContent = total_instructions / running_time | 0;\n             $("running_time").textContent = format_timestamp(running_time / 1000 | 0);\n         }\n \ndiff --git a/src/browser/starter.js b/src/browser/starter.js\nindex 25944f00..0928c450 100644\n--- a/src/browser/starter.js\n+++ b/src/browser/starter.js\n@@ -844,7 +844,7 @@ V86Starter.prototype.get_instruction_counter = function()\n {\n     if(this.v86)\n     {\n-        return this.v86.cpu.timestamp_counter[0];\n+        return this.v86.cpu.timestamp_counter[0] >>> 0;\n     }\n     else\n     {\n', 'commit_subject': 'Fix instruction counter wrap-around', 'repo_name': 'copy/v86', 'committer': {'name': 'Fabian', 'email': 'f84e2e2dadd87384fb55f25886926b777e8378f1@copy.sh', 'time_sec': '1609463669', 'tz_offset': '-360', 'date': {'seconds': '1609463669'}}, 'commit_tree': '660d62c6ef3b4ebad886ea0cc9fd05e519ab082f', 'commit_message': 'Fix instruction counter wrap-around\n', 'language_name': 'Python', 'license': 'bsd-2-clause', 'hunks': '@@ -844,7 +844,7 @@ V86Starter.prototype.get_instruction_counter = function()\n {\n     if(this.v86)\n     {\n-        return this.v86.cpu.timestamp_counter[0];\n+        return this.v86.cpu.timestamp_counter[0] >>> 0;\n     }\n     else\n     {\n', 'addition_count': 1, 'deletion_count': 1, 'src_file': 'a/src/browser/starter.js', 'tgt_file': 'b/src/browser/starter.js', 'file_extension': '.js', 'before_file': '"use strict";\n\n/**\n * Constructor for emulator instances.\n *\n * Usage: `var emulator = new V86Starter(options);`\n *\n * Options can have the following properties (all optional, default in parenthesis):\n *\n * - `memory_size number` (16 * 1024 * 1024) - The memory size in bytes, should\n *   be a power of 2.\n * - `vga_memory_size number` (8 * 1024 * 1024) - VGA memory size in bytes.\n *\n * - `autostart boolean` (false) - If emulation should be started when emulator\n *   is ready.\n *\n * - `disable_keyboard boolean` (false) - If the keyboard should be disabled.\n * - `disable_mouse boolean` (false) - If the mouse should be disabled.\n *\n * - `network_relay_url string` (No network card) - The url of a server running\n *   websockproxy. See [networking.md](networking.md). Setting this will\n *   enable an emulated network card.\n *\n * - `bios Object` (No bios) - Either a url pointing to a bios or an\n *   ArrayBuffer, see below.\n * - `vga_bios Object` (No VGA bios) - VGA bios, see below.\n * - `hda Object` (No hard drive) - First hard disk, see below.\n * - `fda Object` (No floppy disk) - First floppy disk, see below.\n * - `cdrom Object` (No CD) - See below.\n *\n * - `bzimage Object` - A Linux kernel image to boot (only bzimage format), see below.\n * - `initrd Object` - A Linux ramdisk image, see below.\n * - `bzimage_initrd_from_filesystem boolean` - Automatically fetch bzimage and\n *    initrd from the specified `filesystem`.\n *\n * - `initial_state Object` (Normal boot) - An initial state to load, see\n *   [`restore_state`](#restore_statearraybuffer-state) and below.\n *\n * - `filesystem Object` (No 9p filesystem) - A 9p filesystem, see\n *   [filesystem.md](filesystem.md).\n *\n * - `serial_container HTMLTextAreaElement` (No serial terminal) - A textarea\n *   that will receive and send data to the emulated serial terminal.\n *   Alternatively the serial terminal can also be accessed programatically,\n *   see [serial.html](../examples/serial.html).\n *\n * - `screen_container HTMLElement` (No screen) - An HTMLElement. This should\n *   have a certain structure, see [basic.html](../examples/basic.html).\n *\n * ***\n *\n * There are two ways to load images (`bios`, `vga_bios`, `cdrom`, `hda`, ...):\n *\n * - Pass an object that has a url. Optionally, `async: true` and `size:\n *   size_in_bytes` can be added to the object, so that sectors of the image\n *   are loaded on demand instead of being loaded before boot (slower, but\n *   strongly recommended for big files). In that case, the `Range: bytes=...`\n *   header must be supported on the server.\n *\n *   ```javascript\n *   // download file before boot\n *   bios: {\n *       url: "bios/seabios.bin"\n *   }\n *   // download file sectors as requested, size is required\n *   hda: {\n *       url: "disk/linux.iso",\n *       async: true,\n *       size: 16 * 1024 * 1024\n *   }\n *   ```\n *\n * - Pass an `ArrayBuffer` or `File` object as `buffer` property.\n *\n *   ```javascript\n *   // use <input type=file>\n *   bios: {\n *       buffer: document.all.hd_image.files[0]\n *   }\n *   // start with empty hard drive\n *   hda: {\n *       buffer: new ArrayBuffer(16 * 1024 * 1024)\n *   }\n *   ```\n *\n * ***\n *\n * @param {Object} options Options to initialize the emulator with.\n * @constructor\n */\nfunction V86Starter(options)\n{\n    //var worker = new Worker("src/browser/worker.js");\n    //var adapter_bus = this.bus = WorkerBus.init(worker);\n\n    this.cpu_is_running = false;\n\n    const bus = Bus.create();\n    const adapter_bus = this.bus = bus[0];\n    this.emulator_bus = bus[1];\n\n    var cpu;\n    var wasm_memory;\n\n    const wasm_table = new WebAssembly.Table({ element: "anyfunc", "initial": WASM_TABLE_SIZE + WASM_TABLE_OFFSET });\n\n    const wasm_shared_funcs = {\n        "cpu_exception_hook": (n) => {\n            return this["cpu_exception_hook"] && this["cpu_exception_hook"](n);\n        },\n        "hlt_op": function() { return cpu.hlt_op(); },\n        "abort": function() { dbg_assert(false); },\n        "microtick": v86.microtick,\n        "get_rand_int": function() { return v86util.get_rand_int(); },\n\n        "pic_acknowledge": function() { cpu.pic_acknowledge(); },\n\n        "io_port_read8": function(addr) { return cpu.io.port_read8(addr); },\n        "io_port_read16": function(addr) { return cpu.io.port_read16(addr); },\n        "io_port_read32": function(addr) { return cpu.io.port_read32(addr); },\n        "io_port_write8": function(addr, value) { cpu.io.port_write8(addr, value); },\n        "io_port_write16": function(addr, value) { cpu.io.port_write16(addr, value); },\n        "io_port_write32": function(addr, value) { cpu.io.port_write32(addr, value); },\n\n        "mmap_read8": function(addr) { return cpu.mmap_read8(addr); },\n        "mmap_read16": function(addr) { return cpu.mmap_read16(addr); },\n        "mmap_read32": function(addr) { return cpu.mmap_read32(addr); },\n        "mmap_write8": function(addr, value) { cpu.mmap_write8(addr, value); },\n        "mmap_write16": function(addr, value) { cpu.mmap_write16(addr, value); },\n        "mmap_write32": function(addr, value) { cpu.mmap_write32(addr, value); },\n        "mmap_write64": function(addr, value0, value1) { cpu.mmap_write64(addr, value0, value1); },\n        "mmap_write128": function(addr, value0, value1, value2, value3) {\n            cpu.mmap_write128(addr, value0, value1, value2, value3);\n        },\n\n        "log_from_wasm": function(offset, len) {\n            const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);\n            dbg_log(str, LOG_CPU);\n        },\n        "console_log_from_wasm": function(offset, len) {\n            const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);\n            console.error(str);\n        },\n        "dbg_trace_from_wasm": function() {\n            dbg_trace(LOG_CPU);\n        },\n\n        "codegen_finalize": (wasm_table_index, start, state_flags, ptr, len) => {\n            cpu.codegen_finalize(wasm_table_index, start, state_flags, ptr, len);\n        },\n        "jit_clear_func": (wasm_table_index) => cpu.jit_clear_func(wasm_table_index),\n        "jit_clear_all_funcs": () => cpu.jit_clear_all_funcs(),\n\n        "__indirect_function_table": wasm_table,\n    };\n\n    let wasm_fn = options["wasm_fn"];\n\n    if(!wasm_fn)\n    {\n        wasm_fn = env =>\n        {\n            return new Promise(resolve => {\n                let v86_bin = DEBUG ? "v86-debug.wasm" : "v86.wasm";\n                let v86_bin_fallback = "v86-fallback.wasm";\n\n                if(options["wasm_path"])\n                {\n                    v86_bin = options["wasm_path"];\n                    const slash = v86_bin.lastIndexOf("/");\n                    const dir = slash === -1 ? "" : v86_bin.substr(0, slash);\n                    v86_bin_fallback = dir + "/" + v86_bin_fallback;\n                }\n                else if(typeof window === "undefined" && typeof __dirname === "string")\n                {\n                    v86_bin = __dirname + "/" + v86_bin;\n                    v86_bin_fallback = __dirname + "/" + v86_bin_fallback;\n                }\n                else\n                {\n                    v86_bin = "build/" + v86_bin;\n                    v86_bin_fallback = "build/" + v86_bin_fallback;\n                }\n\n                v86util.load_file(v86_bin, {\n                    done: async bytes =>\n                    {\n                        try\n                        {\n                            const { instance } = await WebAssembly.instantiate(bytes, env);\n                            resolve(instance.exports);\n                        }\n                        catch(err)\n                        {\n                            v86util.load_file(v86_bin_fallback, {\n                                    done: async bytes => {\n                                        const { instance } = await WebAssembly.instantiate(bytes, env);\n                                        resolve(instance.exports);\n                                    },\n                                });\n                        }\n                    },\n                    progress: e =>\n                    {\n                        this.emulator_bus.send("download-progress", {\n                            file_index: 0,\n                            file_count: 1,\n                            file_name: v86_bin,\n\n                            lengthComputable: e.lengthComputable,\n                            total: e.total,\n                            loaded: e.loaded,\n                        });\n                    }\n                });\n            });\n        };\n    }\n\n    wasm_fn({ "env": wasm_shared_funcs })\n        .then((exports) => {\n            wasm_memory = exports.memory;\n            exports["rust_init"]();\n\n            const emulator = this.v86 = new v86(this.emulator_bus, { exports, wasm_table });\n            cpu = emulator.cpu;\n\n            this.continue_init(emulator, options);\n        });\n}\n\nV86Starter.prototype.continue_init = async function(emulator, options)\n{\n    this.bus.register("emulator-stopped", function()\n    {\n        this.cpu_is_running = false;\n    }, this);\n\n    this.bus.register("emulator-started", function()\n    {\n        this.cpu_is_running = true;\n    }, this);\n\n    var settings = {};\n\n    this.disk_images = {\n        "fda": undefined,\n        "fdb": undefined,\n        "hda": undefined,\n        "hdb": undefined,\n        "cdrom": undefined,\n    };\n\n    settings.acpi = options["acpi"];\n    settings.load_devices = true;\n    settings.log_level = options["log_level"];\n    settings.memory_size = options["memory_size"] || 64 * 1024 * 1024;\n    settings.vga_memory_size = options["vga_memory_size"] || 8 * 1024 * 1024;\n    settings.boot_order = options["boot_order"] || 0x213;\n    settings.fastboot = options["fastboot"] || false;\n    settings.fda = undefined;\n    settings.fdb = undefined;\n    settings.uart1 = options["uart1"];\n    settings.uart2 = options["uart2"];\n    settings.uart3 = options["uart3"];\n    settings.cmdline = options["cmdline"];\n    settings.preserve_mac_from_state_image = options["preserve_mac_from_state_image"];\n    settings.mac_address_translation = options["mac_address_translation"];\n    settings.cpuid_level = options["cpuid_level"];\n\n    if(options["network_adapter"])\n    {\n        this.network_adapter = options["network_adapter"](this.bus);\n    }\n    else if(options["network_relay_url"])\n    {\n        this.network_adapter = new NetworkAdapter(options["network_relay_url"], this.bus);\n    }\n\n    // Enable unconditionally, so that state images don\'t miss hardware\n    // TODO: Should be properly fixed in restore_state\n    settings.enable_ne2k = true;\n\n    if(!options["disable_keyboard"])\n    {\n        this.keyboard_adapter = new KeyboardAdapter(this.bus);\n    }\n    if(!options["disable_mouse"])\n    {\n        this.mouse_adapter = new MouseAdapter(this.bus, options["screen_container"]);\n    }\n\n    if(options["screen_container"])\n    {\n        this.screen_adapter = new ScreenAdapter(options["screen_container"], this.bus);\n    }\n    else if(options["screen_dummy"])\n    {\n        this.screen_adapter = new DummyScreenAdapter(this.bus);\n    }\n\n    if(options["serial_container"])\n    {\n        this.serial_adapter = new SerialAdapter(options["serial_container"], this.bus);\n        //this.recording_adapter = new SerialRecordingAdapter(this.bus);\n    }\n\n    if(options["serial_container_xtermjs"])\n    {\n        this.serial_adapter = new SerialAdapterXtermJS(options["serial_container_xtermjs"], this.bus);\n    }\n\n    if(!options["disable_speaker"])\n    {\n        this.speaker_adapter = new SpeakerAdapter(this.bus);\n    }\n\n    // ugly, but required for closure compiler compilation\n    function put_on_settings(name, buffer)\n    {\n        switch(name)\n        {\n            case "hda":\n                settings.hda = this.disk_images["hda"] = buffer;\n                break;\n            case "hdb":\n                settings.hdb = this.disk_images["hdb"] = buffer;\n                break;\n            case "cdrom":\n                settings.cdrom = this.disk_images["cdrom"] = buffer;\n                break;\n            case "fda":\n                settings.fda = this.disk_images["fda"] = buffer;\n                break;\n            case "fdb":\n                settings.fdb = this.disk_images["fdb"] = buffer;\n                break;\n\n            case "multiboot":\n                settings.multiboot = this.disk_images["multiboot"] = buffer.buffer;\n                break;\n            case "bzimage":\n                settings.bzimage = this.disk_images["bzimage"] = buffer.buffer;\n                break;\n            case "initrd":\n                settings.initrd = this.disk_images["initrd"] = buffer.buffer;\n                break;\n\n            case "bios":\n                settings.bios = buffer.buffer;\n                break;\n            case "vga_bios":\n                settings.vga_bios = buffer.buffer;\n                break;\n            case "initial_state":\n                settings.initial_state = buffer.buffer;\n                break;\n            case "fs9p_json":\n                settings.fs9p_json = buffer;\n                break;\n            default:\n                dbg_assert(false, name);\n        }\n    }\n\n    var files_to_load = [];\n\n    function add_file(name, file)\n    {\n        if(!file)\n        {\n            return;\n        }\n\n        if(file.get && file.set && file.load)\n        {\n            files_to_load.push({\n                name: name,\n                loadable: file,\n            });\n            return;\n        }\n\n        if(name === "bios" || name === "vga_bios" ||\n            name === "initial_state" || name === "multiboot" ||\n            name === "bzimage" || name === "initrd")\n        {\n            // Ignore async for these because they must be available before boot.\n            // This should make result.buffer available after the object is loaded\n            file.async = false;\n        }\n\n        if(file.buffer instanceof ArrayBuffer)\n        {\n            var buffer = new v86util.SyncBuffer(file.buffer);\n            files_to_load.push({\n                name: name,\n                loadable: buffer,\n            });\n        }\n        else if(typeof File !== "undefined" && file.buffer instanceof File)\n        {\n            // SyncFileBuffer:\n            // - loads the whole disk image into memory, impossible for large files (more than 1GB)\n            // - can later serve get/set operations fast and synchronously\n            // - takes some time for first load, neglectable for small files (up to 100Mb)\n            //\n            // AsyncFileBuffer:\n            // - loads slices of the file asynchronously as requested\n            // - slower get/set\n\n            // Heuristics: If file is larger than or equal to 256M, use AsyncFileBuffer\n            if(file.async === undefined)\n            {\n                file.async = file.buffer.size >= 256 * 1024 * 1024;\n            }\n\n            if(file.async)\n            {\n                var buffer = new v86util.AsyncFileBuffer(file.buffer);\n            }\n            else\n            {\n                var buffer = new v86util.SyncFileBuffer(file.buffer);\n            }\n\n            files_to_load.push({\n                name: name,\n                loadable: buffer,\n            });\n        }\n        else if(file.url)\n        {\n            if(file.async)\n            {\n                let buffer;\n\n                if(file.use_parts)\n                {\n                    buffer = new v86util.AsyncXHRPartfileBuffer(file.url, file.size, file.fixed_chunk_size);\n                }\n                else\n                {\n                    buffer = new v86util.AsyncXHRBuffer(file.url, file.size);\n                }\n\n                files_to_load.push({\n                    name: name,\n                    loadable: buffer,\n                });\n            }\n            else\n            {\n                files_to_load.push({\n                    name: name,\n                    url: file.url,\n                    size: file.size,\n                });\n            }\n        }\n        else\n        {\n            dbg_log("Ignored file: url=" + file.url + " buffer=" + file.buffer);\n        }\n    }\n\n    if(options["state"])\n    {\n        console.warn("Warning: Unknown option \'state\'. Did you mean \'initial_state\'?");\n    }\n\n    var image_names = [\n        "bios", "vga_bios",\n        "cdrom", "hda", "hdb", "fda", "fdb",\n        "initial_state", "multiboot",\n        "bzimage", "initrd",\n    ];\n\n    for(var i = 0; i < image_names.length; i++)\n    {\n        add_file(image_names[i], options[image_names[i]]);\n    }\n\n    if(options["filesystem"])\n    {\n        var fs_url = options["filesystem"].basefs;\n        var base_url = options["filesystem"].baseurl;\n\n        let file_storage = new MemoryFileStorage();\n\n        if(base_url)\n        {\n            file_storage = new ServerFileStorageWrapper(file_storage, base_url);\n        }\n        settings.fs9p = this.fs9p = new FS(file_storage);\n\n        if(fs_url)\n        {\n            dbg_assert(base_url, "Filesystem: baseurl must be specified");\n\n            var size;\n\n            if(typeof fs_url === "object")\n            {\n                size = fs_url.size;\n                fs_url = fs_url.url;\n            }\n            dbg_assert(typeof fs_url === "string");\n\n            files_to_load.push({\n                name: "fs9p_json",\n                url: fs_url,\n                size: size,\n                as_json: true,\n            });\n        }\n    }\n\n    var starter = this;\n    var total = files_to_load.length;\n\n    var cont = function(index)\n    {\n        if(index === total)\n        {\n            setTimeout(done.bind(this), 0);\n            return;\n        }\n\n        var f = files_to_load[index];\n\n        if(f.loadable)\n        {\n            f.loadable.onload = function(e)\n            {\n                put_on_settings.call(this, f.name, f.loadable);\n                cont(index + 1);\n            }.bind(this);\n            f.loadable.load();\n        }\n        else\n        {\n            v86util.load_file(f.url, {\n                done: function(result)\n                {\n                    put_on_settings.call(this, f.name, f.as_json ? result : new v86util.SyncBuffer(result));\n                    cont(index + 1);\n                }.bind(this),\n                progress: function progress(e)\n                {\n                    if(e.target.status === 200)\n                    {\n                        starter.emulator_bus.send("download-progress", {\n                            file_index: index,\n                            file_count: total,\n                            file_name: f.url,\n\n                            lengthComputable: e.lengthComputable,\n                            total: e.total || f.size,\n                            loaded: e.loaded,\n                        });\n                    }\n                    else\n                    {\n                        starter.emulator_bus.send("download-error", {\n                            file_index: index,\n                            file_count: total,\n                            file_name: f.url,\n                            request: e.target,\n                        });\n                    }\n                },\n                as_json: f.as_json,\n            });\n        }\n    }.bind(this);\n    cont(0);\n\n    async function done()\n    {\n        //if(settings.initial_state)\n        //{\n        //    // avoid large allocation now, memory will be restored later anyway\n        //    settings.memory_size = 0;\n        //}\n\n        if(settings.fs9p && settings.fs9p_json)\n        {\n            if(!settings.initial_state)\n            {\n                settings.fs9p.load_from_json(settings.fs9p_json);\n            }\n            else\n            {\n                dbg_log("Filesystem basefs ignored: Overridden by state image");\n            }\n\n            if(options["bzimage_initrd_from_filesystem"])\n            {\n                const { bzimage_path, initrd_path } = this.get_bzimage_initrd_from_filesystem(settings.fs9p);\n\n                dbg_log("Found bzimage: " + bzimage_path + " and initrd: " + initrd_path);\n\n                const [initrd, bzimage] = await Promise.all([\n                    settings.fs9p.read_file(initrd_path),\n                    settings.fs9p.read_file(bzimage_path),\n                ]);\n                put_on_settings.call(this, "initrd", new v86util.SyncBuffer(initrd.buffer));\n                put_on_settings.call(this, "bzimage", new v86util.SyncBuffer(bzimage.buffer));\n                finish.call(this);\n            }\n            else\n            {\n                finish.call(this);\n            }\n        }\n        else\n        {\n            dbg_assert(\n                !options["bzimage_initrd_from_filesystem"],\n                "bzimage_initrd_from_filesystem: Requires a filesystem");\n            finish.call(this);\n        }\n\n        function finish()\n        {\n            this.serial_adapter && this.serial_adapter.show && this.serial_adapter.show();\n\n            this.bus.send("cpu-init", settings);\n\n            if(settings.initial_state)\n            {\n                emulator.restore_state(settings.initial_state);\n\n                // The GC can\'t free settings, since it is referenced from\n                // several closures. This isn\'t needed anymore, so we delete it\n                // here\n                settings.initial_state = undefined;\n            }\n\n            if(options["autostart"])\n            {\n                this.bus.send("cpu-run");\n            }\n\n            this.emulator_bus.send("emulator-loaded");\n        }\n    }\n};\n\nV86Starter.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)\n{\n    const root = (filesystem.read_dir("/") || []).map(x => "/" + x);\n    const boot = (filesystem.read_dir("/boot/") || []).map(x => "/boot/" + x);\n\n    let initrd_path;\n    let bzimage_path;\n\n    for(let f of [].concat(root, boot))\n    {\n        const old = /old/i.test(f) || /fallback/i.test(f);\n        const is_bzimage = /vmlinuz/i.test(f) || /bzimage/i.test(f);\n        const is_initrd = /initrd/i.test(f) || /initramfs/i.test(f);\n\n        if(is_bzimage && (!bzimage_path || !old))\n        {\n            bzimage_path = f;\n        }\n\n        if(is_initrd && (!initrd_path || !old))\n        {\n            initrd_path = f;\n        }\n    }\n\n    if(!initrd_path || !bzimage_path)\n    {\n        console.log("Failed to find bzimage or initrd in filesystem. Files:");\n        console.log(root.join(" "));\n        console.log(boot.join(" "));\n    }\n\n    return { initrd_path, bzimage_path };\n};\n\n/**\n * Start emulation. Do nothing if emulator is running already. Can be\n * asynchronous.\n * @export\n */\nV86Starter.prototype.run = async function()\n{\n    this.bus.send("cpu-run");\n};\n\n/**\n * Stop emulation. Do nothing if emulator is not running. Can be asynchronous.\n * @export\n */\nV86Starter.prototype.stop = async function()\n{\n    if(!this.cpu_is_running)\n    {\n        return;\n    }\n\n    await new Promise(resolve => {\n        const listener = () => {\n            this.remove_listener("emulator-stopped", listener);\n            resolve();\n        };\n        this.add_listener("emulator-stopped", listener);\n        this.bus.send("cpu-stop");\n    });\n};\n\n/**\n * @ignore\n * @export\n */\nV86Starter.prototype.destroy = async function()\n{\n    await this.stop();\n\n    this.v86.destroy();\n    this.keyboard_adapter && this.keyboard_adapter.destroy();\n    this.network_adapter && this.network_adapter.destroy();\n    this.mouse_adapter && this.mouse_adapter.destroy();\n    this.screen_adapter && this.screen_adapter.destroy();\n    this.serial_adapter && this.serial_adapter.destroy();\n    this.speaker_adapter && this.speaker_adapter.destroy();\n};\n\n/**\n * Restart (force a reboot).\n * @export\n */\nV86Starter.prototype.restart = function()\n{\n    this.bus.send("cpu-restart");\n};\n\n/**\n * Add an event listener (the emulator is an event emitter). A list of events\n * can be found at [events.md](events.md).\n *\n * The callback function gets a single argument which depends on the event.\n *\n * @param {string} event Name of the event.\n * @param {function(*)} listener The callback function.\n * @export\n */\nV86Starter.prototype.add_listener = function(event, listener)\n{\n    this.bus.register(event, listener, this);\n};\n\n/**\n * Remove an event listener.\n *\n * @param {string} event\n * @param {function(*)} listener\n * @export\n */\nV86Starter.prototype.remove_listener = function(event, listener)\n{\n    this.bus.unregister(event, listener);\n};\n\n/**\n * Restore the emulator state from the given state, which must be an\n * ArrayBuffer returned by\n * [`save_state`](#save_statefunctionobject-arraybuffer-callback).\n *\n * Note that the state can only be restored correctly if this constructor has\n * been created with the same options as the original instance (e.g., same disk\n * images, memory size, etc.).\n *\n * Different versions of the emulator might use a different format for the\n * state buffer.\n *\n * @param {ArrayBuffer} state\n * @export\n */\nV86Starter.prototype.restore_state = async function(state)\n{\n    dbg_assert(arguments.length === 1);\n    this.v86.restore_state(state);\n};\n\n/**\n * Asynchronously save the current state of the emulator.\n *\n * @return {Promise<ArrayBuffer>}\n * @export\n */\nV86Starter.prototype.save_state = async function()\n{\n    dbg_assert(arguments.length === 0);\n    return this.v86.save_state();\n};\n\n/**\n * Return an object with several statistics. Return value looks similar to\n * (but can be subject to change in future versions or different\n * configurations, so use defensively):\n *\n * ```javascript\n * {\n *     "cpu": {\n *         "instruction_counter": 2821610069\n *     },\n *     "hda": {\n *         "sectors_read": 95240,\n *         "sectors_written": 952,\n *         "bytes_read": 48762880,\n *         "bytes_written": 487424,\n *         "loading": false\n *     },\n *     "cdrom": {\n *         "sectors_read": 0,\n *         "sectors_written": 0,\n *         "bytes_read": 0,\n *         "bytes_written": 0,\n *         "loading": false\n *     },\n *     "mouse": {\n *         "enabled": true\n *     },\n *     "vga": {\n *         "is_graphical": true,\n *         "res_x": 800,\n *         "res_y": 600,\n *         "bpp": 32\n *     }\n * }\n * ```\n *\n * @deprecated\n * @return {Object}\n * @export\n */\nV86Starter.prototype.get_statistics = function()\n{\n    if(this.v86)\n    {\n        return this.v86.cpu.timestamp_counter[0];\n    }\n    else\n    {\n    };\n\n    if(!this.v86)\n    {\n        return stats;\n    }\n\n    var devices = this.v86.cpu.devices;\n\n    if(devices.hda)\n    {\n        stats.hda = devices.hda.stats;\n    }\n\n    if(devices.cdrom)\n    {\n        stats.cdrom = devices.cdrom.stats;\n    }\n\n    if(devices.ps2)\n    {\n        stats["mouse"] = {\n            "enabled": devices.ps2.use_mouse,\n        };\n    }\n\n    if(devices.vga)\n    {\n        stats["vga"] = {\n            "is_graphical": devices.vga.stats.is_graphical,\n        };\n    }\n\n    return stats;\n};\n\n/**\n * @return {number}\n * @ignore\n * @export\n */\nV86Starter.prototype.get_instruction_counter = function()\n{\n    if(this.v86)\n    {\n        return this.v86.cpu.instruction_counter[0] >>> 0;\n    }\n    else\n    {\n        // TODO: Should be handled using events\n        return 0;\n    }\n};\n\n/**\n * @return {boolean}\n * @export\n */\nV86Starter.prototype.is_running = function()\n{\n    return this.cpu_is_running;\n};\n\n/**\n * Send a sequence of scan codes to the emulated PS2 controller. A list of\n * codes can be found at http://stanislavs.org/helppc/make_codes.html.\n * Do nothing if there is no keyboard controller.\n *\n * @param {Array.<number>} codes\n * @export\n */\nV86Starter.prototype.keyboard_send_scancodes = function(codes)\n{\n    for(var i = 0; i < codes.length; i++)\n    {\n        this.bus.send("keyboard-code", codes[i]);\n    }\n};\n\n/**\n * Send translated keys\n * @ignore\n * @export\n */\nV86Starter.prototype.keyboard_send_keys = function(codes)\n{\n    for(var i = 0; i < codes.length; i++)\n    {\n        this.keyboard_adapter.simulate_press(codes[i]);\n    }\n};\n\n/**\n * Send text\n * @ignore\n * @export\n */\nV86Starter.prototype.keyboard_send_text = function(string)\n{\n    for(var i = 0; i < string.length; i++)\n    {\n        this.keyboard_adapter.simulate_char(string[i]);\n    }\n};\n\n/**\n * Download a screenshot.\n *\n * @ignore\n * @export\n */\nV86Starter.prototype.screen_make_screenshot = function()\n{\n    if(this.screen_adapter)\n    {\n        this.screen_adapter.make_screenshot();\n    }\n};\n\n/**\n * Set the scaling level of the emulated screen.\n *\n * @param {number} sx\n * @param {number} sy\n *\n * @ignore\n * @export\n */\nV86Starter.prototype.screen_set_scale = function(sx, sy)\n{\n    if(this.screen_adapter)\n    {\n        this.screen_adapter.set_scale(sx, sy);\n    }\n};\n\n/**\n * Go fullscreen.\n *\n * @ignore\n * @export\n */\nV86Starter.prototype.screen_go_fullscreen = function()\n{\n    if(!this.screen_adapter)\n    {\n        return;\n    }\n\n    var elem = document.getElementById("screen_container");\n\n    if(!elem)\n    {\n        return;\n    }\n\n    // bracket notation because otherwise they get renamed by closure compiler\n    var fn = elem["requestFullScreen"] ||\n            elem["webkitRequestFullscreen"] ||\n            elem["mozRequestFullScreen"] ||\n            elem["msRequestFullScreen"];\n\n    if(fn)\n    {\n        fn.call(elem);\n\n        // This is necessary, because otherwise chromium keyboard doesn\'t work anymore.\n        // Might (but doesn\'t seem to) break something else\n        var focus_element = document.getElementsByClassName("phone_keyboard")[0];\n        focus_element && focus_element.focus();\n    }\n\n    try {\n        navigator.keyboard.lock();\n    } catch(e) {}\n\n    this.lock_mouse();\n};\n\n/**\n * Lock the mouse cursor: It becomes invisble and is not moved out of the\n * browser window.\n *\n * @ignore\n * @export\n */\nV86Starter.prototype.lock_mouse = function()\n{\n    var elem = document.body;\n\n    var fn = elem["requestPointerLock"] ||\n                elem["mozRequestPointerLock"] ||\n                elem["webkitRequestPointerLock"];\n\n    if(fn)\n    {\n        fn.call(elem);\n    }\n};\n\n/**\n * Enable or disable sending mouse events to the emulated PS2 controller.\n *\n * @param {boolean} enabled\n */\nV86Starter.prototype.mouse_set_status = function(enabled)\n{\n    if(this.mouse_adapter)\n    {\n        this.mouse_adapter.emu_enabled = enabled;\n    }\n};\n\n/**\n * Enable or disable sending keyboard events to the emulated PS2 controller.\n *\n * @param {boolean} enabled\n * @export\n */\nV86Starter.prototype.keyboard_set_status = function(enabled)\n{\n    if(this.keyboard_adapter)\n    {\n        this.keyboard_adapter.emu_enabled = enabled;\n    }\n};\n\n\n/**\n * Send a string to the first emulated serial terminal.\n *\n * @param {string} data\n * @export\n */\nV86Starter.prototype.serial0_send = function(data)\n{\n    for(var i = 0; i < data.length; i++)\n    {\n        this.bus.send("serial0-input", data.charCodeAt(i));\n    }\n};\n\n/**\n * Send bytes to a serial port (to be received by the emulated PC).\n *\n * @param {Uint8Array} data\n * @export\n */\nV86Starter.prototype.serial_send_bytes = function(serial, data)\n{\n    for(var i = 0; i < data.length; i++)\n    {\n        this.bus.send("serial" + serial + "-input", data[i]);\n    }\n};\n\n/**\n * Mount another filesystem to the current filesystem.\n * @param {string} path Path for the mount point\n * @param {string|undefined} baseurl\n * @param {string|undefined} basefs As a JSON string\n * @param {function(Object)=} callback\n * @export\n */\nV86Starter.prototype.mount_fs = async function(path, baseurl, basefs, callback)\n{\n    let file_storage = new MemoryFileStorage();\n\n    if(baseurl)\n    {\n        file_storage = new ServerFileStorageWrapper(file_storage, baseurl);\n    }\n    const newfs = new FS(file_storage, this.fs9p.qidcounter);\n    const mount = () =>\n    {\n        const idx = this.fs9p.Mount(path, newfs);\n        if(!callback)\n        {\n            return;\n        }\n        if(idx === -ENOENT)\n        {\n            callback(new FileNotFoundError());\n        }\n        else if(idx === -EEXIST)\n        {\n            callback(new FileExistsError());\n        }\n        else if(idx < 0)\n        {\n            dbg_assert(false, "Unexpected error code: " + (-idx));\n            callback(new Error("Failed to mount. Error number: " + (-idx)));\n        }\n        else\n        {\n            callback(null);\n        }\n    };\n    if(baseurl)\n    {\n        dbg_assert(typeof basefs === "object", "Filesystem: basefs must be a JSON object");\n        newfs.load_from_json(basefs, () => mount());\n    }\n    else\n    {\n        mount();\n    }\n};\n\n/**\n * Write to a file in the 9p filesystem. Nothing happens if no filesystem has\n * been initialized.\n *\n * @param {string} file\n * @param {Uint8Array} data\n * @export\n */\nV86Starter.prototype.create_file = async function(file, data)\n{\n    dbg_assert(arguments.length === 2);\n    var fs = this.fs9p;\n\n    if(!fs)\n    {\n        return;\n    }\n\n    var parts = file.split("/");\n    var filename = parts[parts.length - 1];\n\n    var path_infos = fs.SearchPath(file);\n    var parent_id = path_infos.parentid;\n    var not_found = filename === "" || parent_id === -1;\n\n    if(!not_found)\n    {\n        await fs.CreateBinaryFile(filename, parent_id, data);\n    }\n    else\n    {\n        return Promise.reject(new FileNotFoundError());\n    }\n};\n\n/**\n * Read a file in the 9p filesystem. Nothing happens if no filesystem has been\n * initialized.\n *\n * @param {string} file\n * @export\n */\nV86Starter.prototype.read_file = async function(file)\n{\n    dbg_assert(arguments.length === 1);\n    var fs = this.fs9p;\n\n    if(!fs)\n    {\n        return;\n    }\n\n    const result = await fs.read_file(file);\n\n    if(result)\n    {\n        return result;\n    }\n    else\n    {\n        return Promise.reject(new FileNotFoundError());\n    }\n};\n\nV86Starter.prototype.automatically = function(steps)\n{\n    const run = (steps) =>\n    {\n        const step = steps[0];\n\n        if(!step)\n        {\n            return;\n        }\n\n        const remaining_steps = steps.slice(1);\n\n        if(step.sleep)\n        {\n            setTimeout(() => run(remaining_steps), step.sleep * 1000);\n            return;\n        }\n\n        if(step.vga_text)\n        {\n            const screen = this.screen_adapter.get_text_screen();\n\n            for(let line of screen)\n            {\n                if(line.includes(step.vga_text))\n                {\n                    run(remaining_steps);\n                    return;\n                }\n            }\n\n            setTimeout(() => run(steps), 1000);\n            return;\n        }\n\n        if(step.keyboard_send)\n        {\n            if(step.keyboard_send instanceof Array)\n            {\n                this.keyboard_send_scancodes(step.keyboard_send);\n            }\n            else\n            {\n                dbg_assert(typeof step.keyboard_send === "string");\n                this.keyboard_send_text(step.keyboard_send);\n            }\n\n            run(remaining_steps);\n            return;\n        }\n\n        if(step.call)\n        {\n            step.call();\n            run(remaining_steps);\n            return;\n        }\n\n        dbg_assert(false, step);\n    };\n\n    run(steps);\n\n};\n\n/**\n * Reads data from memory at specified offset.\n *\n * @param {number} offset\n * @param {number} length\n * @returns\n */\nV86Starter.prototype.read_memory = function(offset, length)\n{\n    return this.v86.cpu.read_blob(offset, length);\n};\n\n/**\n * Writes data to memory at specified offset.\n *\n * @param {Array.<number>|Uint8Array} blob\n * @param {number} offset\n */\nV86Starter.prototype.write_memory = function(blob, offset)\n{\n    this.v86.cpu.write_blob(blob, offset);\n};\n\n/**\n * @ignore\n * @constructor\n *\n * @param {string=} message\n */\nfunction FileExistsError(message)\n{\n    this.message = message || "File already exists";\n}\nFileExistsError.prototype = Error.prototype;\n\n/**\n * @ignore\n * @constructor\n *\n * @param {string=} message\n */\nfunction FileNotFoundError(message)\n{\n    this.message = message || "File not found";\n}\nFileNotFoundError.prototype = Error.prototype;\n\n// Closure Compiler\'s way of exporting\nif(typeof window !== "undefined")\n{\n    window["V86Starter"] = V86Starter;\n    window["V86"] = V86Starter;\n}\nelse if(typeof module !== "undefined" && typeof module.exports !== "undefined")\n{\n    module.exports["V86Starter"] = V86Starter;\n    module.exports["V86"] = V86Starter;\n}\nelse if(typeof importScripts === "function")\n{\n    // web worker\n    self["V86Starter"] = V86Starter;\n    self["V86"] = V86Starter;\n}\n', 'message': 'Fix instruction counter wrap-around\n', 'author_name': 'Fabian', 'lang': 'js'}