diff --git a/example_v86.html b/example_v86.html new file mode 100644 index 00000000..75ba0c28 --- /dev/null +++ b/example_v86.html @@ -0,0 +1,75 @@ + + + + + + + example computer program + + + + + + + + + + + + diff --git a/loader.js b/loader.js index 1e304381..5acde595 100644 --- a/loader.js +++ b/loader.js @@ -64,6 +64,7 @@ var Module = null; xmil: img("/images/xmillenium_logo.jpg"), vmac: img("/images/vmac.png"), ruffle: img("/images/ruffle.png"), + v86: img("/images/v86.png"), }; } else { images = { ia: img("other_logos/ia-logo-150x150.png"), @@ -77,6 +78,7 @@ var Module = null; xmil: img("other_logos/xmillenium_logo.jpg"), vmac: img("other_logos/vmac.png"), ruffle: img("/other_logos/ruffle.png"), + v86: img("other_logos/v86.png"), }; } @@ -188,6 +190,11 @@ var Module = null; cfgr = NP2Loader; get_files = get_vmac_files; } + else if (module && module.indexOf("v86") === 0) { + emulator_logo = images.v86; + cfgr = V86Loader; + get_files = get_v86_files; + } else if (module) { emulator_logo = images.mame; cfgr = MAMELoader; @@ -605,6 +612,49 @@ var Module = null; return files; } + function get_v86_files(cfgr, metadata, modulecfg, filelist) { + var files = []; + + if (modulecfg['bios_filename']) { + files.push(cfgr.mountFile('/' + modulecfg['bios_filename'], cfgr.fetchFile("BIOS File", get_bios_url(modulecfg['bios_filename'])))); + files.push(cfgr.bios(modulecfg['bios_filename'])); + } + if (modulecfg['vga_bios_filename']) { + files.push(cfgr.mountFile('/' + modulecfg['vga_bios_filename'], cfgr.fetchFile("VGA BIOS File", get_bios_url(modulecfg['vga_bios_filename'])))); + files.push(cfgr.vgaBios(modulecfg['vga_bios_filename'])); + } + + var meta = dict_from_xml(metadata), + game_files_counter = {}; + files_with_ext_from_filelist(filelist, meta.emulator_ext).forEach(function (file, i) { + if (modulecfg.peripherals && modulecfg.peripherals[i]) { + game_files_counter[file.name] = modulecfg.peripherals[i]; + } + }); + meta_props_matching(meta, /^v86_drive_([a-zA-Z0-9]+)$/).forEach(function (result) { + var key = result[0], periph = result[1][1]; + game_files_counter[meta[key]] = periph; + }); + + var game_files = Object.keys(game_files_counter), + len = game_files.length; + game_files.forEach(function (filename, i) { + var title = "Game File ("+ (i+1) +" of "+ len +")", + ext = filename.match(/\.([^.]*)$/)[1], + url = (filename.includes("/")) ? get_zip_url(filename) + : get_zip_url(filename, get_item_name(game)), + periph = game_files_counter[filename], + path = '/' + periph + '.' + ext, + periph_cfg = {}; + periph_cfg[periph] = {"path": path}; + files.push(cfgr.mountFile(path, + cfgr.fetchFile(title, url))); + files.push(periph_cfg); + }); + + return files; + } + var get_item_name = function (game_path) { return game_path.split('/').shift(); }; @@ -949,6 +999,52 @@ var Module = null; return { extra_np2_args: args }; }; + /** + * V86Loader + */ + function V86Loader() { + var config = Array.prototype.reduce.call(arguments, extend); + config.runner = V86Runner; + return config; + } + V86Loader.__proto__ = BaseLoader; + + V86Loader.bios = function (filename) { + return {"bios": {"path": filename}} + }; + + V86Loader.vgaBios = function (filename) { + return {"vga_bios": {"path": filename}} + }; + + V86Loader.fda = function (filename) { + return {"fda": {"path": filename}} + }; + + V86Loader.fdb = function (filename) { + return {"fdb": {"path": filename}} + }; + + V86Loader.hda = function (filename) { + return {"hda": {"path": filename}} + }; + + V86Loader.hdb = function (filename) { + return {"hda": {"path": filename}} + }; + + V86Loader.cdrom = function (filename) { + return {"cdrom": {"path": filename}} + }; + + V86Loader.memorySize = function (amount) { + return {"memory_size": amount}; + }; + + V86Loader.vgaMemorySize = function (amount) { + return {"vga_memory_size": amount}; + }; + var build_mame_arguments = function (muted, driver, native_resolution, sample_rate, peripheral, autoboot, extra_args, keepaspect, scale) { scale = scale || 1; var args = [driver, @@ -1276,6 +1372,101 @@ var Module = null; getfullscreenenabler().call(this._canvas); }; + /* + * V86Runner + */ + function V86Runner(canvas, game_data) { + // v86 needs a specific DOM structure instead of a canvas + var screenContainerOuterElt = document.createElement("div"); + screenContainerOuterElt.id = canvas.id; + screenContainerOuterElt.classList = canvas.classList; + screenContainerOuterElt.style = canvas.style; + + var screenContainerInnerElt = document.createElement("div"); + screenContainerInnerElt.classList = ["emularity-v86-screen-container"]; + screenContainerInnerElt.style = "display:flex;justify-content:center;align-items:center;background-color:#000;"; + + var textDivElt = document.createElement("div"); + textDivElt.classList = ["emularity-v86-screen-text"]; + textDivElt.style = "font-size:14px;font-family:monospace;line-height:14px;white-space:pre;"; + var canvasElt = document.createElement("canvas"); + canvasElt.classList = ["emularity-v86-screen-canvas"]; + canvasElt.style = "display:none;"; + + screenContainerInnerElt.appendChild(textDivElt); + screenContainerInnerElt.appendChild(canvasElt); + screenContainerOuterElt.appendChild(screenContainerInnerElt); + canvas.parentNode.replaceChild(screenContainerOuterElt, canvas); + + var cfg = {}; + cfg.screen_container = screenContainerInnerElt; + cfg.memory_size = game_data.memory_size || 32 << 20; + cfg.vga_memory_size = game_data.vga_memory_size || 2 << 20; + cfg.autostart = true; // FIXME + + cfg.wasm_fn = env => { + return new Promise(async resolve => { + const wasm = await WebAssembly.instantiate(game_data.wasmBinary, env); + resolve(wasm.instance.exports); + }); + }; + ["bios", "vga_bios", "fda", "fdb", "cdrom", "hda", "hdb"].forEach(key => { + if (game_data[key] && game_data[key]["path"]) { + cfg[key] = {"buffer": game_data.fs.readFileSync('/'+game_data[key]["path"], null, flag_r).buffer}; + } + }); + + var emu = new V86Starter(cfg); + this._emulator = emu; + this.ready = null; + + if (game_data["scale"]) { + emu.screen_set_scale(game_data["scale"], game_data["scale"]); + } + + screenContainerInnerElt.addEventListener('click', function (e) { + emu.lock_mouse(); + }); + } + + V86Runner.prototype.start = function () { + // this._emulator.run(); // FIXME + }; + + V86Runner.prototype.pause = function () { + this._emulator.stop(); + }; + + V86Runner.prototype.stop = function () { + this._emulator.stop(); + }; + + V86Runner.prototype.mute = function () { + if (this._emulator.is_muted) { + this._emulator.speaker_adapter.mixer.set_volume(1, undefined); + this._emulator.is_muted = false; + } + }; + + V86Runner.prototype.unmute = function () { + if (!this._emulator.is_muted) { + this._emulator.speaker_adapter.mixer.set_volume(0, undefined); + this._emulator.is_muted = true; + } + }; + + V86Runner.prototype.onStarted = function (func) { + this._emulator.add_listener("emulator-started", func); + }; + + V86Runner.prototype.onReset = function (func) { + // not supported + }; + + V86Runner.prototype.requestFullScreen = function () { + getfullscreenenabler().call(this._canvas); + }; + /* * RuffleRunner */ @@ -2202,6 +2393,7 @@ var Module = null; window.PCELoader = PCELoader; window.VICELoader = VICELoader; window.NP2Loader = NP2Loader; + window.V86Loader = V86Loader; window.RuffleLoader = RuffleLoader; window.Emulator = Emulator; window._SDL_CreateRGBSurfaceFrom = _SDL_CreateRGBSurfaceFrom;