nostalgist
Version:
Nostalgist.js is a JavaScript library that allows you to run emulators of retro consoles within web browsers.
1,722 lines • 93.8 kB
JavaScript
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
//#region src/constants/system.ts
const systemCoreMap = {
gb: "mgba",
gba: "mgba",
gbc: "mgba",
megadrive: "genesis_plus_gx",
nes: "fceumm",
snes: "snes9x"
};
//#endregion
//#region src/libs/set-immediate-polyfill.ts
const originalSetImmediate = globalThis.setImmediate;
let setImmediateChannel = null;
const setImmediateQueue = [];
function immediatePolyfill(callback, ...args) {
if (typeof originalSetImmediate === "function") return originalSetImmediate(callback, ...args);
if (typeof MessageChannel !== "undefined") {
if (!setImmediateChannel) {
setImmediateChannel = new MessageChannel();
setImmediateChannel.port1.addEventListener("message", () => {
const fn = setImmediateQueue.shift();
if (fn) try {
fn();
} catch {}
});
setImmediateChannel.port1.start();
}
setImmediateQueue.push(() => callback(...args));
setImmediateChannel.port2.postMessage(0);
return 0;
}
return setTimeout(() => callback(...args), 0);
}
function installSetImmediatePolyfill() {
if (typeof globalThis.setImmediate === "function") return;
globalThis.setImmediate = immediatePolyfill;
}
function uninstallSetImmediatePolyfill() {
if (globalThis.setImmediate === immediatePolyfill) delete globalThis.setImmediate;
}
//#endregion
//#region node_modules/.pnpm/ini@6.0.0/node_modules/ini/lib/ini.js
var require_ini = /* @__PURE__ */ __commonJSMin(((exports, module) => {
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;
/* istanbul ignore next */
opt.platform = opt.platform || typeof process !== "undefined" && process.platform;
opt.bracketedArray = opt.bracketedArray !== false;
/* istanbul ignore next */
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 = 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 = Object.create(null);
continue;
}
p = out[section] = out[section] || 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?.[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] = 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 i = 0, l = val.length; i < l; i++) {
const c = val.charAt(i);
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;
};
module.exports = {
parse: decode,
decode,
stringify: encode,
encode,
safe,
unsafe
};
}));
//#endregion
//#region node_modules/.pnpm/path-browserify@1.0.1/node_modules/path-browserify/index.js
var require_path_browserify = /* @__PURE__ */ __commonJSMin(((exports, module) => {
function assertPath(path) {
if (typeof path !== "string") throw new TypeError("Path must be a string. Received " + JSON.stringify(path));
}
function normalizeStringPosix(path, allowAboveRoot) {
var res = "";
var lastSegmentLength = 0;
var lastSlash = -1;
var dots = 0;
var code;
for (var i = 0; i <= path.length; ++i) {
if (i < path.length) code = path.charCodeAt(i);
else if (code === 47) break;
else code = 47;
if (code === 47) {
if (lastSlash === i - 1 || dots === 1) {} else if (lastSlash !== i - 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 = i;
dots = 0;
continue;
}
} else if (res.length === 2 || res.length === 1) {
res = "";
lastSegmentLength = 0;
lastSlash = i;
dots = 0;
continue;
}
}
if (allowAboveRoot) {
if (res.length > 0) res += "/..";
else res = "..";
lastSegmentLength = 2;
}
} else {
if (res.length > 0) res += "/" + path.slice(lastSlash + 1, i);
else res = path.slice(lastSlash + 1, i);
lastSegmentLength = i - lastSlash - 1;
}
lastSlash = i;
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 = {
resolve: function resolve() {
var resolvedPath = "";
var resolvedAbsolute = false;
var cwd;
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
var path;
if (i >= 0) path = arguments[i];
else {
if (cwd === void 0) cwd = process.cwd();
path = cwd;
}
assertPath(path);
if (path.length === 0) continue;
resolvedPath = path + "/" + resolvedPath;
resolvedAbsolute = path.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(path) {
assertPath(path);
if (path.length === 0) return ".";
var isAbsolute = path.charCodeAt(0) === 47;
var trailingSeparator = path.charCodeAt(path.length - 1) === 47;
path = normalizeStringPosix(path, !isAbsolute);
if (path.length === 0 && !isAbsolute) path = ".";
if (path.length > 0 && trailingSeparator) path += "/";
if (isAbsolute) return "/" + path;
return path;
},
isAbsolute: function isAbsolute(path) {
assertPath(path);
return path.length > 0 && path.charCodeAt(0) === 47;
},
join: function join() {
if (arguments.length === 0) return ".";
var joined;
for (var i = 0; i < arguments.length; ++i) {
var arg = arguments[i];
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 toLen = to.length - toStart;
var length = fromLen < toLen ? fromLen : toLen;
var lastCommonSep = -1;
var i = 0;
for (; i <= length; ++i) {
if (i === length) {
if (toLen > length) {
if (to.charCodeAt(toStart + i) === 47) return to.slice(toStart + i + 1);
else if (i === 0) return to.slice(toStart + i);
} else if (fromLen > length) {
if (from.charCodeAt(fromStart + i) === 47) lastCommonSep = i;
else if (i === 0) lastCommonSep = 0;
}
break;
}
var fromCode = from.charCodeAt(fromStart + i);
if (fromCode !== to.charCodeAt(toStart + i)) break;
else if (fromCode === 47) lastCommonSep = i;
}
var out = "";
for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) if (i === fromEnd || from.charCodeAt(i) === 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(path) {
return path;
},
dirname: function dirname(path) {
assertPath(path);
if (path.length === 0) return ".";
var code = path.charCodeAt(0);
var hasRoot = code === 47;
var end = -1;
var matchedSlash = true;
for (var i = path.length - 1; i >= 1; --i) {
code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
end = i;
break;
}
} else matchedSlash = false;
}
if (end === -1) return hasRoot ? "/" : ".";
if (hasRoot && end === 1) return "//";
return path.slice(0, end);
},
basename: function basename(path, ext) {
if (ext !== void 0 && typeof ext !== "string") throw new TypeError("\"ext\" argument must be a string");
assertPath(path);
var start = 0;
var end = -1;
var matchedSlash = true;
var i;
if (ext !== void 0 && ext.length > 0 && ext.length <= path.length) {
if (ext.length === path.length && ext === path) return "";
var extIdx = ext.length - 1;
var firstNonSlashEnd = -1;
for (i = path.length - 1; i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else {
if (firstNonSlashEnd === -1) {
matchedSlash = false;
firstNonSlashEnd = i + 1;
}
if (extIdx >= 0) if (code === ext.charCodeAt(extIdx)) {
if (--extIdx === -1) end = i;
} else {
extIdx = -1;
end = firstNonSlashEnd;
}
}
}
if (start === end) end = firstNonSlashEnd;
else if (end === -1) end = path.length;
return path.slice(start, end);
} else {
for (i = path.length - 1; i >= 0; --i) if (path.charCodeAt(i) === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else if (end === -1) {
matchedSlash = false;
end = i + 1;
}
if (end === -1) return "";
return path.slice(start, end);
}
},
extname: function extname(path) {
assertPath(path);
var startDot = -1;
var startPart = 0;
var end = -1;
var matchedSlash = true;
var preDotState = 0;
for (var i = path.length - 1; i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1) {
matchedSlash = false;
end = i + 1;
}
if (code === 46) {
if (startDot === -1) startDot = i;
else if (preDotState !== 1) preDotState = 1;
} else if (startDot !== -1) preDotState = -1;
}
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) return "";
return path.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(path) {
assertPath(path);
var ret = {
root: "",
dir: "",
base: "",
ext: "",
name: ""
};
if (path.length === 0) return ret;
var code = path.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 i = path.length - 1;
var preDotState = 0;
for (; i >= start; --i) {
code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1) {
matchedSlash = false;
end = i + 1;
}
if (code === 46) {
if (startDot === -1) startDot = i;
else if (preDotState !== 1) preDotState = 1;
} else if (startDot !== -1) preDotState = -1;
}
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
if (end !== -1) if (startPart === 0 && isAbsolute) ret.base = ret.name = path.slice(1, end);
else ret.base = ret.name = path.slice(startPart, end);
} else {
if (startPart === 0 && isAbsolute) {
ret.name = path.slice(1, startDot);
ret.base = path.slice(1, end);
} else {
ret.name = path.slice(startPart, startDot);
ret.base = path.slice(startPart, end);
}
ret.ext = path.slice(startDot, end);
}
if (startPart > 0) ret.dir = path.slice(0, startPart - 1);
else if (isAbsolute) ret.dir = "/";
return ret;
},
sep: "/",
delimiter: ":",
win32: null,
posix: null
};
posix.posix = posix;
module.exports = posix;
}));
//#endregion
//#region src/libs/vendors.ts
var import_ini = /* @__PURE__ */ __toESM(require_ini(), 1);
var import_path_browserify = /* @__PURE__ */ __toESM(require_path_browserify(), 1);
const vendors = {
ini: import_ini.default,
path: import_path_browserify.default
};
//#endregion
//#region src/libs/utils.ts
const { path: path$4 } = vendors;
const textEncoder = new TextEncoder();
function urlBaseName(url) {
let pathname = url;
try {
pathname = new URL(url).pathname;
} catch {}
const name = path$4.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, "-");
if (path$4.parse(baseName).ext) return baseName;
return "";
}
function isAbsoluteUrl(string) {
if (!string) return false;
if (typeof string !== "string") return false;
return [
"http://",
"https://",
"//",
"data:",
"blob:"
].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 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?.aborted) {
uninstallSetImmediatePolyfill();
throw new Error("Launch aborted");
}
}
function padZero(number) {
return (number < 10 ? "0" : "") + number;
}
async function getResult(value) {
if (!value) return value;
if (typeof value?.then === "function") return getResult(await value);
if (typeof value === "function") return getResult(value());
return value;
}
function isResolvableFileContent(value) {
if (typeof value === "string") return true;
return [
globalThis.Response,
globalThis.Uint8Array,
globalThis.URL,
globalThis.Request,
globalThis.Response,
globalThis.FileSystemFileHandle,
globalThis.Blob
].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 && uint8Array[1] === 75 && (uint8Array[2] === 3 || uint8Array[2] === 5 || uint8Array[2] === 7) && (uint8Array[3] === 4 || uint8Array[3] === 6 || uint8Array[3] === 8);
}
//#endregion
//#region src/libs/options.ts
const { path: path$3 } = vendors;
function getDefaultRetroarchConfig() {
return {
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",
input_cheat_index_minus: "nul",
input_cheat_index_plus: "nul",
input_cheat_toggle: "nul",
input_desktop_menu_toggle: "nul",
input_exit_emulator: "nul",
input_fps_toggle: "nul",
input_frame_advance: "nul",
input_game_focus_toggle: "nul",
input_grab_mouse_toggle: "nul",
input_hold_fast_forward: "nul",
input_hold_slowmotion: "nul",
input_load_state: "nul",
input_netplay_game_watch: "nul",
input_netplay_player_chat: "nul",
input_pause_toggle: "nul",
input_reset: "nul",
input_rewind: "nul",
input_save_state: "nul",
input_screenshot: "nul",
input_shader_next: "nul",
input_shader_prev: "nul",
input_shader_toggle: "nul",
input_state_slot_decrease: "nul",
input_state_slot_increase: "nul",
input_toggle_fast_forward: "nul",
input_toggle_fullscreen: "nul",
input_volume_down: "nul",
input_volume_up: "nul",
input_player1_analog_dpad_mode: 1,
input_player2_analog_dpad_mode: 1,
input_player3_analog_dpad_mode: 1,
input_player4_analog_dpad_mode: 1
};
}
const cdnBaseUrl = "https://cdn.jsdelivr.net/gh";
const coreRepo = "arianrhodsandlot/retroarch-emscripten-build";
const coreVersion = "v1.22.2";
const coreDirectory = "retroarch";
const shaderRepo = "libretro/glsl-shaders";
const shaderVersion = "468f67b6f6788e2719d1dd28dfb2c9b7c3db3cc7";
const zipjsURL = "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.8.11/+esm";
const extractCache = /* @__PURE__ */ new Map();
async function extractCore(core) {
const url = `${cdnBaseUrl}/${coreRepo}@${coreVersion}/${coreDirectory}/${core}_libretro.zip`;
const [{ BlobReader, BlobWriter, ZipReader }, response] = await Promise.all([import(
/* @vite-ignore */
/* webpackIgnore: true */
zipjsURL
), fetch(url)]);
const entries = await new ZipReader(new BlobReader(await response.blob())).getEntries();
const result = {};
await Promise.all(entries.map(async (entry) => {
if (entry && !entry.directory) {
if (entry.filename.endsWith(".js")) result.js = await entry.getData?.(new BlobWriter("application/octet-stream"));
else if (entry.filename.endsWith(".wasm")) result.wasm = await entry.getData?.(new BlobWriter("application/octet-stream"));
}
}));
if (!result.js || !result.wasm) throw new Error(`Failed to extract core files for ${core}`);
return result;
}
async function extractCoreWithCache(core) {
if (extractCache.has(core)) return extractCache.get(core);
const promise = extractCore(core);
extractCache.set(core, promise);
const result = await promise;
extractCache.delete(core);
return result;
}
function getDefaultOptions() {
return {
element: "",
retroarchConfig: getDefaultRetroarchConfig(),
retroarchCoreConfig: {},
runEmulatorManually: false,
setupEmulatorManually: false,
async resolveCoreJs(core) {
if (typeof core !== "string") throw new TypeError("the core name must be a string");
const { js } = await extractCoreWithCache(core);
if (!js) throw new Error(`Failed to load core JS for ${core}`);
return js;
},
async resolveCoreWasm(core) {
if (typeof core !== "string") throw new TypeError("the core name must be a string");
const { wasm } = await extractCoreWithCache(core);
if (!wasm) throw new Error(`failed to load core WASM for ${core}`);
return wasm;
},
resolveRom(file) {
if (typeof file !== "string") return file || [];
if (isAbsoluteUrl(file)) return 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"
}[path$3.extname(file)];
if (romRepo) return `${cdnBaseUrl}/${romRepo}@master/${encodeURIComponent(file)}`;
return file;
},
resolveBios(file) {
return file;
},
resolveShader(name) {
if (!name) return [];
const preset = `${cdnBaseUrl}/${shaderRepo}@${shaderVersion}/${name}.glslp`;
const segments = name.split(path$3.sep);
segments.splice(-1, 0, "shaders");
return [preset, `${cdnBaseUrl}/${shaderRepo}@${shaderVersion}/${segments.join(path$3.sep)}.glsl`];
}
};
}
let globalOptions = getDefaultOptions();
function getGlobalOptions() {
return globalOptions;
}
function updateGlobalOptions(options) {
merge(globalOptions, options);
}
function resetGlobalOptions() {
globalOptions = getDefaultOptions();
}
//#endregion
//#region src/classes/resolvable-file.ts
const { path: path$2 } = vendors;
const fileNameHeaderRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const urlSegmentSeparator = /[?/#]/;
function isURLStringLike(value) {
if (typeof value !== "string") return false;
if ([
"http://",
"https://",
"data:",
"blob:",
"./",
"../"
].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);
}
var ResolvableFile = class ResolvableFile {
/** The base name of the file, without its extension. */
get baseName() {
return path$2.parse(this.name).name;
}
/** The extension name of the file, with a leading ".". */
get extension() {
return path$2.parse(this.name).ext;
}
constructor({ blobType, name, raw, signal, urlResolver }) {
this.name = "";
this.blobType = "application/octet-stream";
this.raw = raw;
if (signal) this.signal = signal;
if (urlResolver) this.urlResolver = urlResolver;
if (blobType) this.blobType = blobType;
if (name) this.name = extractValidFileName(name);
}
static async create(rawOrOption) {
if (isNil(rawOrOption)) throw new Error("parameter is not valid");
if (rawOrOption instanceof ResolvableFile) return rawOrOption;
const resolvableFile = new ResolvableFile(typeof rawOrOption === "object" && "raw" in rawOrOption ? rawOrOption : { raw: rawOrOption });
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?.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 extention = isZip(await this.getUint8Array()) ? "zip" : "bin";
this.name ||= generateValidFileName(extention);
}
async loadFetchable(fetchable) {
if (isRequest(fetchable)) this.name ||= extractValidFileName(fetchable.url);
else if (isURL(fetchable)) this.name ||= extractValidFileName(fetchable.href);
else 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) {
const [fileName, fileContent] = await Promise.all([getResult(object.fileName), getResult(object.fileContent)]);
this.name ||= extractValidFileName(fileName);
await this.loadContent(fileContent);
}
loadPlainText(text) {
this.blob = new Blob([text], { type: this.blobType });
}
async loadResponse(response) {
const header = response.headers.get("Content-Disposition");
if (header) {
const extracted = fileNameHeaderRegex.exec(header)?.[1]?.replaceAll(/['"]/g, "");
if (extracted) this.name ||= extractValidFileName(extracted);
}
if (!response.ok) throw new Error("Failed to load response", { cause: response });
this.blob = await response.blob();
this.name ||= extractValidFileName(response.url);
}
loadUint8Array(uint8Array) {
this.uint8Array = uint8Array;
this.blob = new Blob([uint8Array], { type: this.blobType });
}
};
//#endregion
//#region src/classes/emulator-options.ts
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(),
sram: /* @__PURE__ */ new Map(),
state: /* @__PURE__ */ new Map()
};
}
var EmulatorOptions = class EmulatorOptions {
static {
this.cacheStorage = getCacheStore();
}
/**
* 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;
}
constructor(options) {
this.bios = [];
this.cache = {
bios: false,
core: false,
rom: false,
shader: false,
sram: false,
state: false
};
this.core = {};
this.rom = [];
this.shader = [];
this.loadPromises = [];
this.nostalgistOptions = options;
this.emscriptenModule = options.emscriptenModule ?? {};
this.respondToGlobalEvents = options.respondToGlobalEvents ?? true;
this.signal = options.signal;
this.size = options.size ?? "auto";
this.sramType = options.sramType ?? "srm";
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);
}
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.saveToCache();
}
loadFromCache() {
const loadPromises = [];
const loadMethodMap = {
bios: this.updateBios,
core: this.updateCore,
rom: this.updateRom,
shader: this.updateShader,
sram: this.updateSRAM,
state: this.updateState
};
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 promise = loadMethodMap[field].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() {
const { resolveBios } = this.nostalgistOptions;
let { bios } = 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() {
const { resolveRom } = this.nostalgistOptions;
let { 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 ||= generateValidFileName();
}
async updateShader() {
const { resolveShader } = this.nostalgistOptions;
let { 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((rawShaderFile) => ResolvableFile.create({
raw: rawShaderFile,
signal: this.signal
})));
}
};
//#endregion
//#region src/constants/core-info.ts
const coreInfoMap = {
"3dengine": { corename: "3DEngine" },
"4do": {
corename: "4DO",
savestate: true
},
"81": { savestate: true },
"2048": {
savestate: true,
supportsNoGame: 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,
corename: "FCEUmm",
savestate: true
},
ffmpeg: { corename: "FFmpeg" },
fixgb: { corename: "fixGB" },
fixnes: { corename: "fixNES" },
flycast: {
corename: "Flycast",
savestate: true
},
flycast_gles2: {
corename: "Flycast GLES2",
savestate: true
},
fmsx: { corename: "fMSX" },
freechaf: { corename: "FreeChaF" },
freeintv: { corename: "FreeIntv" },
freej2me: { corename: "FreeJ2ME" },
frodo: { corename: "Frodo" },
fsuae: { corename: "FS-UAE" },
fuse: { corename: "Fuse" },
galaksija: { supportsNoGame: true },
gambatte: {
corename: "Gambatte",
savestate: true
},
gearboy: { corename: "Gearboy" },
gearcoleco: { corename: "Gearcoleco" },
gearsystem: { corename: "Gearsystem" },
genesis_plus_gx: {
cheats: true,
corename: "Genesis Plus GX",
savestate: true
},
genesis_plus_gx_wide: {
cheats: true,
corename: "Genesis Plus GX Wide",
savestate: true
},
gme: { corename: "Game Music Emu" },
gong: {
corename: "Gong",
savestate: true,
supportsNoGame: true
},
gpsp: {
corename: "gpSP",
savestate: true
},
gw: { corename: "GW" },
handy: {
corename: "Handy",
savestate: true
},
hatari: {
corename: "Hatari",
savestate: true
},
hbmame: { corename: "HBMAME (Git)" },
higan_sfc: {
corename: "nSide (Super Famicom Accuracy)",
savestate: true
},
higan_sfc_balanced: {
corename: "nSide (Super Famicom Balanced)",
savestate: true
},
imageviewer: { corename: "Imageviewer" },
ishiiruka: {
corename: "Ishiiruka",
savestate: true
},
jaxe: { corename: "JAXE" },
jumpnbump: { corename: "jumpnbump" },
kronos: {
cheats: true,
corename: "Kronos",
savestate: true
},
lowresnx: { corename: "lowresnx" },
lutro: { corename: "Lutro" },
mame: {
corename: "MAME",
savestate: true
},
mame2000: {
corename: "MAME 2000 (0.37b5)",
savestate: true
},
mame2003: {
corename: "MAME 2003 (0.78)",
savestate: true
},
mame2003_midway: {
corename: "MAME 2003 Midway (0.78)",
savestate: true
},
mame2003_plus: {
corename: "MAME 2003-Plus",
savestate: true
},
mame2009: { corename: "MAME 2009 (0.135u4)" },
mame2010: { corename: "MAME 2010 (0.139)" },
mame2015: { corename: "MAME 2015 (0.160)" },
mame2016: { corename: "MAME 2016 (0.174)" },
mamearcade: { corename: "MAME (Git)" },
mamemess: {
corename: "MESS (Git)",
savestate: true
},
mednafen_gba: { corename: "Beetle GBA" },
mednafen_lynx: { corename: "Beetle Lynx" },
mednafen_ngp: {
corename: "Beetle NeoPop",
savestate: true
},
mednafen_pce: {
corename: "Beetle PCE",
savestate: true
},
mednafen_pce_fast: {
corename: "Beetle PCE Fast",
savestate: true
},
mednafen_pcfx: { corename: "Beetle PC-FX" },
mednafen_psx: {
cheats: true,
corename: "Beetle PSX",
savestate: true
},
mednafen_psx_hw: {
cheats: true,
corename: "Beetle PSX HW",
savestate: true
},
mednafen_saturn: {
cheats: true,
corename: "Beetle Saturn",
savestate: true
},
mednafen_snes: {
corename: "Beetle bsnes",
savestate: true
},
mednafen_supafaust: {
cheats: true,
corename: "Beetle Supafaust",
savestate: true
},
mednafen_supergrafx: {
cheats: true,
corename: "Beetle SuperGrafx",
savestate: true
},
mednafen_vb: { corename: "Beetle VB" },
mednafen_wswan: {
corename: "Beetle WonderSwan",
savestate: true
},
melonds: { corename: "melonDS" },
mesen: {
cheats: true,
corename: "Mesen",
savestate: true
},
"mesen-s": { corename: "Mesen-S" },
mess2015: { corename: "MESS 2015 (0.160)" },
meteor: { corename: "Meteor" },
mgba: {
cheats: true,
corename: "mGBA",
savestate: true
},
minivmac: { corename: "MinivmacII" },
mojozork: {
corename: "mojozork",
savestate: true
},
moonlight: {
corename: "Moonlight",
supportsNoGame: true
},
mpv: { corename: "MPV" },
mrboom: {
corename: "Mr.Boom",
savestate: true,
supportsNoGame: true
},
mu: {
corename: "Mu",
supportsNoGame: true
},
mupen64plus_next: {
cheats: true,
corename: "Mupen64Plus-Next",
savestate: true
},
mupen64plus_next_develop: {
cheats: true,
corename: "Mupen64Plus-Next",
savestate: true
},
mupen64plus_next_gles2: {
cheats: true,
corename: "Mupen64Plus-Next",
savestate: true
},
mupen64plus_next_gles3: {
cheats: true,
corename: "Mupen64Plus-Next",
savestate: true
},
nekop2: {
corename: "Neko Project II",
savestate: true
},
neocd: { corename: "NeoCD" },
nes: { savestate: true },
nestopia: {
cheats: true,
corename: "Nestopia",
savestate: true
},
np2kai: {
corename: "Neko Project II Kai",
savestate: true
},
numero: {
corename: "Numero",
savestate: true,
supportsNoGame: true
},
nxengine: {
corename: "NXEngine",
supportsNoGame: true
},
o2em: {
corename: "O2EM",
savestate: true
},
oberon: { corename: "Oberon" },
openlara: { corename: "OpenLara" },
opentyrian: {
corename: "OpenTyrian",
supportsNoGame: true
},
opera: {
corename: "Opera",
savestate: true,
supportsNoGame: true
},
parallel_n64: {
corename: "ParaLLEl N64",
savestate: true
},
parallel_n64_debug: {
corename: "ParaLLEl (Debug)",
savestate: true
},
pascal_pong: {
corename: "PascalPong",
supportsNoGame: true
},
pcem: {
corename: "PCem",
supportsNoGame: true
},
pcsx_rearmed: {
cheats: true,
corename: "PCSX-ReARMed",
savestate: true
},
pcsx_rearmed_interpreter: {
cheats: true,
corename: "PCSX ReARMed [Interpreter]",
savestate: true
},
pcsx_rearmed_neon: {
cheats: true,
corename: "PCSX ReARMed [NEON]",
savestate: true
},
pcsx1: { corename: "PCSX1" },
pcsx2: {
corename: "LRPS2",
savestate: true
},
picodrive: {
cheats: true,
corename: "PicoDrive",
savestate: true
},
play: {
corename: "Play!",
savestate: true
},
pocketcdg: { corename: "PocketCDG" },
pokemini: {
corena