UNPKG

@thenick775/mgba-wasm

Version:
1,296 lines (1,079 loc) 486 kB
var mGBA = (() => { var _scriptName = import.meta.url; return ( async function(moduleArg = {}) { var moduleRtn; // include: shell.js // The Module object: Our interface to the outside world. We import // and export values on it. There are various ways Module can be used: // 1. Not defined. We create it here // 2. A function parameter, function(moduleArg) => Promise<Module> // 3. pre-run appended it, var Module = {}; ..generated code.. // 4. External script tag defines var Module. // We need to check if Module already exists (e.g. case 3 above). // Substitution will be replaced with actual code on later stage of the build, // this way Closure Compiler will not mangle it (e.g. case 4. above). // Note that if you want to run closure, and also to use Module // after the generated code, you will need to define var Module = {}; // before the code. Then that object will be used in the code, and you // can continue to use Module afterwards as well. var Module = moduleArg; // Set up the promise that indicates the Module is initialized var readyPromiseResolve, readyPromiseReject; var readyPromise = new Promise((resolve, reject) => { readyPromiseResolve = resolve; readyPromiseReject = reject; }); // Determine the runtime environment we are in. You can customize this by // setting the ENVIRONMENT setting at compile time (see settings.js). // Attempt to auto-detect the environment var ENVIRONMENT_IS_WEB = typeof window == 'object'; var ENVIRONMENT_IS_WORKER = typeof WorkerGlobalScope != 'undefined'; // N.b. Electron.js environment is simultaneously a NODE-environment, but // also a web environment. var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string' && process.type != 'renderer'; var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; // Three configurations we can be running in: // 1) We could be the application main() thread running in the main JS UI thread. (ENVIRONMENT_IS_WORKER == false and ENVIRONMENT_IS_PTHREAD == false) // 2) We could be the application main() thread proxied to worker. (with Emscripten -sPROXY_TO_WORKER) (ENVIRONMENT_IS_WORKER == true, ENVIRONMENT_IS_PTHREAD == false) // 3) We could be an application pthread running in a worker. (ENVIRONMENT_IS_WORKER == true and ENVIRONMENT_IS_PTHREAD == true) // The way we signal to a worker that it is hosting a pthread is to construct // it with a specific name. var ENVIRONMENT_IS_PTHREAD = ENVIRONMENT_IS_WORKER && self.name?.startsWith('em-pthread'); // --pre-jses are emitted after the Module integration code, so that they can // refer to Module (if they choose; they can also define Module) // include: /home/mgba/src/src/platform/wasm/pre.js /* Copyright (c) 2013-2023 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ Module.loadGame = (romPath, savePathOverride) => { const loadGame = cwrap('loadGame', 'number', ['string', 'string']); if (loadGame(romPath, savePathOverride)) { const arr = romPath.split('.'); arr.pop(); const saveName = arr.join('.') + '.sav'; const autoSaveStateName = arr.join('.') + '_auto.ss'; Module.gameName = romPath; Module.saveName = savePathOverride ?? saveName.replace('/data/games/', '/data/saves/'); Module.autoSaveStateName = autoSaveStateName.replace( '/data/games/', '/autosave/' ); return true; } return false; }; Module.getSave = () => { const exists = FS.analyzePath(Module.saveName).exists; return exists ? FS.readFile(Module.saveName) : null; }; Module.listRoms = () => { return FS.readdir('/data/games/'); }; Module.listSaves = () => { return FS.readdir('/data/saves/'); }; Module.FSInit = () => { return new Promise((resolve, reject) => { FS.mkdir('/data'); FS.mount(FS.filesystems.IDBFS, {}, '/data'); // mount auto save directory, this should auto persist, while the data mount should not FS.mkdir('/autosave'); FS.mount(FS.filesystems.IDBFS, { autoPersist: true }, '/autosave'); // load data from IDBFS FS.syncfs(true, (err) => { if (err) { reject(new Error(`Error syncing app data from IndexedDB: ${err}`)); } // When we read from indexedb, these directories may or may not exist. // If we mkdir and they already exist they throw, so just catch all of them. try { FS.mkdir('/data/saves'); } catch (e) {} try { FS.mkdir('/data/states'); } catch (e) {} try { FS.mkdir('/data/games'); } catch (e) {} try { FS.mkdir('/data/cheats'); } catch (e) {} try { FS.mkdir('/data/screenshots'); } catch (e) {} try { FS.mkdir('/data/patches'); } catch (e) {} resolve(); }); }); }; Module.FSSync = () => { return new Promise((resolve, reject) => { // write data to IDBFS FS.syncfs((err) => { if (err) { reject(new Error(`Error syncing app data to IndexedDB: ${err}`)); } resolve(); }); }); }; Module.filePaths = () => { return { root: '/data', cheatsPath: '/data/cheats', gamePath: '/data/games', savePath: '/data/saves', saveStatePath: '/data/states', screenshotsPath: '/data/screenshots', patchPath: '/data/patches', autosave: '/autosave', }; }; Module.uploadSaveOrSaveState = (file, callback) => { const split = file.name.split('.'); if (split.length < 2) { console.warn('unrecognized file extension: ' + file.name); return; } const extension = split[split.length - 1].toLowerCase(); let dir = null; if (extension == 'sav') { dir = '/data/saves/'; } else if (extension.startsWith('ss')) { dir = '/data/states/'; } else { console.warn('unrecognized file extension: ' + extension); return; } const reader = new FileReader(); reader.onload = (e) => { FS.writeFile(dir + file.name, new Uint8Array(e.target.result)); if (callback) { callback(); } }; reader.readAsArrayBuffer(file); }; Module.uploadRom = (file, callback) => { const split = file.name.split('.'); if (split.length < 2) { console.warn('unrecognized file extension: ' + file.name); return; } const extension = split[split.length - 1].toLowerCase(); let dir = null; if (['gba', 'gbc', 'gb', 'zip', '7z'].includes(extension)) { dir = '/data/games/'; } else { console.warn('unrecognized file extension: ' + extension); return; } const reader = new FileReader(); reader.onload = (e) => { FS.writeFile(dir + file.name, new Uint8Array(e.target.result)); if (callback) { callback(); } }; reader.readAsArrayBuffer(file); }; Module.uploadCheats = (file, callback) => { const split = file.name.split('.'); if (split.length < 2) { console.warn('unrecognized file extension: ' + file.name); return; } const extension = split[split.length - 1].toLowerCase(); let dir = null; if (extension == 'cheats') { dir = '/data/cheats/'; } else { console.warn('unrecognized file extension: ' + extension); return; } const reader = new FileReader(); reader.onload = (e) => { FS.writeFile(dir + file.name, new Uint8Array(e.target.result)); if (callback) { callback(); } }; reader.readAsArrayBuffer(file); }; Module.uploadPatch = (file, callback) => { const split = file.name.split('.'); if (split.length < 2) { console.warn('unrecognized file extension: ' + file.name); return; } const extension = split[split.length - 1].toLowerCase(); let dir = null; if (['ips', 'ups', 'bps'].includes(extension)) { dir = '/data/patches/'; } else { console.warn('unrecognized file extension: ' + extension); return; } const reader = new FileReader(); reader.onload = (e) => { FS.writeFile(dir + file.name, new Uint8Array(e.target.result)); if (callback) { callback(); } }; reader.readAsArrayBuffer(file); }; const keyBindings = new Map([ ['a', 0], ['b', 1], ['select', 2], ['start', 3], ['right', 4], ['left', 5], ['up', 6], ['down', 7], ['r', 8], ['l', 9], ]); Module.buttonPress = (name) => { const buttonPress = cwrap('buttonPress', null, ['number']); buttonPress(keyBindings.get(name.toLowerCase())); }; Module.buttonUnpress = (name) => { const buttonUnpress = cwrap('buttonUnpress', null, ['number']); buttonUnpress(keyBindings.get(name.toLowerCase())); }; // bindingName is the key name you want to associate to an input, ex. 'p' key binding -> 'a' input // inputName is the name of the input to bind to, ex 'a', 'b', 'up' etc. Module.bindKey = (bindingName, inputName) => { const bindKey = cwrap('bindKey', null, ['string', 'number']); bindKey(bindingName, keyBindings.get(inputName.toLowerCase())); }; Module.pauseGame = () => { const pauseGame = cwrap('pauseGame', null, []); pauseGame(); }; Module.resumeGame = () => { const resumeGame = cwrap('resumeGame', null, []); resumeGame(); }; Module.pauseAudio = () => { const pauseAudio = cwrap('pauseAudio', null, []); pauseAudio(); }; Module.resumeAudio = () => { const resumeAudio = cwrap('resumeAudio', null, []); resumeAudio(); }; Module.getVolume = () => { const getVolume = cwrap('getVolume', 'number', []); return getVolume(); }; Module.setVolume = (percent) => { const setVolume = cwrap('setVolume', null, ['number']); setVolume(percent); }; Module.getMainLoopTimingMode = () => { const getMainLoopTimingMode = cwrap('getMainLoopTimingMode', 'number', []); return getMainLoopTimingMode(); }; Module.getMainLoopTimingValue = () => { const getMainLoopTimingValue = cwrap('getMainLoopTimingValue', 'number', []); return getMainLoopTimingValue(); }; Module.setMainLoopTiming = (mode, value) => { const setMainLoopTiming = cwrap('setMainLoopTiming', 'number', [ 'number', 'number', ]); setMainLoopTiming(mode, value); }; Module.quitGame = () => { const quitGame = cwrap('quitGame', null, []); quitGame(); }; Module.quitMgba = () => { const quitMgba = cwrap('quitMgba', null, []); quitMgba(); }; Module.quickReload = () => { const quickReload = cwrap('quickReload', null, []); quickReload(); }; Module.toggleInput = (toggle) => { const setEventEnable = cwrap('setEventEnable', null, ['boolean']); setEventEnable(toggle); }; Module.screenshot = (fileName) => { const screenshot = cwrap('screenshot', 'boolean', ['string']); return screenshot(fileName); }; Module.saveState = (slot) => { const saveState = cwrap('saveState', 'boolean', ['number']); return saveState(slot); }; Module.loadState = (slot) => { const loadState = cwrap('loadState', 'boolean', ['number']); return loadState(slot); }; Module.forceAutoSaveState = () => { const autoSaveState = cwrap('autoSaveState', 'boolean', []); return autoSaveState(); }; Module.loadAutoSaveState = () => { const loadAutoSaveState = cwrap('loadAutoSaveState', 'boolean', []); return loadAutoSaveState(); }; Module.getAutoSaveState = () => { const exists = FS.analyzePath(Module.autoSaveStateName).exists; return exists ? { autoSaveStateName: Module.autoSaveStateName, data: FS.readFile(Module.autoSaveStateName), } : null; }; Module.uploadAutoSaveState = async (autoSaveStateName, data) => { return new Promise((resolve, reject) => { try { if (!(data instanceof Uint8Array)) { console.warn('Auto save state data must be a Uint8Array'); return; } if (!autoSaveStateName.length) { console.warn('Auto save state file name invalid'); return; } const path = `${autoSaveStateName}`; FS.writeFile(path, data); resolve(); } catch (err) { reject(err); } }); }; Module.saveStateSlot = (slot, flags) => { var saveStateSlot = cwrap('saveStateSlot', 'number', ['number', 'number']); Module.saveStateSlot = (slot, flags) => { if (flags === undefined) { flags = 0b111111; } return saveStateSlot(slot, flags); }; return Module.saveStateSlot(slot, flags); }; Module.loadStateSlot = (slot, flags) => { var loadStateSlot = cwrap('loadStateSlot', 'number', ['number', 'number']); Module.loadStateSlot = (slot, flags) => { if (flags === undefined) { flags = 0b111101; } return loadStateSlot(slot, flags); }; return Module.loadStateSlot(slot, flags); }; Module.autoLoadCheats = () => { const autoLoadCheats = cwrap('autoLoadCheats', 'bool', []); return autoLoadCheats(); }; Module.setFastForwardMultiplier = (multiplier) => { const setFastForwardMultiplier = cwrap('setFastForwardMultiplier', null, [ 'number', ]); setFastForwardMultiplier(multiplier); }; Module.getFastForwardMultiplier = () => { const getFastForwardMultiplier = cwrap( 'getFastForwardMultiplier', 'number', [] ); return getFastForwardMultiplier(); }; // core callback store, used to persist long lived js function pointers for use in core callbacks in c const coreCallbackStore = { alarmCallbackPtr: null, coreCrashedCallbackPtr: null, keysReadCallbackPtr: null, saveDataUpdatedCallbackPtr: null, videoFrameEndedCallbackPtr: null, videoFrameStartedCallbackPtr: null, autoSaveStateCapturedCallbackPtr: null, autoSaveStateLoadedCallbackPtr: null, }; // adds user callbacks to the callback store, and makes function(s) available to the core in c // passing null clears the callback, allowing for partial additions/removals of callbacks Module.addCoreCallbacks = (callbacks) => { const addCoreCallbacks = cwrap('addCoreCallbacks', null, ['number']); Object.keys(coreCallbackStore).forEach((callbackKey) => { const callbackName = callbackKey.replace('CallbackPtr', 'Callback'); const callback = callbacks[callbackName]; // if the pointer is stored remove the old function pointer if a new callback was passed, or the callback is null if (callback !== undefined && !!coreCallbackStore[callbackKey]) { removeFunction(coreCallbackStore[callbackKey]); coreCallbackStore[callbackKey] = null; } // add the new function pointer to the store if present if (!!callback) coreCallbackStore[callbackKey] = addFunction(callback, 'vi'); }); // add the callbacks from the store to the core addCoreCallbacks( coreCallbackStore.alarmCallbackPtr, coreCallbackStore.coreCrashedCallbackPtr, coreCallbackStore.keysReadCallbackPtr, coreCallbackStore.saveDataUpdatedCallbackPtr, coreCallbackStore.videoFrameEndedCallbackPtr, coreCallbackStore.videoFrameStartedCallbackPtr, coreCallbackStore.autoSaveStateCapturedCallbackPtr, coreCallbackStore.autoSaveStateLoadedCallbackPtr ); }; Module.toggleRewind = (toggle) => { const toggleRewind = cwrap('toggleRewind', null, ['boolean']); toggleRewind(toggle); }; Module.setCoreSettings = (coreSettings) => { const setIntegerCoreSetting = cwrap('setIntegerCoreSetting', null, [ 'string', 'number', ]); if (coreSettings.allowOpposingDirections !== undefined) setIntegerCoreSetting( 'allowOpposingDirections', coreSettings.allowOpposingDirections ); if (coreSettings.rewindBufferCapacity !== undefined) setIntegerCoreSetting( 'rewindBufferCapacity', coreSettings.rewindBufferCapacity ); if (coreSettings.rewindBufferInterval !== undefined) setIntegerCoreSetting( 'rewindBufferInterval', coreSettings.rewindBufferInterval ); if (coreSettings?.frameSkip !== undefined) setIntegerCoreSetting('frameSkip', coreSettings.frameSkip); if (coreSettings.audioSampleRate !== undefined) setIntegerCoreSetting('audioSampleRate', coreSettings.audioSampleRate); if (coreSettings.audioBufferSize !== undefined) setIntegerCoreSetting('audioBufferSize', coreSettings.audioBufferSize); if (coreSettings.videoSync !== undefined) setIntegerCoreSetting('videoSync', coreSettings.videoSync); if (coreSettings.audioSync !== undefined) setIntegerCoreSetting('audioSync', coreSettings.audioSync); if (coreSettings.threadedVideo !== undefined) setIntegerCoreSetting('threadedVideo', coreSettings.threadedVideo); if (coreSettings.rewindEnable !== undefined) setIntegerCoreSetting('rewindEnable', coreSettings.rewindEnable); if (coreSettings.baseFpsTarget !== undefined) setIntegerCoreSetting('baseFpsTarget', coreSettings.baseFpsTarget); if (coreSettings.timestepSync !== undefined) setIntegerCoreSetting('timestepSync', coreSettings.timestepSync); if (coreSettings.showFpsCounter !== undefined) setIntegerCoreSetting('showFpsCounter', coreSettings.showFpsCounter); if (coreSettings.autoSaveStateTimerIntervalSeconds !== undefined) setIntegerCoreSetting( 'autoSaveStateTimerIntervalSeconds', coreSettings.autoSaveStateTimerIntervalSeconds ); if (coreSettings.autoSaveStateEnable !== undefined) setIntegerCoreSetting( 'autoSaveStateEnable', coreSettings.autoSaveStateEnable ); if (coreSettings.restoreAutoSaveStateOnLoad !== undefined) setIntegerCoreSetting( 'restoreAutoSaveStateOnLoad', coreSettings.restoreAutoSaveStateOnLoad ); }; // end include: /home/mgba/src/src/platform/wasm/pre.js // Sometimes an existing Module object exists with properties // meant to overwrite the default module functionality. Here // we collect those properties and reapply _after_ we configure // the current environment's defaults to avoid having to be so // defensive during initialization. var moduleOverrides = Object.assign({}, Module); var arguments_ = []; var thisProgram = './this.program'; var quit_ = (status, toThrow) => { throw toThrow; }; // `/` should be present at the end if `scriptDirectory` is not empty var scriptDirectory = ''; function locateFile(path) { if (Module['locateFile']) { return Module['locateFile'](path, scriptDirectory); } return scriptDirectory + path; } // Hooks that are implemented differently in different runtime environments. var readAsync, readBinary; // Note that this includes Node.js workers when relevant (pthreads is enabled). // Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and // ENVIRONMENT_IS_NODE. if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { if (ENVIRONMENT_IS_WORKER) { // Check worker, not web, since window could be polyfilled scriptDirectory = self.location.href; } else if (typeof document != 'undefined' && document.currentScript) { // web scriptDirectory = document.currentScript.src; } // When MODULARIZE, this JS may be executed later, after document.currentScript // is gone, so we saved it, and we use it here instead of any other info. if (_scriptName) { scriptDirectory = _scriptName; } // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them. // otherwise, slice off the final part of the url to find the script directory. // if scriptDirectory does not contain a slash, lastIndexOf will return -1, // and scriptDirectory will correctly be replaced with an empty string. // If scriptDirectory contains a query (starting with ?) or a fragment (starting with #), // they are removed because they could contain a slash. if (scriptDirectory.startsWith('blob:')) { scriptDirectory = ''; } else { scriptDirectory = scriptDirectory.slice(0, scriptDirectory.replace(/[?#].*/, '').lastIndexOf('/')+1); } { // include: web_or_worker_shell_read.js if (ENVIRONMENT_IS_WORKER) { readBinary = (url) => { var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); xhr.responseType = 'arraybuffer'; xhr.send(null); return new Uint8Array(/** @type{!ArrayBuffer} */(xhr.response)); }; } readAsync = async (url) => { var response = await fetch(url, { credentials: 'same-origin' }); if (response.ok) { return response.arrayBuffer(); } throw new Error(response.status + ' : ' + response.url); }; // end include: web_or_worker_shell_read.js } } else { } var out = Module['print'] || console.log.bind(console); var err = Module['printErr'] || console.error.bind(console); // Merge back in the overrides Object.assign(Module, moduleOverrides); // Free the object hierarchy contained in the overrides, this lets the GC // reclaim data used. moduleOverrides = null; // Emit code to handle expected values on the Module object. This applies Module.x // to the proper local x. This has two benefits: first, we only emit it if it is // expected to arrive, and second, by using a local everywhere else that can be // minified. if (Module['arguments']) arguments_ = Module['arguments']; if (Module['thisProgram']) thisProgram = Module['thisProgram']; // perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message // end include: shell.js // include: preamble.js // === Preamble library stuff === // Documentation for the public APIs defined in this file must be updated in: // site/source/docs/api_reference/preamble.js.rst // A prebuilt local version of the documentation is available at: // site/build/text/docs/api_reference/preamble.js.txt // You can also build docs locally as HTML or other formats in site/ // An online HTML version (which may be of a different version of Emscripten) // is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html var wasmBinary = Module['wasmBinary']; // Wasm globals var wasmMemory; // For sending to workers. var wasmModule; //======================================== // Runtime essentials //======================================== // whether we are quitting the application. no code should run after this. // set in exit() and abort() var ABORT = false; // set by exit() and abort(). Passed to 'onExit' handler. // NOTE: This is also used as the process return code code in shell environments // but only when noExitRuntime is false. var EXITSTATUS; // In STRICT mode, we only define assert() when ASSERTIONS is set. i.e. we // don't define it at all in release modes. This matches the behaviour of // MINIMAL_RUNTIME. // TODO(sbc): Make this the default even without STRICT enabled. /** @type {function(*, string=)} */ function assert(condition, text) { if (!condition) { // This build was created without ASSERTIONS defined. `assert()` should not // ever be called in this configuration but in case there are callers in // the wild leave this simple abort() implementation here for now. abort(text); } } // Memory management var HEAP, /** @type {!Int8Array} */ HEAP8, /** @type {!Uint8Array} */ HEAPU8, /** @type {!Int16Array} */ HEAP16, /** @type {!Uint16Array} */ HEAPU16, /** @type {!Int32Array} */ HEAP32, /** @type {!Uint32Array} */ HEAPU32, /** @type {!Float32Array} */ HEAPF32, /* BigInt64Array type is not correctly defined in closure /** not-@type {!BigInt64Array} */ HEAP64, /* BigUint64Array type is not correctly defined in closure /** not-t@type {!BigUint64Array} */ HEAPU64, /** @type {!Float64Array} */ HEAPF64; var runtimeInitialized = false; /** * Indicates whether filename is delivered via file protocol (as opposed to http/https) * @noinline */ var isFileURI = (filename) => filename.startsWith('file://'); // include: runtime_shared.js // include: runtime_stack_check.js // end include: runtime_stack_check.js // include: runtime_exceptions.js // end include: runtime_exceptions.js // include: runtime_debug.js // end include: runtime_debug.js // include: memoryprofiler.js // end include: memoryprofiler.js // include: runtime_pthread.js // Pthread Web Worker handling code. // This code runs only on pthread web workers and handles pthread setup // and communication with the main thread via postMessage. if (ENVIRONMENT_IS_PTHREAD) { var wasmModuleReceived; // Thread-local guard variable for one-time init of the JS state var initializedJS = false; function threadPrintErr(...args) { var text = args.join(' '); console.error(text); } if (!Module['printErr']) err = threadPrintErr; function threadAlert(...args) { var text = args.join(' '); postMessage({cmd: 'alert', text, threadId: _pthread_self()}); } self.alert = threadAlert; // Turn unhandled rejected promises into errors so that the main thread will be // notified about them. self.onunhandledrejection = (e) => { throw e.reason || e; }; function handleMessage(e) { try { var msgData = e['data']; //dbg('msgData: ' + Object.keys(msgData)); var cmd = msgData.cmd; if (cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code. // Until we initialize the runtime, queue up any further incoming messages. let messageQueue = []; self.onmessage = (e) => messageQueue.push(e); // And add a callback for when the runtime is initialized. self.startWorker = (instance) => { // Notify the main thread that this thread has loaded. postMessage({ cmd: 'loaded' }); // Process any messages that were queued before the thread was ready. for (let msg of messageQueue) { handleMessage(msg); } // Restore the real message handler. self.onmessage = handleMessage; }; // Use `const` here to ensure that the variable is scoped only to // that iteration, allowing safe reference from a closure. for (const handler of msgData.handlers) { // The the main module has a handler for a certain even, but no // handler exists on the pthread worker, then proxy that handler // back to the main thread. if (!Module[handler] || Module[handler].proxy) { Module[handler] = (...args) => { postMessage({ cmd: 'callHandler', handler, args: args }); } // Rebind the out / err handlers if needed if (handler == 'print') out = Module[handler]; if (handler == 'printErr') err = Module[handler]; } } wasmMemory = msgData.wasmMemory; updateMemoryViews(); wasmModuleReceived(msgData.wasmModule); } else if (cmd === 'run') { // Call inside JS module to set up the stack frame for this pthread in JS module scope. // This needs to be the first thing that we do, as we cannot call to any C/C++ functions // until the thread stack is initialized. establishStackSpace(msgData.pthread_ptr); // Pass the thread address to wasm to store it for fast access. __emscripten_thread_init(msgData.pthread_ptr, /*is_main=*/0, /*is_runtime=*/0, /*can_block=*/1, 0, 0); PThread.threadInitTLS(); // Await mailbox notifications with `Atomics.waitAsync` so we can start // using the fast `Atomics.notify` notification path. __emscripten_thread_mailbox_await(msgData.pthread_ptr); if (!initializedJS) { initializedJS = true; } try { invokeEntryPoint(msgData.start_routine, msgData.arg); } catch(ex) { if (ex != 'unwind') { // The pthread "crashed". Do not call `_emscripten_thread_exit` (which // would make this thread joinable). Instead, re-throw the exception // and let the top level handler propagate it back to the main thread. throw ex; } } } else if (msgData.target === 'setimmediate') { // no-op } else if (cmd === 'checkMailbox') { if (initializedJS) { checkMailbox(); } } else if (cmd) { // The received message looks like something that should be handled by this message // handler, (since there is a cmd field present), but is not one of the // recognized commands: err(`worker: received unknown command ${cmd}`); err(msgData); } } catch(ex) { __emscripten_thread_crashed(); throw ex; } }; self.onmessage = handleMessage; } // ENVIRONMENT_IS_PTHREAD // end include: runtime_pthread.js function updateMemoryViews() { var b = wasmMemory.buffer; Module['HEAP8'] = HEAP8 = new Int8Array(b); Module['HEAP16'] = HEAP16 = new Int16Array(b); Module['HEAPU8'] = HEAPU8 = new Uint8Array(b); Module['HEAPU16'] = HEAPU16 = new Uint16Array(b); Module['HEAP32'] = HEAP32 = new Int32Array(b); Module['HEAPU32'] = HEAPU32 = new Uint32Array(b); Module['HEAPF32'] = HEAPF32 = new Float32Array(b); Module['HEAPF64'] = HEAPF64 = new Float64Array(b); Module['HEAP64'] = HEAP64 = new BigInt64Array(b); Module['HEAPU64'] = HEAPU64 = new BigUint64Array(b); } // end include: runtime_shared.js // In non-standalone/normal mode, we create the memory here. // include: runtime_init_memory.js // Create the wasm memory. (Note: this only applies if IMPORTED_MEMORY is defined) // check for full engine support (use string 'subarray' to avoid closure compiler confusion) if (!ENVIRONMENT_IS_PTHREAD) { if (Module['wasmMemory']) { wasmMemory = Module['wasmMemory']; } else { var INITIAL_MEMORY = Module['INITIAL_MEMORY'] || 268435456; /** @suppress {checkTypes} */ wasmMemory = new WebAssembly.Memory({ 'initial': INITIAL_MEMORY / 65536, 'maximum': INITIAL_MEMORY / 65536, 'shared': true, }); } updateMemoryViews(); } // end include: runtime_init_memory.js function preRun() { if (Module['preRun']) { if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; while (Module['preRun'].length) { addOnPreRun(Module['preRun'].shift()); } } callRuntimeCallbacks(onPreRuns); } function initRuntime() { runtimeInitialized = true; if (ENVIRONMENT_IS_PTHREAD) return startWorker(Module); if (!Module['noFSInit'] && !FS.initialized) FS.init(); TTY.init(); wasmExports['__wasm_call_ctors'](); FS.ignorePermissions = false; } function preMain() { } function postRun() { if (ENVIRONMENT_IS_PTHREAD) return; // PThreads reuse the runtime from the main thread. if (Module['postRun']) { if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; while (Module['postRun'].length) { addOnPostRun(Module['postRun'].shift()); } } callRuntimeCallbacks(onPostRuns); } // A counter of dependencies for calling run(). If we need to // do asynchronous work before running, increment this and // decrement it. Incrementing must happen in a place like // Module.preRun (used by emcc to add file preloading). // Note that you can add dependencies in preRun, even though // it happens right before run - run will be postponed until // the dependencies are met. var runDependencies = 0; var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled function getUniqueRunDependency(id) { return id; } function addRunDependency(id) { runDependencies++; Module['monitorRunDependencies']?.(runDependencies); } function removeRunDependency(id) { runDependencies--; Module['monitorRunDependencies']?.(runDependencies); if (runDependencies == 0) { if (dependenciesFulfilled) { var callback = dependenciesFulfilled; dependenciesFulfilled = null; callback(); // can add another dependenciesFulfilled } } } /** @param {string|number=} what */ function abort(what) { Module['onAbort']?.(what); what = 'Aborted(' + what + ')'; // TODO(sbc): Should we remove printing and leave it up to whoever // catches the exception? err(what); ABORT = true; what += '. Build with -sASSERTIONS for more info.'; // Use a wasm runtime error, because a JS error might be seen as a foreign // exception, which means we'd run destructors on it. We need the error to // simply make the program stop. // FIXME This approach does not work in Wasm EH because it currently does not assume // all RuntimeErrors are from traps; it decides whether a RuntimeError is from // a trap or not based on a hidden field within the object. So at the moment // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that // allows this in the wasm spec. // Suppress closure compiler warning here. Closure compiler's builtin extern // definition for WebAssembly.RuntimeError claims it takes no arguments even // though it can. // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. /** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError(what); readyPromiseReject(e); // Throw the error whether or not MODULARIZE is set because abort is used // in code paths apart from instantiation where an exception is expected // to be thrown when abort is called. throw e; } var wasmBinaryFile; function findWasmBinary() { if (Module['locateFile']) { return locateFile('mgba.wasm'); } // Use bundler-friendly `new URL(..., import.meta.url)` pattern; works in browsers too. return new URL('mgba.wasm', import.meta.url).href; } function getBinarySync(file) { if (file == wasmBinaryFile && wasmBinary) { return new Uint8Array(wasmBinary); } if (readBinary) { return readBinary(file); } throw 'both async and sync fetching of the wasm failed'; } async function getWasmBinary(binaryFile) { // If we don't have the binary yet, load it asynchronously using readAsync. if (!wasmBinary) { // Fetch the binary using readAsync try { var response = await readAsync(binaryFile); return new Uint8Array(response); } catch { // Fall back to getBinarySync below; } } // Otherwise, getBinarySync should be able to get it synchronously return getBinarySync(binaryFile); } async function instantiateArrayBuffer(binaryFile, imports) { try { var binary = await getWasmBinary(binaryFile); var instance = await WebAssembly.instantiate(binary, imports); return instance; } catch (reason) { err(`failed to asynchronously prepare wasm: ${reason}`); abort(reason); } } async function instantiateAsync(binary, binaryFile, imports) { if (!binary && typeof WebAssembly.instantiateStreaming == 'function' ) { try { var response = fetch(binaryFile, { credentials: 'same-origin' }); var instantiationResult = await WebAssembly.instantiateStreaming(response, imports); return instantiationResult; } catch (reason) { // We expect the most common failure cause to be a bad MIME type for the binary, // in which case falling back to ArrayBuffer instantiation should work. err(`wasm streaming compile failed: ${reason}`); err('falling back to ArrayBuffer instantiation'); // fall back of instantiateArrayBuffer below }; } return instantiateArrayBuffer(binaryFile, imports); } function getWasmImports() { assignWasmImports(); // prepare imports return { 'env': wasmImports, 'wasi_snapshot_preview1': wasmImports, } } // Create the wasm instance. // Receives the wasm imports, returns the exports. async function createWasm() { // Load the wasm module and create an instance of using native support in the JS engine. // handle a generated wasm instance, receiving its exports and // performing other necessary setup /** @param {WebAssembly.Module=} module*/ function receiveInstance(instance, module) { wasmExports = instance.exports; registerTLSInit(wasmExports['_emscripten_tls_init']); wasmTable = wasmExports['__indirect_function_table']; // We now have the Wasm module loaded up, keep a reference to the compiled module so we can post it to the workers. wasmModule = module; removeRunDependency('wasm-instantiate'); return wasmExports; } // wait for the pthread pool (if any) addRunDependency('wasm-instantiate'); // Prefer streaming instantiation if available. function receiveInstantiationResult(result) { // 'result' is a ResultObject object which has both the module and instance. // receiveInstance() will swap in the exports (to Module.asm) so they can be called return receiveInstance(result['instance'], result['module']); } var info = getWasmImports(); // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback // to manually instantiate the Wasm module themselves. This allows pages to // run the instantiation parallel to any other async startup actions they are // performing. // Also pthreads and wasm workers initialize the wasm instance through this // path. if (Module['instantiateWasm']) { return new Promise((resolve, reject) => { Module['instantiateWasm'](info, (mod, inst) => { receiveInstance(mod, inst); resolve(mod.exports); }); }); } if (ENVIRONMENT_IS_PTHREAD) { return new Promise((resolve) => { wasmModuleReceived = (module) => { // Instantiate from the module posted from the main thread. // We can just use sync instantiation in the worker. var instance = new WebAssembly.Instance(module, getWasmImports()); resolve(receiveInstance(instance, module)); }; }); } wasmBinaryFile ??= findWasmBinary(); try { var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); var exports = receiveInstantiationResult(result); return exports; } catch (e) { // If instantiation fails, reject the module ready promise. readyPromiseReject(e); return Promise.reject(e); } } // === Body === var ASM_CONSTS = { 310352: ($0, $1) => { Module.canvas.width = $0; Module.canvas.height = $1; }, 310409: () => { console.error("thread instantiation failed") }, 310458: ($0, $1, $2, $3, $4, $5, $6) => { Module.version = { gitCommit : UTF8ToString($0), gitShort : UTF8ToString($1), gitBranch : UTF8ToString($2), gitRevision : $3, binaryName : UTF8ToString($4), projectName : UTF8ToString($5), projectVersion : UTF8ToString($6) }; }, 310690: ($0, $1) => { const funcPtr = $0; const ctx = $1; const func = wasmTable.get(funcPtr); if (func) func(ctx); }, 310788: ($0, $1) => { const funcPtr = $0; const ctx = $1; const func = wasmTable.get(funcPtr); if (func) func(ctx); }, 310886: ($0, $1) => { const funcPtr = $0; const ctx = $1; const func = wasmTable.get(funcPtr); if (func) func(ctx); }, 310984: ($0, $1) => { const funcPtr = $0; const ctx = $1; const func = wasmTable.get(funcPtr); if (func) func(ctx); }, 311082: ($0, $1) => { const funcPtr = $0; const ctx = $1; const func = wasmTable.get(funcPtr); if (func) func(ctx); }, 311180: ($0, $1) => { const funcPtr = $0; const ctx = $1; const func = wasmTable.get(funcPtr); if (func) func(ctx); }, 311278: () => { FS.syncfs(function (err) { assert(!err); }) }, 311322: ($0) => { var str = UTF8ToString($0) + '\n\n' + 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :'; var reply = window.prompt(str, "i"); if (reply === null) { reply = "i"; } return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL); }, 311547: () => { if (typeof(AudioContext) !== 'undefined') { return true; } else if (typeof(webkitAudioContext) !== 'undefined') { return true; } return false; }, 311694: () => { if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) { return true; } else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') { return true; } return false; }, 311928: ($0) => { if(typeof(Module['SDL2']) === 'undefined') { Module['SDL2'] = {}; } var SDL2 = Module['SDL2']; if (!$0) { SDL2.audio = {}; } else { SDL2.capture = {}; } if (!SDL2.audioContext) { if (typeof(AudioContext) !== 'undefined') { SDL2.audioContext = new AudioContext(); } else if (typeof(webkitAudioContext) !== 'undefined') { SDL2.audioContext = new webkitAudioContext(); } if (SDL2.audioContext) { if ((typeof navigator.userActivation) === 'undefined') { autoResumeAudioContext(SDL2.audioContext); } } } return SDL2.audioContext === undefined ? -1 : 0; }, 312480: () => { var SDL2 = Module['SDL2']; return SDL2.audioContext.sampleRate; }, 312548: ($0, $1, $2, $3) => { var SDL2 = Module['SDL2']; var have_microphone = function(stream) { if (SDL2.capture.silenceTimer !== undefined) { clearInterval(SDL2.capture.silenceTimer); SDL2.capture.silenceTimer = undefined; SDL2.capture.silenceBuffer = undefined } SDL2.capture.mediaStreamNode = SDL2.audioContext.createMediaStreamSource(stream); SDL2.capture.scriptProcessorNode = SDL2.audioContext.createScriptProcessor($1, $0, 1); SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) { if ((SDL2 === undefined) || (SDL2.capture === undefined)) { return; } audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0); SDL2.capture.currentCaptureBuffer = audioProcessingEvent.inputBuffer; dynCall('vi', $2, [$3]); }; SDL2.capture.mediaStreamNode.connect(SDL2.capture.scriptProcessorNode); SDL2.capture.scriptProcessorNode.connect(SDL2.audioContext.destination); SDL2.capture.stream = stream; }; var no_microphone = function(error) { }; SDL2.capture.silenceBuffer = SDL2.audioContext.createBuffer($0, $1, SDL2.audioContext.sampleRate); SDL2.capture.silenceBuffer.getChannelData(0).fill(0.0); var silence_callback = function() { SDL2.capture.currentCaptureBuffer = SDL2.capture.silenceBuffer; dynCall('vi', $2, [$3]); }; SDL2.capture.silenceTimer = setInterval(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000); if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) { navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone); } else if (navigator.webkitGetUserMedia !== undefined) { navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone); } }, 314241: ($0, $1, $2, $3) => { var SDL2 = Module['SDL2']; SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0); SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) { if ((SDL2 === undefined) || (SDL2.audio === undefined)) { return; } if (SDL2.audio.silenceTimer !== undefined) { clearInterval(SDL2.audio.silenceTimer); SDL2.audio.silenceTimer = undefined; SDL2.audio.silenceBuffer = undefined; } SDL2.audio.currentOutputBuffer = e['outputBuffer']; dynCall('vi', $2, [$3]); }; SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']); if (SDL2.audioContext.state === 'suspended') { SDL2.audio.silenceBuffer = SDL2.audioContext.createBuffer($0, $1, SDL2.audioContext.sampleRate); SDL2.audio.silenceBuffer.getChannelData(0).fill(0.0); var silence_callback = function() { if ((typeof navigator.userActivation) !== 'undefined') { if (navigator.userActivation.hasBeenActive) { SDL2.audioContext.resume(); } } SDL2.audio.currentOutputBuffer = SDL2.audio.silenceBuffer; dynCall('vi', $2, [$3]); SDL2.audio.currentOutputBuffer = undefined; }; SDL2.audio.silenceTimer = setInterval(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000); } }, 315416: ($0, $1) => { var SDL2 = Module['SDL2']; var numChannels = SDL2.capture.currentCaptureBuffer.numberOfChannels; for (var c = 0; c < numChannels; ++c) { var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c); if (channelData.length != $1) { throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; } if (numChannels == 1) { for (var j = 0; j < $1; ++j) { setValue($0 + (j * 4), channelData[j], 'float'); } } else { for (var j = 0; j < $1; ++j) { setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float'); } } } }, 316021: ($0, $1) => { var SDL2 = Module['SDL2']; var buf = $0 >>> 2; var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels']; for (var c = 0; c < numChannels; ++c) { var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c); if (channelData.length != $1) { throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; } for (var j = 0; j < $1; ++j) { channelData[j] = HEAPF32[buf + (j*numChannels + c)]; } } }, 316510: ($0) => { var SDL2 = Module['SDL2']; if ($0) { if (SDL2.capture.silenceTimer !== undefined) { clearInterval(SDL2.capture.silenceTimer); } if (SDL2.capture.stream !== undefined) { var tracks = SDL2.capture.stream.getAudioTracks(); for (var i = 0; i < tracks.length; i++) { SDL2.capture.stream.removeTrack(tracks[i]); } } if (SDL2.capture.scriptProcessorNode !== undefined) { SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {}; SDL2.capture.scriptProcessorNode.disconnect(); } if (SDL2.capture.mediaStreamNode !== undefined) { SDL2.capture.mediaStreamNode.disconnect(); } SDL2.capture = undefined; } else { if (SDL2.audio.scriptProcessorNode != undefined) { SDL2.audio.scriptProcessorNode.disconnect(); } if (SDL2.audio.silenceTimer !== undefined) { clearInterval(SDL2.audio.silenceTimer); } SDL2.audio = undefined; } if ((SDL2.audioContext !== undefined) && (SDL2.audio === undefined) && (SDL2.capture === undefined)) { SDL2.audioContext.close(); SDL2.audioContext = undefined; } }, 317516: ($0, $1, $2) => { var w = $0; var h = $1; var pixels = $2; if (!Module['SDL2']) Module['SDL2'] = {}; var SDL2 = Module['SDL2']; if (SDL2.ctxCanvas !== Module['canvas']) { SDL2.ctx = Module['createContext'](Module['canvas'], false, true); SDL2.ctxCanvas = Module['canvas']; } if (SDL2.w !== w || SDL2.h !== h || SDL2.imageCtx !== SDL2.ctx) { SDL2.image = SDL2.ctx.createImageData(w, h); SDL2.w = w; SDL2.h = h; SDL2.imageCtx = SDL2.ctx; } var data = SDL2.image.data; var src = pixels / 4; var dst = 0; var num; if (typeof CanvasPixelArray !== 'undefined' && data instanceof CanvasPixelArray) { num = data.length; while (dst < num) { var val = HEAP32[src]; data[dst ] = val & 0xff; data[dst+1] = (val >> 8) & 0xff; data[dst+2] = (val >> 16) & 0xff; data[dst+3] = 0xff; src++; dst += 4; } } else { if (SDL2.data32Data !== data) { SDL2.data32 = new Int32Array(data.buffer); SDL2.data8 = new Uint8Array(data.buffer); SDL2.data32Data = data; } var data32 = SDL2.data32; num = data32.length; data32.set(HEAP32.subarray(src, src + num)); var data8 = SDL2.data8; var i = 3; var j = i + 4*num; if (num % 8 == 0) { while (i < j) { data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; data8[i] = 0xff; i = i + 4 | 0; } } else { while (i < j) { data8[i] = 0xff; i = i + 4 | 0; } } } SDL2.ctx.putImageData(SDL2.image, 0, 0); }, 318984: ($0, $1, $2, $3, $4) => { var w = $0; var h = $1; var hot_x = $2; var hot_y = $3; var pixels = $4; var canvas = document.createElement("canvas"); canvas.width = w; canvas.height = h; var ctx = canvas.getContext("2d"); var image = ctx.createImageData(w, h); var data = image.data; var src = pixels / 4; var dst = 0; var num; if (typeof CanvasPixelArray !== 'undefined' && data instanceof CanvasPixelArray) { num = data.length; while (dst < num) { var val = HEAP32[src]; data[dst ] = val & 0xff; data[dst+1] = (val >> 8) & 0xff; data[dst+2] = (val >> 16) & 0xff; data[dst+3] = (val >> 24) & 0xff; src++; dst += 4; } } else { var data32 = new Int32Array(data.buffer); num = data32.length; data32.set(HEAP32.subarray(src, src + num)); } ctx.putImageData(image, 0, 0); var url = hot_x === 0 && hot_y === 0 ? "url(" + canvas.toDataURL() + "), auto" : "url(" + canvas.toDataURL() + ") " + hot_x + " " + hot_y + ", auto"; var urlBuf = _malloc(url.length + 1); stringToUTF8(url, urlBuf, url.length + 1); return urlBuf; }, 319972: ($0) => { if (Module['canvas']) { Module['canvas'].style['cursor'] = UTF8ToString($0); } }, 320055: () => { if (Module['canvas']) { Module['canvas'].style['cursor'] = 'none'; } }, 320124: () => { return window.innerWidth; }, 320154: () => { return window.innerHeight; } }; // end include: preamble.js class ExitStatus { name = 'ExitStatus'; constructor(status) { this.message = `Program terminated with exit(${status})`; this.status = status; } } var terminateWorker = (worker) => { worker.terminate(); // terminate() can be asynchronous, so in theory the worker can continue // to run for some amount of time after termination. However from our POV // the worker now dead and we don't want to hear from it again, so we stub // out its message handler here. This avoids having to check in each of // the onmessage handlers if the message was coming from valid worker. worker.onmessage = (e) => { }; }; var cleanupThread = (pthread_ptr) => { var worker = PThread.pthreads[pthread_ptr]; PThread.returnWorkerToPool(worker); }; var callRuntimeCallbacks = (callbacks) => { while (callbacks.length > 0) { // Pass the module as the first argument. callbacks.shift()(Module); } }; var onPreRuns = []; var addOnPreRun = (cb) => onPreRuns.unshift(cb); var spawnThread = (threadParams) => { var worker = PThread.getNewWorker(); if (!worker) { // No available workers in the PThread pool. return 6; } PThread.runningWorkers.push(worker); // Add to pthreads map PThread.pthreads[threadParams.pthread_ptr] = worker; worker.pthread_ptr = threadParams.pthread_ptr; var msg = { cmd: 'run',