// # DosHost // This class is used to detect and provide information about // features that supported in current environment /* tslint:disable:member-ordering */ import { Build } from "./js-dos-build"; import { ICache } from "./js-dos-cache"; import { DosModule } from "./js-dos-module"; import { Xhr } from "./js-dos-xhr"; class DosHost { public wasmSupported = false; public global = window as any; private wdosboxPromise: Promise | null = null; constructor() { this.global.exports = {}; // ### WebAssembly // Host able to detect is WebAssembly supported or not, // this information is stored in `Host.wasmSupported` variable try { if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function" && typeof WebAssembly.compile === "function") { const wmodule = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); if (wmodule instanceof WebAssembly.Module) { this.wasmSupported = new WebAssembly.Instance(wmodule) instanceof WebAssembly.Instance; } } } catch (error) { /* do nothing WebAssembly is not supported */ } // ### polyfill // Host also provides limited set of polyfills to support legacy browsers this.polyfill(); } // Currently polyfill contains implementations for: // `Math.imul`, `Math.fround`, `Math.clz32`, `Math.trunc` /* tslint:disable:no-bitwise */ /* tslint:disable:only-arrow-functions */ private polyfill() { if (!Math.imul || Math.imul(0xffffffff, 5) !== -5) { Math.imul = function imul(a: any, b: any) { const ah = a >>> 16; const al = a & 0xffff; const bh = b >>> 16; const bl = b & 0xffff; return (al * bl + ((ah * bl + al * bh) << 16)) | 0; }; } Math.imul = Math.imul; if (!Math.fround) { Math.fround = function(x) { return x; }; } Math.fround = Math.fround; if (!Math.clz32) { Math.clz32 = function(x) { x = x >>> 0; for (let i = 0; i < 32; i++) { if (x & (1 << (31 - i))) { return i; } } return 32; }; } Math.clz32 = Math.clz32; if (!Math.trunc) { Math.trunc = function(x) { return x < 0 ? Math.ceil(x) : Math.floor(x); }; } Math.trunc = Math.trunc; } // ### resolveDosBox // `resolveDosBox` is another important task of DosHost public resolveDosBox(url: string, cache: ICache, module: DosModule) { // When dosbox is resolved, WDOSBOX module is set to // global variable `exports.WDOSBOX`. This variable is // used to prevent next loads of same dosbox module. if (this.global.exports.WDOSBOX) { module.ondosbox(this.global.exports.WDOSBOX, this.global.exports.instantiateWasm); return; } if (this.wdosboxPromise === null) { this.wdosboxPromise = this.compileDosBox(url, cache, module); } this.wdosboxPromise.then((instance: any) => { /* leave promise scope */ const fn = () => { this.wdosboxPromise = null; module.ondosbox(this.global.exports.WDOSBOX, this.global.exports.instantiateWasm); }; setTimeout(fn, 1); }, (message: string) => { /* leave promise scope */ const fn = () => { this.wdosboxPromise = null; if (module.onerror !== undefined) { module.onerror(message); } }; setTimeout(fn, 1); }); } // If dosbox is not yet resolved, then: private compileDosBox(url: string, cache: ICache, module: DosModule) { const fromIndex = url.lastIndexOf("/"); const wIndex = url.indexOf("w", fromIndex); const isWasmUrl = wIndex === fromIndex + 1 && wIndex >= 0; if (this.wasmSupported && isWasmUrl) { return this.compileWasmDosBox(url, cache, module); } else { if (module.log) { module.log("[WARN] Using js version of dosbox, perfomance can be lower then expected"); module.log("[DEBUG] Wasm supported: " + this.wasmSupported + ", url: " + url); } // fallback to js version if wasm not supported if (isWasmUrl) { url = url.substr(0, wIndex) + url.substr(wIndex + 1); if (url.endsWith("dosbox.js")) { // do not use dosbox.js, because it's not asm.js url = url.replace("dosbox.js", "dosbox-emterp.js"); } } return this.compileJsDosBox(url, cache, module); } } private compileJsDosBox(url: string, cache: ICache, module: DosModule): Promise { return new Promise((resolve, reject) => { const buildTotal = Build.jsSize; // @ts-ignore the unusued local for memUrl not being read const memUrl = url.replace(".js", ".js.mem"); // * Host download `dosbox.js` new Xhr(url, { cache, progress: (total, loaded) => { if (module.onprogress) { module.onprogress("Resolving DosBox (" + url + ")", buildTotal, Math.min(buildTotal, loaded)); } }, fail: (url: string, status: number, message: string) => { reject("Can't download dosbox.js, code: " + status + ", message: " + message + ", url: " + url); }, success: (response: string) => { if (module.onprogress !== undefined) { module.onprogress("Resolving DosBox (" + url + ")", buildTotal, buildTotal); } response += /* tslint:disable:no-eval */ eval.call(this, response); /* tslint:enable:no-eval */ resolve(this.global.exports.WDOSBOX); }, }); }); } private compileWasmDosBox(url: string, cache: ICache, module: DosModule): Promise { return new Promise((resolve, reject) => { const buildTotal = Build.wasmSize + Build.wasmJsSize; const wasmUrl = url.replace(".js", ".wasm.js"); // * Host downloads `wdosbox` asm + js scripts new Xhr(wasmUrl, { cache, responseType: "arraybuffer", progress: (total, loaded) => { if (module.onprogress) { module.onprogress("Resolving DosBox (" + url + ")", buildTotal, Math.min(Build.wasmSize, loaded)); } }, fail: (url: string, status: number, message: string) => { reject("Can't download wasm, code: " + status + ", message: " + message + ", url: " + url); }, success: (response: any) => { // * Compile dosbox wasm module const promise = WebAssembly.compile(response); const onreject = (reason: any) => { reject(reason + ""); }; promise.catch(onreject); promise.then((wasmModule) => { this.global.exports.instantiateWasm = (info: any, receiveInstance: any) => { info.env.globalscall = (...args: any[]) => { if (module.onglobals) { module.onglobals.apply(null, args); } }; // * Instaniate it for each new dosbox runtime return WebAssembly.instantiate(wasmModule, info) .catch(onreject) .then((instance) => { receiveInstance(instance, wasmModule); }); }; new Xhr(url, { cache, progress: (total, loaded) => { if (module.onprogress) { module.onprogress("Resolving DosBox", buildTotal, Math.min(buildTotal, Build.wasmSize + loaded)); } }, fail: (url: string, status: number, message: string) => { reject("Can't download wdosbox.js, code: " + status + ", message: " + message + ", url: " + url); }, success: (response: string) => { if (module.onprogress !== undefined) { module.onprogress("Resolving DosBox", buildTotal, buildTotal); } response += /* tslint:disable:no-eval */ eval.call(window, response); /* tslint:enable:no-eval */ resolve(this.global.exports.WDOSBOX); }, }); }); }, }); }); } } export const Host = new DosHost();