UNPKG

nostalgist

Version:

Nostalgist.js is a JavaScript library that allows you to run emulators of retro consoles within web browsers.

1,531 lines 97.2 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); const systemCoreMap = { gb: "mgba", gba: "mgba", gbc: "mgba", megadrive: "genesis_plus_gx", nes: "fceumm", snes: "snes9x" }; function getDefaultExportFromCjs(x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x; } var ini$3; var hasRequiredIni; function requireIni() { if (hasRequiredIni) return ini$3; hasRequiredIni = 1; const { hasOwnProperty } = Object.prototype; const encode = (obj, opt = {}) => { if (typeof opt === "string") { opt = { section: opt }; } opt.align = opt.align === true; opt.newline = opt.newline === true; opt.sort = opt.sort === true; opt.whitespace = opt.whitespace === true || opt.align === true; opt.platform = opt.platform || typeof process !== "undefined" && process.platform; opt.bracketedArray = opt.bracketedArray !== false; const eol = opt.platform === "win32" ? "\r\n" : "\n"; const separator = opt.whitespace ? " = " : "="; const children = []; const keys = opt.sort ? Object.keys(obj).sort() : Object.keys(obj); let padToChars = 0; if (opt.align) { padToChars = safe( keys.filter((k) => obj[k] === null || Array.isArray(obj[k]) || typeof obj[k] !== "object").map((k) => Array.isArray(obj[k]) ? `${k}[]` : k).concat([""]).reduce((a, b) => safe(a).length >= safe(b).length ? a : b) ).length; } let out = ""; const arraySuffix = opt.bracketedArray ? "[]" : ""; for (const k of keys) { const val = obj[k]; if (val && Array.isArray(val)) { for (const item of val) { out += safe(`${k}${arraySuffix}`).padEnd(padToChars, " ") + separator + safe(item) + eol; } } else if (val && typeof val === "object") { children.push(k); } else { out += safe(k).padEnd(padToChars, " ") + separator + safe(val) + eol; } } if (opt.section && out.length) { out = "[" + safe(opt.section) + "]" + (opt.newline ? eol + eol : eol) + out; } for (const k of children) { const nk = splitSections(k, ".").join("\\."); const section = (opt.section ? opt.section + "." : "") + nk; const child = encode(obj[k], { ...opt, section }); if (out.length && child.length) { out += eol; } out += child; } return out; }; function splitSections(str, separator) { var lastMatchIndex = 0; var lastSeparatorIndex = 0; var nextIndex = 0; var sections = []; do { nextIndex = str.indexOf(separator, lastMatchIndex); if (nextIndex !== -1) { lastMatchIndex = nextIndex + separator.length; if (nextIndex > 0 && str[nextIndex - 1] === "\\") { continue; } sections.push(str.slice(lastSeparatorIndex, nextIndex)); lastSeparatorIndex = nextIndex + separator.length; } } while (nextIndex !== -1); sections.push(str.slice(lastSeparatorIndex)); return sections; } const decode = (str, opt = {}) => { opt.bracketedArray = opt.bracketedArray !== false; const out = /* @__PURE__ */ Object.create(null); let p = out; let section = null; const re = /^\[([^\]]*)\]\s*$|^([^=]+)(=(.*))?$/i; const lines = str.split(/[\r\n]+/g); const duplicates = {}; for (const line of lines) { if (!line || line.match(/^\s*[;#]/) || line.match(/^\s*$/)) { continue; } const match = line.match(re); if (!match) { continue; } if (match[1] !== void 0) { section = unsafe(match[1]); if (section === "__proto__") { p = /* @__PURE__ */ Object.create(null); continue; } p = out[section] = out[section] || /* @__PURE__ */ Object.create(null); continue; } const keyRaw = unsafe(match[2]); let isArray; if (opt.bracketedArray) { isArray = keyRaw.length > 2 && keyRaw.slice(-2) === "[]"; } else { duplicates[keyRaw] = ((duplicates == null ? void 0 : duplicates[keyRaw]) || 0) + 1; isArray = duplicates[keyRaw] > 1; } const key = isArray && keyRaw.endsWith("[]") ? keyRaw.slice(0, -2) : keyRaw; if (key === "__proto__") { continue; } const valueRaw = match[3] ? unsafe(match[4]) : true; const value = valueRaw === "true" || valueRaw === "false" || valueRaw === "null" ? JSON.parse(valueRaw) : valueRaw; if (isArray) { if (!hasOwnProperty.call(p, key)) { p[key] = []; } else if (!Array.isArray(p[key])) { p[key] = [p[key]]; } } if (Array.isArray(p[key])) { p[key].push(value); } else { p[key] = value; } } const remove = []; for (const k of Object.keys(out)) { if (!hasOwnProperty.call(out, k) || typeof out[k] !== "object" || Array.isArray(out[k])) { continue; } const parts = splitSections(k, "."); p = out; const l = parts.pop(); const nl = l.replace(/\\\./g, "."); for (const part of parts) { if (part === "__proto__") { continue; } if (!hasOwnProperty.call(p, part) || typeof p[part] !== "object") { p[part] = /* @__PURE__ */ Object.create(null); } p = p[part]; } if (p === out && nl === l) { continue; } p[nl] = out[k]; remove.push(k); } for (const del of remove) { delete out[del]; } return out; }; const isQuoted = (val) => { return val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'"); }; const safe = (val) => { if (typeof val !== "string" || val.match(/[=\r\n]/) || val.match(/^\[/) || val.length > 1 && isQuoted(val) || val !== val.trim()) { return JSON.stringify(val); } return val.split(";").join("\\;").split("#").join("\\#"); }; const unsafe = (val) => { val = (val || "").trim(); if (isQuoted(val)) { if (val.charAt(0) === "'") { val = val.slice(1, -1); } try { val = JSON.parse(val); } catch { } } else { let esc = false; let unesc = ""; for (let i2 = 0, l = val.length; i2 < l; i2++) { const c = val.charAt(i2); if (esc) { if ("\\;#".indexOf(c) !== -1) { unesc += c; } else { unesc += "\\" + c; } esc = false; } else if (";#".indexOf(c) !== -1) { break; } else if (c === "\\") { esc = true; } else { unesc += c; } } if (esc) { unesc += "\\"; } return unesc.trim(); } return val; }; ini$3 = { parse: decode, decode, stringify: encode, encode, safe, unsafe }; return ini$3; } var iniExports = requireIni(); const ini$2 = /* @__PURE__ */ getDefaultExportFromCjs(iniExports); var pathBrowserify; var hasRequiredPathBrowserify; function requirePathBrowserify() { if (hasRequiredPathBrowserify) return pathBrowserify; hasRequiredPathBrowserify = 1; function assertPath(path2) { if (typeof path2 !== "string") { throw new TypeError("Path must be a string. Received " + JSON.stringify(path2)); } } function normalizeStringPosix(path2, allowAboveRoot) { var res = ""; var lastSegmentLength = 0; var lastSlash = -1; var dots = 0; var code; for (var i2 = 0; i2 <= path2.length; ++i2) { if (i2 < path2.length) code = path2.charCodeAt(i2); else if (code === 47) break; else code = 47; if (code === 47) { if (lastSlash === i2 - 1 || dots === 1) ; else if (lastSlash !== i2 - 1 && dots === 2) { if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) { if (res.length > 2) { var lastSlashIndex = res.lastIndexOf("/"); if (lastSlashIndex !== res.length - 1) { if (lastSlashIndex === -1) { res = ""; lastSegmentLength = 0; } else { res = res.slice(0, lastSlashIndex); lastSegmentLength = res.length - 1 - res.lastIndexOf("/"); } lastSlash = i2; dots = 0; continue; } } else if (res.length === 2 || res.length === 1) { res = ""; lastSegmentLength = 0; lastSlash = i2; dots = 0; continue; } } if (allowAboveRoot) { if (res.length > 0) res += "/.."; else res = ".."; lastSegmentLength = 2; } } else { if (res.length > 0) res += "/" + path2.slice(lastSlash + 1, i2); else res = path2.slice(lastSlash + 1, i2); lastSegmentLength = i2 - lastSlash - 1; } lastSlash = i2; dots = 0; } else if (code === 46 && dots !== -1) { ++dots; } else { dots = -1; } } return res; } function _format(sep, pathObject) { var dir = pathObject.dir || pathObject.root; var base = pathObject.base || (pathObject.name || "") + (pathObject.ext || ""); if (!dir) { return base; } if (dir === pathObject.root) { return dir + base; } return dir + sep + base; } var posix = { // path.resolve([from ...], to) resolve: function resolve() { var resolvedPath = ""; var resolvedAbsolute = false; var cwd; for (var i2 = arguments.length - 1; i2 >= -1 && !resolvedAbsolute; i2--) { var path2; if (i2 >= 0) path2 = arguments[i2]; else { if (cwd === void 0) cwd = process.cwd(); path2 = cwd; } assertPath(path2); if (path2.length === 0) { continue; } resolvedPath = path2 + "/" + resolvedPath; resolvedAbsolute = path2.charCodeAt(0) === 47; } resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute); if (resolvedAbsolute) { if (resolvedPath.length > 0) return "/" + resolvedPath; else return "/"; } else if (resolvedPath.length > 0) { return resolvedPath; } else { return "."; } }, normalize: function normalize(path2) { assertPath(path2); if (path2.length === 0) return "."; var isAbsolute = path2.charCodeAt(0) === 47; var trailingSeparator = path2.charCodeAt(path2.length - 1) === 47; path2 = normalizeStringPosix(path2, !isAbsolute); if (path2.length === 0 && !isAbsolute) path2 = "."; if (path2.length > 0 && trailingSeparator) path2 += "/"; if (isAbsolute) return "/" + path2; return path2; }, isAbsolute: function isAbsolute(path2) { assertPath(path2); return path2.length > 0 && path2.charCodeAt(0) === 47; }, join: function join() { if (arguments.length === 0) return "."; var joined; for (var i2 = 0; i2 < arguments.length; ++i2) { var arg = arguments[i2]; assertPath(arg); if (arg.length > 0) { if (joined === void 0) joined = arg; else joined += "/" + arg; } } if (joined === void 0) return "."; return posix.normalize(joined); }, relative: function relative(from, to) { assertPath(from); assertPath(to); if (from === to) return ""; from = posix.resolve(from); to = posix.resolve(to); if (from === to) return ""; var fromStart = 1; for (; fromStart < from.length; ++fromStart) { if (from.charCodeAt(fromStart) !== 47) break; } var fromEnd = from.length; var fromLen = fromEnd - fromStart; var toStart = 1; for (; toStart < to.length; ++toStart) { if (to.charCodeAt(toStart) !== 47) break; } var toEnd = to.length; var toLen = toEnd - toStart; var length = fromLen < toLen ? fromLen : toLen; var lastCommonSep = -1; var i2 = 0; for (; i2 <= length; ++i2) { if (i2 === length) { if (toLen > length) { if (to.charCodeAt(toStart + i2) === 47) { return to.slice(toStart + i2 + 1); } else if (i2 === 0) { return to.slice(toStart + i2); } } else if (fromLen > length) { if (from.charCodeAt(fromStart + i2) === 47) { lastCommonSep = i2; } else if (i2 === 0) { lastCommonSep = 0; } } break; } var fromCode = from.charCodeAt(fromStart + i2); var toCode = to.charCodeAt(toStart + i2); if (fromCode !== toCode) break; else if (fromCode === 47) lastCommonSep = i2; } var out = ""; for (i2 = fromStart + lastCommonSep + 1; i2 <= fromEnd; ++i2) { if (i2 === fromEnd || from.charCodeAt(i2) === 47) { if (out.length === 0) out += ".."; else out += "/.."; } } if (out.length > 0) return out + to.slice(toStart + lastCommonSep); else { toStart += lastCommonSep; if (to.charCodeAt(toStart) === 47) ++toStart; return to.slice(toStart); } }, _makeLong: function _makeLong(path2) { return path2; }, dirname: function dirname(path2) { assertPath(path2); if (path2.length === 0) return "."; var code = path2.charCodeAt(0); var hasRoot = code === 47; var end = -1; var matchedSlash = true; for (var i2 = path2.length - 1; i2 >= 1; --i2) { code = path2.charCodeAt(i2); if (code === 47) { if (!matchedSlash) { end = i2; break; } } else { matchedSlash = false; } } if (end === -1) return hasRoot ? "/" : "."; if (hasRoot && end === 1) return "//"; return path2.slice(0, end); }, basename: function basename(path2, ext) { if (ext !== void 0 && typeof ext !== "string") throw new TypeError('"ext" argument must be a string'); assertPath(path2); var start = 0; var end = -1; var matchedSlash = true; var i2; if (ext !== void 0 && ext.length > 0 && ext.length <= path2.length) { if (ext.length === path2.length && ext === path2) return ""; var extIdx = ext.length - 1; var firstNonSlashEnd = -1; for (i2 = path2.length - 1; i2 >= 0; --i2) { var code = path2.charCodeAt(i2); if (code === 47) { if (!matchedSlash) { start = i2 + 1; break; } } else { if (firstNonSlashEnd === -1) { matchedSlash = false; firstNonSlashEnd = i2 + 1; } if (extIdx >= 0) { if (code === ext.charCodeAt(extIdx)) { if (--extIdx === -1) { end = i2; } } else { extIdx = -1; end = firstNonSlashEnd; } } } } if (start === end) end = firstNonSlashEnd; else if (end === -1) end = path2.length; return path2.slice(start, end); } else { for (i2 = path2.length - 1; i2 >= 0; --i2) { if (path2.charCodeAt(i2) === 47) { if (!matchedSlash) { start = i2 + 1; break; } } else if (end === -1) { matchedSlash = false; end = i2 + 1; } } if (end === -1) return ""; return path2.slice(start, end); } }, extname: function extname(path2) { assertPath(path2); var startDot = -1; var startPart = 0; var end = -1; var matchedSlash = true; var preDotState = 0; for (var i2 = path2.length - 1; i2 >= 0; --i2) { var code = path2.charCodeAt(i2); if (code === 47) { if (!matchedSlash) { startPart = i2 + 1; break; } continue; } if (end === -1) { matchedSlash = false; end = i2 + 1; } if (code === 46) { if (startDot === -1) startDot = i2; else if (preDotState !== 1) preDotState = 1; } else if (startDot !== -1) { preDotState = -1; } } if (startDot === -1 || end === -1 || // We saw a non-dot character immediately before the dot preDotState === 0 || // The (right-most) trimmed path component is exactly '..' preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { return ""; } return path2.slice(startDot, end); }, format: function format(pathObject) { if (pathObject === null || typeof pathObject !== "object") { throw new TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject); } return _format("/", pathObject); }, parse: function parse(path2) { assertPath(path2); var ret = { root: "", dir: "", base: "", ext: "", name: "" }; if (path2.length === 0) return ret; var code = path2.charCodeAt(0); var isAbsolute = code === 47; var start; if (isAbsolute) { ret.root = "/"; start = 1; } else { start = 0; } var startDot = -1; var startPart = 0; var end = -1; var matchedSlash = true; var i2 = path2.length - 1; var preDotState = 0; for (; i2 >= start; --i2) { code = path2.charCodeAt(i2); if (code === 47) { if (!matchedSlash) { startPart = i2 + 1; break; } continue; } if (end === -1) { matchedSlash = false; end = i2 + 1; } if (code === 46) { if (startDot === -1) startDot = i2; else if (preDotState !== 1) preDotState = 1; } else if (startDot !== -1) { preDotState = -1; } } if (startDot === -1 || end === -1 || // We saw a non-dot character immediately before the dot preDotState === 0 || // The (right-most) trimmed path component is exactly '..' preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { if (end !== -1) { if (startPart === 0 && isAbsolute) ret.base = ret.name = path2.slice(1, end); else ret.base = ret.name = path2.slice(startPart, end); } } else { if (startPart === 0 && isAbsolute) { ret.name = path2.slice(1, startDot); ret.base = path2.slice(1, end); } else { ret.name = path2.slice(startPart, startDot); ret.base = path2.slice(startPart, end); } ret.ext = path2.slice(startDot, end); } if (startPart > 0) ret.dir = path2.slice(0, startPart - 1); else if (isAbsolute) ret.dir = "/"; return ret; }, sep: "/", delimiter: ":", win32: null, posix: null }; posix.posix = posix; pathBrowserify = posix; return pathBrowserify; } var pathBrowserifyExports = requirePathBrowserify(); const path$5 = /* @__PURE__ */ getDefaultExportFromCjs(pathBrowserifyExports); const vendors = { ini: ini$2, path: path$5 }; const { path: path$4 } = vendors; const fileNameHeaderRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; const urlSegmentSeparator = /[?/#]/; function isURLStringLike(value) { if (typeof value !== "string") { return false; } const prefixes = ["http://", "https://", "data:", "blob:", "./", "../"]; if (prefixes.some((absolutePrefix) => value.startsWith(absolutePrefix))) { return true; } if (["#", "{"].some((char) => value.startsWith(char))) { return false; } if (value.includes("\n")) { return false; } const segments = value.split(urlSegmentSeparator); if (segments.length < 2) { return false; } return segments.every((segment) => segment.length < 100); } function isURL(value) { return typeof globalThis.URL === "function" && value instanceof globalThis.URL; } function isRequest(value) { return typeof globalThis.Request === "function" && value instanceof globalThis.Request; } function isResponse(value) { return typeof globalThis.Response === "function" && value instanceof globalThis.Response; } function isArrayBuffer(value) { return typeof globalThis.ArrayBuffer === "function" && value instanceof globalThis.ArrayBuffer; } function isUint8Array(value) { return typeof globalThis.Uint8Array === "function" && value instanceof globalThis.Uint8Array; } function isBlob(value) { return typeof globalThis.Blob === "function" && value instanceof globalThis.Blob; } function isFileSystemFileHandle(value) { return typeof globalThis.FileSystemFileHandle === "function" && value instanceof globalThis.FileSystemFileHandle; } function isFetchable(value) { return isURLStringLike(value) || isURL(value) || isRequest(value); } class ResolvableFile { constructor({ blobType, name, raw, signal, urlResolver }) { __publicField(this, "name", ""); __publicField(this, "arrayBuffer"); __publicField(this, "blob"); __publicField(this, "blobType", "application/octet-stream"); __publicField(this, "objectUrl"); __publicField(this, "raw"); __publicField(this, "signal"); __publicField(this, "text"); __publicField(this, "uint8Array"); __publicField(this, "urlResolver"); this.raw = raw; if (signal) { this.signal = signal; } if (urlResolver) { this.urlResolver = urlResolver; } if (blobType) { this.blobType = blobType; } if (name) { this.name = extractValidFileName(name); } } /** The base name of the file, without its extension. */ get baseName() { return path$4.parse(this.name).name; } /** The extension name of the file, with a leading ".". */ get extension() { return path$4.parse(this.name).ext; } static async create(rawOrOption) { if (isNil(rawOrOption)) { throw new Error("parameter is not valid"); } if (rawOrOption instanceof ResolvableFile) { return rawOrOption; } const option = typeof rawOrOption === "object" && "raw" in rawOrOption ? rawOrOption : { raw: rawOrOption }; const resolvableFile = new ResolvableFile(option); await resolvableFile.load(); return resolvableFile; } dispose() { if (typeof this.objectUrl === "string") { URL.revokeObjectURL(this.objectUrl); } } async getArrayBuffer() { if (this.arrayBuffer) { return this.arrayBuffer; } this.arrayBuffer = await this.getBlob().arrayBuffer(); return this.arrayBuffer; } getBlob() { if (!this.blob) { throw new Error("blob is not available"); } return this.blob; } getObjectUrl() { if (this.objectUrl) { return this.objectUrl; } this.objectUrl = URL.createObjectURL(this.getBlob()); return this.objectUrl; } async getText() { if (this.text !== void 0) { return this.text; } this.text = await this.getBlob().text(); return this.text; } async getUint8Array() { if (this.uint8Array) { return this.uint8Array; } const arrayBuffer = await this.getArrayBuffer(); this.uint8Array = new Uint8Array(arrayBuffer); return this.uint8Array; } async load() { const result = await getResult(this.urlResolver ? this.urlResolver(this) : this.raw); if (typeof result === "object" && "fileContent" in result && "fileName" in result) { const [fileName, fileContent] = await Promise.all([getResult(result.fileName), getResult(result.fileContent)]); await this.loadContent({ fileContent, fileName }); } else { await this.loadContent(result); } } loadArrayBuffer(arrayBuffer) { this.arrayBuffer = arrayBuffer; this.blob = new Blob([arrayBuffer], { type: this.blobType }); } async loadContent(content) { if (isBlob(content)) { this.blob = content; } else if (isFetchable(content)) { await this.loadFetchable(content); } else if (typeof content === "string") { this.loadPlainText(content); } else if (isResolvableFileContent(content == null ? void 0 : content.fileContent)) { await this.loadObject(content); } else if (isArrayBuffer(content)) { this.loadArrayBuffer(content); } else if (isUint8Array(content)) { this.loadUint8Array(content); } else if (isResponse(content)) { await this.loadResponse(content); } else if (isFileSystemFileHandle(content)) { await this.loadFileSystemFileHandle(content); } else { throw new TypeError("failed to resolve the file, file content:", content); } const uint8Array = await this.getUint8Array(); const extention = isZip(uint8Array) ? "zip" : "bin"; this.name || (this.name = generateValidFileName(extention)); } async loadFetchable(fetchable) { if (isRequest(fetchable)) { this.name || (this.name = extractValidFileName(fetchable.url)); } else if (isURL(fetchable)) { this.name || (this.name = extractValidFileName(fetchable.href)); } else { this.name || (this.name = extractValidFileName(fetchable)); } const response = await fetch(fetchable, { signal: this.signal || null }); await this.loadResponse(response); } async loadFileSystemFileHandle(fileSystemFileHandle) { const file = await fileSystemFileHandle.getFile(); this.blob = file; this.name = extractValidFileName(file.name); } async loadObject(object) { let { fileContent, fileName } = object; [fileName, fileContent] = await Promise.all([getResult(fileName), getResult(fileContent)]); this.name || (this.name = extractValidFileName(fileName)); await this.loadContent(fileContent); } loadPlainText(text) { this.blob = new Blob([text], { type: this.blobType }); } async loadResponse(response) { var _a, _b; const header = response.headers.get("Content-Disposition"); if (header) { const extracted = (_b = (_a = fileNameHeaderRegex.exec(header)) == null ? void 0 : _a[1]) == null ? void 0 : _b.replace(/['"]/g, ""); if (extracted) { this.name || (this.name = extractValidFileName(extracted)); } } this.blob = await response.blob(); this.name || (this.name = extractValidFileName(response.url)); } loadUint8Array(uint8Array) { this.uint8Array = uint8Array; this.blob = new Blob([uint8Array], { type: this.blobType }); } } const { path: path$3 } = vendors; const textEncoder = new TextEncoder(); function urlBaseName(url) { let pathname = url; try { pathname = new URL(url).pathname; } catch { } const name = path$3.basename(pathname); try { return decodeURIComponent(name); } catch { return name; } } let i = 0; function id() { i += 1; return i; } function generateValidFileName(extension = "bin") { return `data${id()}.${extension}`; } function extractValidFileName(url) { let baseName = urlBaseName(url) || ""; baseName = baseName.replaceAll(/["%*/:<>?\\|]/g, "-"); const extractedExtension = path$3.parse(baseName).ext; if (extractedExtension) { return baseName; } return ""; } function isAbsoluteUrl(string) { if (!string) { return false; } if (typeof string !== "string") { return false; } const absolutePrefixes = ["http://", "https://", "//", "data:", "blob:"]; return absolutePrefixes.some((absolutePrefix) => string.startsWith(absolutePrefix)); } function updateStyle(element, style) { if (!element) { return; } for (const rule in style) { const value = style[rule]; element.style[rule] = value || null; } } function delay(time) { return new Promise((resolve) => { setTimeout(resolve, time); }); } function isGlobalScript(js) { return js.startsWith("var Module"); } function isEsmScript(js) { return js.includes("import.meta.url"); } async function patchCoreJs({ js, name }) { let jsContent = await js.getText(); if (isGlobalScript(jsContent)) { jsContent = `export function getEmscripten({ Module }) { ${jsContent}; Module.FS = FS; Module.PATH = PATH; Module.ERRNO_CODES = ERRNO_CODES; return { AL: typeof AL === 'undefined' ? null: AL, Browser: typeof Browser === 'undefined' ? null: Browser, JSEvents, Module, exit: _emscripten_force_exit } }`; } else if (isEsmScript(jsContent)) { jsContent = `${jsContent.replace( "readyPromiseResolve(Module)", `readyPromiseResolve({ AL: typeof AL === 'undefined' ? null: AL, Browser: typeof Browser === 'undefined' ? null: Browser, JSEvents, Module, exit: _emscripten_force_exit })` )}; export function getEmscripten({ Module }) { return (libretro_${name} || ${name})(Module) } `; } return jsContent; } async function importCoreJsAsESM({ js, name }) { const jsContent = await patchCoreJs({ js, name }); const jsResolvable = await ResolvableFile.create({ blobType: "application/javascript", raw: jsContent }); const jsObjectUrl = jsResolvable.getObjectUrl(); try { return await import( /* @vite-ignore */ /* webpackIgnore: true */ jsObjectUrl ); } catch { return await new Function(`return import('${jsObjectUrl}')`)(); } finally { jsResolvable.dispose(); } } function isNil(obj) { return obj === void 0 || obj === null; } function isPlainObject$1(obj) { if (isNil(obj)) { return false; } return obj.constructor === Object || !obj.constructor; } function mergeProperty(target, source, key) { const targetValue = target[key]; const sourceValue = source[key]; if (isNil(targetValue)) { target[key] = sourceValue; } else if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { target[key] = [...targetValue, ...sourceValue]; } else if (isPlainObject$1(targetValue) && isPlainObject$1(sourceValue)) { target[key] = isPlainObject$1(targetValue) ? target[key] : {}; merge(target[key], sourceValue); } else { target[key] = sourceValue; } } function merge(target, ...sources) { if (sources.length === 1) { const [source] = sources; for (const key in source) { mergeProperty(target, source, key); } } else { for (const source of sources) { merge(target, source); } } } function checkIsAborted(signal) { if (signal == null ? void 0 : signal.aborted) { throw new Error("Launch aborted"); } } function padZero(number) { return (number < 10 ? "0" : "") + number; } async function getResult(value) { if (!value) { return value; } if (typeof (value == null ? void 0 : value.then) === "function") { return getResult(await value); } if (typeof value === "function") { return getResult(value()); } return value; } const resolvableClasses = [ globalThis.Response, globalThis.Uint8Array, globalThis.URL, globalThis.Request, globalThis.Response, globalThis.FileSystemFileHandle ]; function isResolvableFileContent(value) { if (typeof value === "string") { return true; } return resolvableClasses.some((clazz) => clazz && value instanceof clazz); } function isResolvableFileInput(value) { if (typeof value === "string") { return true; } if ("fileContent" in value) { return true; } if (Array.isArray(value)) { return value.every((item) => isResolvableFileInput(item)); } return isResolvableFileContent(value); } function isZip(uint8Array) { return uint8Array[0] === 80 && // eslint-disable-next-line unicorn/number-literal-case uint8Array[1] === 75 && (uint8Array[2] === 3 || uint8Array[2] === 5 || uint8Array[2] === 7) && (uint8Array[3] === 4 || uint8Array[3] === 6 || uint8Array[3] === 8); } const { path: path$2 } = vendors; function getDefaultRetroarchConfig() { const defaultRetroarchConfig = { menu_driver: "rgui", menu_navigation_browser_filter_supported_extensions_enable: false, notification_show_when_menu_is_alive: true, savestate_auto_load: true, savestate_thumbnail_enable: true, stdin_cmd_enable: true, video_shader_enable: true, input_audio_mute: "nul", // override default 'f9' input_cheat_index_minus: "nul", // override default 't', input_cheat_index_plus: "nul", // override default 'y', input_cheat_toggle: "nul", // override default 'u', input_desktop_menu_toggle: "nul", // override default 'f5' input_exit_emulator: "nul", // override default 'esc', input_fps_toggle: "nul", // override default 'f3' input_frame_advance: "nul", // override default 'k', input_game_focus_toggle: "nul", // override default 'scroll_lock' input_grab_mouse_toggle: "nul", // override default 'f11' input_hold_fast_forward: "nul", // override default 'l', input_hold_slowmotion: "nul", // override default 'e', input_load_state: "nul", // override default 'f4' input_netplay_game_watch: "nul", // override default 'i', input_netplay_player_chat: "nul", // override default 'tilde' input_pause_toggle: "nul", // override default 'p', input_reset: "nul", // override default 'h', input_rewind: "nul", // override default 'r', input_save_state: "nul", // override default 'f2' input_screenshot: "nul", // override default 'f8' input_shader_next: "nul", // override default 'm', input_shader_prev: "nul", // override default 'n', input_shader_toggle: "nul", // override default 'comma' input_state_slot_decrease: "nul", // override default 'f6' input_state_slot_increase: "nul", // override default 'f7' input_toggle_fast_forward: "nul", // override default 'space' input_toggle_fullscreen: "nul", // override default 'f', input_volume_down: "nul", // override default 'subtract' input_volume_up: "nul", // override default 'add' input_player1_analog_dpad_mode: 1, input_player2_analog_dpad_mode: 1, input_player3_analog_dpad_mode: 1, input_player4_analog_dpad_mode: 1 }; return defaultRetroarchConfig; } const cdnBaseUrl = "https://cdn.jsdelivr.net/gh"; const coreRepo = "arianrhodsandlot/retroarch-emscripten-build"; const coreVersion = "v1.20.0"; const coreDirectory = "retroarch"; const shaderRepo = "libretro/glsl-shaders"; const shaderVersion = "326507d"; function getDefaultOptions() { const defaultOptions = { element: "", retroarchConfig: getDefaultRetroarchConfig(), retroarchCoreConfig: {}, runEmulatorManually: false, setupEmulatorManually: false, resolveCoreJs(core) { return `${cdnBaseUrl}/${coreRepo}@${coreVersion}/${coreDirectory}/${core}_libretro.js`; }, resolveCoreWasm(core) { return `${cdnBaseUrl}/${coreRepo}@${coreVersion}/${coreDirectory}/${core}_libretro.wasm`; }, resolveRom(file) { if (typeof file !== "string") { return file || []; } if (isAbsoluteUrl(file)) { return file; } const extension = path$2.extname(file); const romRepo = { ".bin": "retrobrews/md-games", ".gb": "retrobrews/gbc-games", ".gba": "retrobrews/gba-games", ".gbc": "retrobrews/gbc-games", ".md": "retrobrews/md-games", ".nes": "retrobrews/nes-games", ".sfc": "retrobrews/snes-games", ".sms": "retrobrews/sms-games" }[extension]; if (romRepo) { const encodedFile = encodeURIComponent(file); return `${cdnBaseUrl}/${romRepo}@master/${encodedFile}`; } return file; }, resolveBios(file) { return file; }, resolveShader(name) { if (!name) { return []; } const preset = `${cdnBaseUrl}/${shaderRepo}@${shaderVersion}/${name}.glslp`; const segments = name.split(path$2.sep); segments.splice(-1, 0, "shaders"); const shader = `${cdnBaseUrl}/${shaderRepo}@${shaderVersion}/${segments.join(path$2.sep)}.glsl`; return [preset, shader]; } }; return defaultOptions; } let globalOptions = getDefaultOptions(); function getGlobalOptions() { return globalOptions; } function updateGlobalOptions(options) { merge(globalOptions, options); } function resetGlobalOptions() { globalOptions = getDefaultOptions(); } function isPlainObject(value) { if (typeof value !== "object" || value === null) { return false; } const prototype = Object.getPrototypeOf(value); return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); } function isValidCacheKey(cacheKey) { return typeof cacheKey === "string" || isPlainObject(cacheKey); } function getCacheStore() { return { bios: /* @__PURE__ */ new Map(), core: /* @__PURE__ */ new Map(), rom: /* @__PURE__ */ new Map(), shader: /* @__PURE__ */ new Map() }; } const _EmulatorOptions = class _EmulatorOptions { constructor(options) { __publicField(this, "beforeLaunch"); __publicField(this, "bios", []); __publicField(this, "cache", { bios: false, core: false, rom: false, shader: false }); __publicField(this, "core", {}); __publicField(this, "element"); /** * An option to override the `Module` object for Emscripten. See [Module object](https://emscripten.org/docs/api_reference/module.html). * * This is a low level option and not well tested, so use it at your own risk. */ __publicField(this, "emscriptenModule"); __publicField(this, "respondToGlobalEvents"); __publicField(this, "rom", []); __publicField(this, "shader", []); __publicField(this, "signal"); /** * * The size of the canvas element. * If it's `'auto'`, the canvas element will keep its original size, or it's width and height will be updated as specified. */ __publicField(this, "size"); __publicField(this, "sram"); __publicField(this, "state"); __publicField(this, "waitForInteraction"); __publicField(this, "loadPromises", []); __publicField(this, "nostalgistOptions"); this.nostalgistOptions = options; this.emscriptenModule = options.emscriptenModule ?? {}; this.respondToGlobalEvents = options.respondToGlobalEvents ?? true; this.signal = options.signal; this.size = options.size ?? "auto"; this.waitForInteraction = options.waitForInteraction; this.element = this.getElement(); if (typeof options.cache === "boolean") { for (const key in this.cache) { this.cache[key] = options.cache; } } else { Object.assign(this.cache, options.cache); } } /** * RetroArch config. * Not all options can make effects in browser. */ get retroarchConfig() { const options = {}; merge(options, getGlobalOptions().retroarchConfig, this.nostalgistOptions.retroarchConfig); return options; } /** * RetroArch core config. * Not all options can make effects in browser. */ get retroarchCoreConfig() { const options = {}; merge(options, getGlobalOptions().retroarchCoreConfig, this.nostalgistOptions.retroarchCoreConfig); return options; } get style() { const { element, style } = this.nostalgistOptions; const defaultAppearanceStyle = { backgroundColor: "black", imageRendering: "pixelated" }; if (element) { merge(defaultAppearanceStyle, style); return defaultAppearanceStyle; } const defaultLayoutStyle = { height: "100%", left: "0", position: "fixed", top: "0", width: "100%", zIndex: "1" }; merge(defaultLayoutStyle, defaultAppearanceStyle, style); return defaultLayoutStyle; } static async create(options) { const emulatorOptions = new _EmulatorOptions(options); await emulatorOptions.load(); return emulatorOptions; } static resetCacheStore() { Object.assign(_EmulatorOptions.cacheStorage, getCacheStore()); } async load() { this.loadFromCache(); await Promise.all([...this.loadPromises, this.updateState(), this.updateSRAM()]); this.saveToCache(); } loadFromCache() { const loadPromises = []; const loadMethodMap = { bios: this.updateBios, core: this.updateCore, rom: this.updateRom, shader: this.updateShader }; for (const key in this.cache) { const field = key; if (this.cache[field]) { const cache = _EmulatorOptions.cacheStorage[field]; const cacheKey = this.nostalgistOptions[field]; if (isValidCacheKey(cacheKey)) { const cacheValue = cache.get(cacheKey); if (cacheValue) { this[field] = cacheValue; continue; } } } const method = loadMethodMap[field]; const promise = method.call(this); loadPromises.push(promise); } this.loadPromises = loadPromises; } saveToCache() { for (const key in this.cache) { const field = key; if (this.cache[field]) { const cache = _EmulatorOptions.cacheStorage[field]; const cacheKey = this.nostalgistOptions[field]; const cacheValue = this[field]; if (isValidCacheKey(cacheKey) && cacheValue) { cache.set(cacheKey, cacheValue); } } } } async updateSRAM() { if (this.nostalgistOptions.sram) { this.sram = await ResolvableFile.create(this.nostalgistOptions.sram); } } async updateState() { if (this.nostalgistOptions.state) { this.state = await ResolvableFile.create(this.nostalgistOptions.state); } } getElement() { if (typeof document !== "object") { throw new TypeError("document must be an object"); } let { element } = this.nostalgistOptions; if (typeof element === "string" && element) { const canvas = document.body.querySelector(element); if (!canvas) { throw new Error(`can not find element "${element}"`); } if (!(canvas instanceof HTMLCanvasElement)) { throw new TypeError(`element "${element}" is not a canvas element`); } element = canvas; } if (!element) { element = document.createElement("canvas"); } if (element instanceof HTMLCanvasElement) { element.id = "canvas"; return element; } throw new TypeError("invalid element"); } async updateBios() { let { bios, resolveBios } = this.nostalgistOptions; if (!bios) { return; } bios = await getResult(bios); if (!bios) { return; } const biosFiles = Array.isArray(bios) ? bios : [bios]; this.bios = await Promise.all( biosFiles.map( (raw) => ResolvableFile.create( typeof raw === "string" ? { raw, signal: this.signal, urlResolver: () => resolveBios(raw, this.nostalgistOptions) } : { raw, signal: this.signal } ) ) ); } async updateCore() { const { core, resolveCoreJs, resolveCoreWasm } = this.nostalgistOptions; if (typeof core === "object" && "js" in core && "name" in core && "wasm" in core) { const [js, wasm] = await Promise.all([ResolvableFile.create(core.js), ResolvableFile.create(core.wasm)]); this.core = { js, name: core.name, wasm }; return; } const [coreResolvable, coreWasmResolvable] = await Promise.all( [resolveCoreJs, resolveCoreWasm].map( (resolver) => ResolvableFile.create({ raw: core, signal: this.signal, urlResolver: () => resolver(core, this.nostalgistOptions) }) ) ); const name = typeof core === "string" ? core : coreResolvable.name; this.core = { js: coreResolvable, name, wasm: coreWasmResolvable }; } async updateRom() { let { resolveRom, rom } = this.nostalgistOptions; if (!rom) { return; } rom = await getResult(rom); if (!rom) { return; } const romFiles = Array.isArray(rom) ? rom : [rom]; this.rom = await Promise.all( romFiles.map( (romFile) => ResolvableFile.create( typeof romFile === "string" ? { raw: romFile, signal: this.signal, urlResolver: () => resolveRom(romFile, this.nostalgistOptions) } : { raw: romFile, signal: this.signal } ) ) ); for (const resolvable of this.rom) { resolvable.name || (resolvable.name = generateValidFileName()); } } async updateShader() { let { resolveShader, shader } = this.nostalgistOptions; if (!shader) { return; } shader = await getResult(shader); if (!shader) { return; } let rawShaderFile = await resolveShader(shader, this.nostalgistOptions); if (!rawShaderFile) { return; } rawShaderFile = await getResult(rawShaderFile); if (!rawShaderFile) { return; } const rawShaderFiles = Array.isArray(rawShaderFile) ? rawShaderFile : [rawShaderFile]; this.shader = await Promise.all( rawShaderFiles.map((rawShaderFile2) => ResolvableFile.create({ raw: rawShaderFile2, signal: this.signal })) ); } }; __publicField(_EmulatorOptions, "cacheStorage", getCacheStore()); let EmulatorOptions = _EmulatorOptions; const coreInfoMap = { "2048": { savestate: true, supportsNoGame: true }, "3dengine": { corename: "3DEngine" }, "4do": { corename: "4DO", savestate: true }, "81": { savestate: true }, a5200: { savestate: true }, advanced_tests: { corename: "Advanced Test", supportsNoGame: true }, ardens: { corename: "Ardens", savestate: true }, arduous: { corename: "Arduous" }, atari800: { corename: "Atari800", savestate: true }, bk: { savestate: true }, blastem: { corename: "BlastEm", savestate: true }, bluemsx: { corename: "blueMSX", savestate: true }, bnes: { corename: "bnes/higan", savestate: true }, boom3: {}, boom3_xp: {}, bsnes: { savestate: true }, bsnes_cplusplus98: { cheats: true, corename: "bsnes C++98 (v085)", savestate: true }, bsnes_hd_beta: { corename: "bsnes-hd beta", savestate: true }, bsnes_mercury_accuracy: { cheats: true, corename: "bsnes-mercury Accuracy", savestate: true }, bsnes_mercury_balanced: { cheats: true, corename: "bsnes-mercury Balanced", savestate: true }, bsnes_mercury_performance: { cheats: true, corename: "bsnes-mercury Performance", savestate: true }, bsnes2014_accuracy: { cheats: true, corename: "bsnes 2014 Accuracy", savestate: true }, bsnes2014_balanced: { cheats: true, corename: "bsnes 2014 Balanced", savestate: true }, bsnes2014_performance: { cheats: true, corename: "bsnes 2014 Performance", savestate: true }, cannonball: { corename: "Cannonball", supportsNoGame: true }, cap32: { corename: "Caprice32", savestate: true, supportsNoGame: true }, cdi2015: { corename: "Philips CDi 2015" }, chailove: { cheats: true, corename: "ChaiLove", savestate: true }, chimerasnes: { cheats: true, corename: "ChimeraSNES", savestate: true }, citra: { corename: "Citra", savestate: true }, citra_canary: { corename: "Citra Canary/Experimental" }, citra2018: { corename: "Citra 2018" }, craft: { corename: "Craft", supportsNoGame: true }, crocods: { corename: "CrocoDS", savestate: true }, cruzes: { corename: "Cruzes", supportsNoGame: true }, daphne: { corename: "Daphne" }, desmume: { cheats: true, corename: "DeSmuME", savestate: true }, desmume2015: { cheats: true, corename: "DeSmuME 2015", savestate: true }, dinothawr: { corename: "Dinothawr", supportsNoGame: true }, directxbox: { corename: "DirectXBox" }, dirksimple: { corename: "DirkSimple", savestate: true }, dolphin: { corename: "Dolphin", savestate: true }, dolphin_launcher: { corename: "Dolphin Launcher", supportsNoGame: true }, dosbox: { corename: "DOSBox", supportsNoGame: true }, dosbox_core: { corename: "DOSBox-core", supportsNoGame: true }, dosbox_pure: { cheats: true, corename: "DOSBox-pure", savestate: true, supportsNoGame: true }, dosbox_svn: { corename: "DOSBox-SVN", supportsNoGame: true }, dosbox_svn_ce: { corename: "DOSBox-SVN CE", supportsNoGame: true }, duckstation: { corename: "DuckStation", savestate: true }, easyrpg: { corename: "EasyRPG Player" }, ecwolf: { corename: "ECWolf", savestate: true }, emuscv: { corename: "EmuSCV", supportsNoGame: true }, emux_chip8: { corename: "Emux CHIP-8" }, emux_gb: { corename: "Emux GB" }, emux_nes: { corename: "Emux NES" }, emux_sms: { corename: "Emux SMS" }, ep128emu_core: { cheats: true, corename: "ep128emu-core", savestate: true, supportsNoGame: true }, fake08: { corename: "FAKE-08", savestate: true }, fbalpha2012: { corename: "FB Alpha 2012", savestate: true }, fbalpha2012_cps1: { corename: "FB Alpha 2012 CPS-1", savestate: true }, fbalpha2012_cps2: { corename: "FB Alpha 2012 CPS-2", savestate: true }, fbalpha2012_cps3: { corename: "FB Alpha 2012 CPS-3", savestate: true }, fbalpha2012_neogeo: { corename: "FB Alpha 2012 Neo Geo", savestate: true }, fbneo: { cheats: true, corename: "FinalBurn Neo", savestate: true }, fceumm: { cheats: true, cor