From 4fd4b5f788193ac58e151d2b723d355fa015d1b9 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka <kontakt@asie.pl> Date: Sun, 30 Oct 2022 10:51:32 +0100 Subject: [PATCH] preliminary v86 support --- example_v86.html | 75 ++++++++++++++++++ loader.js | 192 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 example_v86.html 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 @@ +<!-- + The Emularity: An Example Computer Loader + For use with The Emularity, downloadable at http://www.emularity.com/ + + SIMPLE STEPS for trying an emulated computer (FreeDOS). + + * Check out this repository in your browser-accessible directory; + this file as well as es6-promise.js, browserfs.min.js and loader.js + are required. The logo and images directories are optional, but the + splash screen looks quite a lot better when they're available. + + * Clone: https://github.com/asiekierka/v86/tree/emularity + and run "make all". + + * Copy bios/seabios.bin, bios/vgabios.bin, build/libv86.js, + build/v86.wasm to "emulators/v86". + + * Optionally, acquire an 8x14 EGA font in WOFF format from: + https://int10h.org/oldschool-pc-fonts/fontlist/ and copy it to + "emulators/v86/ega437.woff". + + * Download FreeDOS from: https://k.copy.sh/freedos722.img + + * Place disk image in an "examples" subdirectory. + + * Visit your example_v86.html file with a modern + Javascript-capable browser. +--> + +<html> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>example computer program</title> + <style type="text/css"> +@font-face { + font-family: 'Ega437'; + src: url('emulators/v86/ega437.woff') format('woff'); +} +.emularity-v86-screen-text { + font-family: 'Ega437', monospace !important; +} + </style> +</head> + +<body> + <canvas id="canvas" style="width: 50%; height: 50%; display: block; margin: 0 auto;" /> + <script type="text/javascript" src="es6-promise.js"></script> + <script type="text/javascript" src="browserfs.min.js"></script> + <script type="text/javascript" src="loader.js"></script> + <script type="text/javascript"> + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new V86Loader(V86Loader.nativeResolution(800, 600), + V86Loader.scale(1), + V86Loader.emulatorJS("emulators/v86/libv86.js"), + V86Loader.emulatorWASM("emulators/v86/v86.wasm"), + V86Loader.mountFile("seabios.bin", + V86Loader.fetchFile("BIOS", + "emulators/v86/seabios.bin")), + V86Loader.mountFile("vgabios.bin", + V86Loader.fetchFile("VGA BIOS", + "emulators/v86/vgabios.bin")), + V86Loader.mountFile("freedos.img", + V86Loader.fetchFile("FreeDOS", + "examples/freedos722.img")), + V86Loader.bios("seabios.bin"), + V86Loader.vgaBios("vgabios.bin"), + V86Loader.fda("freedos.img"), + )); + emulator.start({ waitAfterDownloading: true }); + </script> +</body> + +</html> 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;