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
JavaScript
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