UNPKG

nostalgist

Version:

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

2,902 lines 95.6 kB
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?.[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 {
  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;
  }
  arrayBuffer;
  blob;
  blobType = "application/octet-stream";
  objectUrl;
  raw;
  signal;
  text;
  uint8Array;
  urlResolver;
  constructor({ blobType, name, raw, signal, 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);
    }
  }
  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?.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 ||= 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) {
    let { fileContent, fileName } = object;
    [fileName, fileContent] = await Promise.all([getResult(fileName), getResult(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]?.replace(/['"]/g, "");
      if (extracted) {
        this.name ||= extractValidFileName(extracted);
      }
    }
    this.blob = await response.blob();
    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("var setImmediate", "").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?.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;
  }
  const resolvableClasses = [
    globalThis.Response,
    globalThis.Uint8Array,
    globalThis.URL,
    globalThis.Request,
    globalThis.Response,
    globalThis.FileSystemFileHandle,
    globalThis.Blob
  ];
  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 originalSetImmediate = globalThis.setImmediate;
function setImmediate(callback) {
  return originalSetImmediate ? originalSetImmediate(callback) : setTimeout(callback, 0);
}
function installSetImmediatePolyfill() {
  if (typeof globalThis.setImmediate === "function") {
    return;
  }
  globalThis.setImmediate = setImmediate;
}
function uninstallSetImmediatePolyfill() {
  if (globalThis.setImmediate === setImmediate) {
    delete globalThis.setImmediate;
  }
}
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.21.0";
const coreDirectory = "retroarch";
const shaderRepo = "libretro/glsl-shaders";
const shaderVersion = "f3dc75a";
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(),
    sram: /* @__PURE__ */ new Map(),
    state: /* @__PURE__ */ new Map()
  };
}
class EmulatorOptions {
  static cacheStorage = getCacheStore();
  beforeLaunch;
  bios = [];
  cache = { bios: false, core: false, rom: false, shader: false, sram: false, state: false };
  core = {};
  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.
   */
  emscriptenModule;
  respondToGlobalEvents;
  rom = [];
  shader = [];
  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.
   */
  size;
  sram;
  state;
  waitForInteraction;
  /**
   * 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;
  }
  loadPromises = [];
  nostalgistOptions;
  constructor(options) {
    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);
    }
  }
  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 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 ||= 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 }))
    );
  }
}
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, 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: { corename: "PokeMini", savestate: true },
  potator: { corename: "Potator", savestate: true },
  ppsspp: { corename: "PPSSPP", savestate: true },
  prboom: { cheats: true, corename: "PrBoom", savestate: true },
  prosystem: { corename: "ProSystem", savestate: true },
  puae: { cheats: true, corename: "PUAE", savestate: true, supportsNoGame: true },
  puae2021: { cheats: true, corename: "PUAE 2021", savestate: true, supportsNoGame: true },
  puzzlescript: { corename: "puzzlescript", savestate: true },
  px68k: { corename: "PX68k", supportsNoGame: true },
  quasi88: { cheats: true, corename: "QUASI88", savestate: true, supportsNoGame: true },
  quicknes: { corename: "QuickNES", savestate: true },
  race: { corename: "RACE", savestate: true },
  redbook: { corename: "Redbook" },
  reminiscence: { corename: "REminiscence", savestate: true },
  remotejoy: { corename: "RemoteJoy", supportsNoGame: true },
  retro8: { corename: "Retro8" },
  retrodream: { corename: "RetroDream" },
  rustation: { corename: "Rustation" },
  same_cdi: { corename: "SAME CDi (Git)" },
  sameboy: { corename: "SameBoy" },
  sameduck: { corename: "SameDuck", savestate: true },
  scummvm: { corename: "ScummVM", supportsNoGame: true },
  simcp: { corename: "SimCoupe" },
  smsplus: { corename: "SMS Plus GX" },
  snes9x: { cheats: true, corename: "Snes9x", savestate: true },
  snes9x2002: { cheats: true, corename: "Snes9x 2002", savestate: true },
  snes9x2005: { cheats: true, corename: "Snes9x 2005", savestate: true },
  snes9x2005_plus: { cheats: true, corename: "Snes9x 2005 Plus", savestate: true },
  snes9x2010: { cheats: true, corename: "Snes9x 2010", savestate: true },
  squirreljme: { corename: "SquirrelJME", supportsNoGame: true },
  stella: { corename: "Stella", savestate: true },
  stella2014: { corename: "Stella 2014", savestate: true },
  stonesoup: { corename: "Dungeon Crawl Stone Soup" },
  superbroswar: { corename: "superbroswar" },
  swanstation: { corename: "SwanStation", savestate: true },
  tempgba: { corename: "TempGBA" },
  test: { corename: "Test", supportsNoGame: true },
  test_netplay: { corename: "netplay-test", supportsNoGame: true },
  testaudio_callback: { corename: "TestAudio Callback", supportsNoGame: true },
  testaudio_no_callback: { corename: "TestAudio NoCallback", supportsNoGame: true },
  testaudio_playback_wav: { corename: "TestAudio Playback Wav", supportsNoGame: true },
  testgl: { corename: "TestGL", supportsNoGame: true },
  testgl_compute_shaders: { corename: "TestGL ComputeShaders", supportsNoGame: true },
  testgl_ff: { corename: "TestGL (FF)", supportsNoGame: true },
  testinput_buttontest: { corename: "Button Test", supportsNoGame: true },
  testretroluxury: { corename: "Test RetroLuxury", supportsNoGame: true },
  testsw: { corename: "TestSW", supportsNoGame: true },
  testsw_vram: { corename: "TestSW VRAM", supportsNoGame: true },
  testvulkan: { corename: "TestVulkan", supportsNoGame: true },
  testvulkan_async_compute: { corename: "TestVulkan AsyncCompute", supportsNoGame: true },
  tgbdual: { corename: "TGB Dual", savestate: true },
  theodore: { corename: "theodore", savestate: true, supportsNoGame: true },
  thepowdertoy: { corename: "ThePowderToy", savestate: true, supportsNoGame: true },
  tic80: { cheats: true, corename: "TIC-80", savestate: true },
  tyrquake: { cheats: true, corename: "TyrQuake" },
  uae4arm: { corename: "UAE4ARM", savestate: true },
  ume2015: { corename: "UME 2015 (0.160)" },
  uw8: { corename: "MicroW8", savestate: true },
  uzem: { savestate: true },
  vaporspec: { corename: "VaporSpec", savestate: true },
  vba_next: { corename: "VBA Next", savestate: true },
  vbam: { cheats: true, corename: "VBA-M", savestate: true },
  vecx: { savestate: true },
  vemulator: { corename: "VeMUlator" },
  vice_x128: { cheats: true, corename: "VICE x128", savestate: true, supportsNoGame: true },
  vice_x64: { cheats: true, corename: "VICE x64", savestate: true, supportsNoGame: true },
  vice_x64dtv: { cheats: true, corename: "VICE x64dtv", savestate: true, supportsNoGame: true },
  vice_x64sc: { cheats: true, corename: "VICE x64sc", savestate: true, supportsNoGame: true },
  vice_xcbm2: { cheats: true, corename: "VICE xcbm2", savestate: true, supportsNoGame: true },
  vice_xcbm5x0: { cheats: true, corename: "VICE xcbm5x0", savestate: true, supportsNoGame: true },
  vice_xpet: { cheats: true, corename: "VICE xpet", savestate: true, supportsNoGame: true },
  vice_xplus4: { cheats: true, corename: "VICE xplus4", savestate: true, supportsNoGame: true },
  vice_xscpu64: { cheats: true, corename: "VICE xscpu64", savestate: true, supportsNoGame: true },
  vice_xvic: { cheats: true, corename: "VICE xvic", savestate: true, supportsNoGame: true },
  vircon32: { corename: "Vircon32", supportsNoGame: true },
  virtualjaguar: { corename: "Virtual Jaguar" },
  virtualxt: {},
  vitaquake2: { corename: "vitaQuake 2" },
  "vitaquake2-rogue": { corename: "vitaQuake 2 [Rogue]" },
  "vitaquake2-xatrix": { corename: "vitaQuake 2 [Xatrix]" },
  "vitaquake2-zaero": { corename: "vitaQuake 2 [Zaero]" },
  vitaquake3: { corename: "vitaQuake 3" },
  vitavoyager: { corename: "vitaVoyager" },
  wasm4: { corename: "WASM-4", savestate: true },
  x1: {},
  x64sdl: { corename: "VICE SDL" },
  xrick: { corename: "XRick", supportsNoGame: true },
  yabasanshiro: { cheats: true, corename: "YabaSanshiro", savestate: true },
  yabause: { cheats: true, corename: "Yabause", savestate: true }
};
const keyboardCodeMap = {
  add: "NumpadAdd",
  // is this right?
  alt: "AltLeft",
  backquote: "Backquote",
  backslash: "",
  // what's this?
  backspace: "Backspace",
  capslock: "CapsLock",
  comma: "Comma",
  ctrl: "ControlLeft",
  del: "Delete",
  divide: "NumpadDivide",
  down: "ArrowDown",
  end: "End",
  enter: "Enter",
  equals: "Equal",
  escape: "Escape",
  home: "Home",
  insert: "Insert",
  kp_enter: "NumpadEnter",
  kp_equals: "NumpadEquals",
  // is this right?
  kp_minus: "NumpadSubtract",
  kp_period: "NumpadDecimal",
  kp_plus: "NumpadAdd",
  left: "ArrowLeft",
  leftbracket: "BracketLeft",
  // is this right?
  minus: "Minus",
  multiply: "NumpadMultiply",
  numlock: "NumLock",
  pagedown: "PageDown",
  pageup: "PageUp",
  pause: "Pause",
  period: "Period",
  print_screen: "PrintScreen",
  quote: "Quote",
  ralt: "AltRight",
  rctrl: "ControlRight",
  right: "ArrowRight",
  rightbracket: "BracketRight",
  // is this right?
  rshift: "ShiftRight",
  scroll_lock: "ScrollLock",
  semicolon: "Semicolon",
  shift: "ShiftLeft",
  slash: "Slash",
  space: "Space",
  subtract: "NumpadSubtract",
  // is this right?
  tab: "Tab",
  tilde: "",
  // what's this?
  up: "ArrowUp"
};
function getEmscriptenModuleOverrides(overrides) {
  let resolveRunDependenciesPromise;
  const runDependenciesPromise = new Promise((resolve) => {
    resolveRunDependenciesPromise = resolve;
  });
  const emscriptenModuleOverrides = {
    noExitRuntime: false,
    noInitialRun: true,
    locateFile(file) {
      return file;
    },
    // the return value of `monitorRunDependencies` seems to be misused here, but it works for now
    async monitorRunDependencies(left) {
      if (left === 0) {
        resolveRunDependenciesPromise();
      }
      return await runDependenciesPromise;
    },
    print(...args) {
      console.info(...args);
    },
    printErr(...args) {
      console.error(...args);
    },
    // @ts-expect-error do not throw error when exit
    quit(status, toThrow) {
      if (status) {
        console.info(status, toThrow);
      }
    },
    ...overrides
  };
  return emscriptenModuleOverrides;
}
const { ini: ini$1, path: path$1 } = vendors;
const userdataDirectory = "/home/web_user/retroarch/userdata";
const bundleDirectory = "/home/web_user/retroarch/bundle";
const contentDirectory = path$1.join(userdataDirectory, "content");
const systemDirectory = path$1.join(userdataDirectory, "system");
const configDirectory = path$1.join(userdataDirectory, "config");
const screenshotsDirectory = path$1.join(userdataDirectory, "screenshots");
const shaderDirectory = path$1.join(bundleDirectory, "shaders", "shaders_glsl");
const shaderAssetsDirectory = path$1.join(shaderDirectory, "shaders");
const configPath = path$1.join(userdataDirectory, "retroarch.cfg");
const coreConfigPath = path$1.join(userdataDirectory, "retroarch-core-options.cfg");
class EmulatorFileSystem {
  static bundleDirectory = bundleDirectory;
  static configDirectory = configDirectory;
  static configPath = configPath;
  static contentDirectory = contentDirectory;
  static coreConfigPath = coreConfigPath;
  static screenshotsDirectory = screenshotsDirectory;
  static shaderDirectory = shaderDirectory;
  static systemDirectory = systemDirectory;
  static userdataDirectory = userdataDirectory;
  emscriptenModule;
  signal;
  get FS() {
    return this.emscriptenModule.FS;
  }
  constructor({
    emscriptenModule,
    signal
  }) {
    this.emscriptenModule = emscriptenModule;
    this.signal = signal;
  }
  static async create(...args) {
    const emulatorFileSystem = new EmulatorFileSystem(...args);
    await emulatorFileSystem.prepare();
    return emulatorFileSystem;
  }
  mkdirTree(directory) {
    const { FS } = this;
    FS.mkdirTree(directory);
  }
  readFile(path2, encoding = "utf8") {
    const { FS } = this;
    return FS.readFile(path2, { encoding });
  }
  unlink(path2) {
    const { FS } = this;
    try {
      FS.unlink(path2);
    } catch {
    }
  }
  async waitForFile(fileName) {
    const maxRetries = 30;
    let buffer;
    let isFinished = false;
    let retryTimes = 0;
    while (retryTimes <= maxRetries && !isFinished) {
      const delayTime = Math.min(100 * 2 ** retryTimes, 1e3);
      await delay(delayTime);
      checkIsAborted(this.signal);
      try {
        const newBuffer = this.readFile(fileName, "binary").buffer;
        if (buffer) {
          isFinished = buffer.byteLength > 0 && buffer.byteLength === newBuffer.byteLength;
        }
        buffer = newBuffer;
      } catch (error) {
        console.warn(error);
      }
      retryTimes += 1;
    }
    if (!(isFinished && buffer)) {
      throw new Error("fs timeout");
    }
    return buffer;
  }
  async writeFile(filePath, fileContent) {
    const { FS } = this;
    const directory = path$1.dirname(filePath);
    const fileName = path$1.basename(filePath);
    const resolvable = await ResolvableFile.create(fileContent);
    const buffer = await resolvable.getUint8Array();
    FS.createDataFile("/", fileName, buffer, true, false);
    const encoding = "binary";
    const data = this.readFile(fileName, encoding);
    this.mkdirTree(directory);
    FS.writeFile(filePath, data, { encoding });
    this.unlink(fileName);
  }
  async writeIni(path2, config) {
    if (!config) {
      return;
    }
    const clonedConfig = {};
    for (const key in config) {
      clonedConfig[key] = `__${config[key]}__`;
    }
    const fileContent = ini$1.stringify(clonedConfig, { platform: "linux", whitespace: true }).replaceAll("__", '"');
    await this.writeFile(path2, fileContent);
  }
  async prepare() {
    const directories = [configDirectory, contentDirectory, shaderDirectory, shaderAssetsDirectory, systemDirectory];
    for (const directory of directories) {
      this.mkdirTree(directory);
    }
    const maxWaitTime = 100;
    let waitTime = 0;
    while (!this.emscriptenModule.asm && waitTime < maxWaitTime) {
      await delay(10);
      checkIsAborted(this.signal);
      waitTime += 5;
    }
  }
}
const { ini, path } = vendors;
class Emulator {
  canvasInitialSize = { height: 0, width: 0 };
  emscripten;
  eventListeners = {
    beforeLaunch: [],
    onLaunch: []
  };
  fileSystem;
  gameStatus = "initial";
  messageQueue = [];
  options;
  get coreFullName() {
    const { core } = this.options;
    const coreFullName = coreInfoMap[core.name].corename || core.name;
    if (!coreFullName) {
      throw new Error(`invalid core name: ${core.name}`);
    }
    return coreFullName;
  }
  get fs() {
    if (!this.fileSystem) {
      throw new Error("fileSystem is not ready");
    }
    return this.fileSystem;
  }
  get romBaseName() {
    const {
      rom: [{ baseName }]
    } = this.options;
    return baseName;
  }
  get sramFileDirectory() {
    return path.join(EmulatorFileSystem.userdataDirectory, "saves", this.coreFullName);
  }
  get sramFilePath() {
    return path.join(this.sramFileDirectory, `${this.romBaseName}.srm`);
  }
  get stateFileDirectory() {
    return path.join(EmulatorFileSystem.userdataDirectory, "states", this.coreFullName);
  }
  get stateFilePath() {
    return path.join(this.stateFileDirectory, `${this.romBaseName}.state`);
  }
  get stateThumbnailFilePath() {
    return `${this.stateFilePath}.png`;
  }
  constructor(options) {
    this.options = options;
  }
  callCommand(command) {
    const { Module } = this.getEmscripten();
    Module[command]?.();
  }
  exit(statusCode = 0) {
    try {
      const { exit, JSEvents } = this.getEmscripten();
      exit(statusCode);
      JSEvents.removeAllEventListeners();
    } catch {
    }
    uninstallSetImmediatePolyfill();
    this.gameStatus = "terminated";
  }
  getEmscripten() {
    if (!this.emscripten) {
      throw new Error("emulator is not ready");
    }
    return this.emscripten;
  }
  getOptions() {
    return this.options;
  }
  getStatus() {
    return this.gameStatus;
  }
  async launch() {
    const { element, respondToGlobalEvents, signal, style, waitForInteraction } = this.options;
    updateStyle(element, style);
    const removeProperty = element.style.removeProperty.bind(element.style);
    element.style.removeProperty = (property) => {
      if (property !== "width" && property !== "height") {
        return removeProperty(property);
      }
      return element.style[property];
    };
    if (!element.isConnected) {
      document.body.append(element);
      signal?.addEventListener("abort", () => {
        element?.remove();
      });
    }
    this.canvasInitialSize = this.getElementSize();
    if (respondToGlobalEvents === false) {
      if (!element.tabIndex || element.tabIndex === -1) {
        element.tabIndex = 0;
      }
      const { activeElement } = document;
      element.focus();
      signal?.addEventListener("abort", () => {
        if (activeElement instanceof HTMLElement) {
          activeElement.focus();
        }
      });
    }
    await this.runEventListeners("beforeLaunch");
    if (waitForInteraction) {
      waitForInteraction({
        done: async () => {
          this.runMain();
          await this.runEventListeners("onLaunch");
        }
      });
    } else {
      this.runMain();
      await this.runEventListeners("onLaunch");
    }
  }
  async loadState(state) {
    this.clearStateFile();
    await this.fs.writeFile(this.stateFilePath, state);
    await this.fs.waitForFile(this.stateFilePath);
    this.sendCommand("LOAD_STATE");
  }
  on(event, callback) {
    this.eventListeners[event].push(callback);
    return this;
  }
  pause() {
    if (this.gameStatus === "running") {
      this.sendCommand("PAUSE_TOGGLE");
    }
    this.gameStatus = "paused";
  }
  async press(button, player = 1, time = 100) {
    const code = this.getKeyboardCode(button, player);
    if (code) {
      await this.keyboardPress(code, time);
    }
  }
  pressDown(button, player = 1) {
    const code = this.getKeyboardCode(button, player);
    if (code) {
      this.keyboardDown(code);
    }
  }
  pressUp(button, player = 1) {
    const code = this.getKeyboardCode(button, player);
    if (code) {
      this.keyboardUp(code);
    }
  }
  resize({ height, width }) {
    const { Module } = this.getEmscripten();
    if (typeof width === "number" && typeof height === "number") {
      Module.setCanvasSize(width, height);
    }
  }
  restart() {
    this.sendCommand("RESET");
    this.resume();
  }
  resume() {
    if (this.gameStatus === "paused") {
      this.sendCommand("PAUSE_TOGGLE");
    }
    this.gameStatus = "running";
  }
  async saveSRAM() {
    this.fs.unlink(this.sramFilePath);
    this.callCommand("_cmd_savefiles");
    const buffer = await this.fs.waitForFile(this.sramFilePath);
    const blob = new Blob([buffer], { type: "application/octet-stream" });
    return blob;
  }
  async saveState() {
    this.clearStateFile();
    this.sendCommand("SAVE_STATE");
    const savestateThumbnailEnable = this.options.retroarchConfig.savestate_thumbnail_enable;
    let stateBuffer;
    let stateThumbnailBuffer;
    if (savestateThumbnailEnable) {
      [stateBuffer, stateThumbnailBuffer] = await Promise.all([
        this.fs.waitForFile(this.stateFilePath),
        this.fs.waitForFile(this.stateThumbnailFilePath)
      ]);
    } else {
      stateBuffer = await this.fs.waitForFile(this.stateFilePath);
    }
    this.clearStateFile();
    const state = new Blob([stateBuffer], { type: "application/octet-stream" });
    const thumbnail = stateThumbnailBuffer ? new Blob([stateThumbnailBuffer], { type: "image/png" }) : void 0;
    return { state, thumbnail };
  }
  async screenshot() {
    this.sendCommand("SCREENSHOT");
    const screenshotFileName = this.guessScreenshotFileName();
    const screenshotPath = path.join(EmulatorFileSystem.screenshotsDirectory, screenshotFileName);
    const buffer = await this.fs.waitForFile(screenshotPath);
    this.fs.unlink(screenshotPath);
    return new Blob([buffer], { type: "image/png" });
  }
  sendCommand(msg) {
    const bytes = textEncoder.encode(`${msg}
`);
    this.messageQueue.push([bytes, 0]);
  }
  async setup() {
    await this.setupEmscripten();
    await this.setupFileSystem();
  }
  clearStateFile() {
    try {
      this.fs.unlink(this.stateFilePath);
      this.fs.unlink(this.stateThumbnailFilePath);
    } catch {
    }
  }
  fireKeyboardEvent(type, code) {
    const { JSEvents } = this.getEmscripten();
    for (const { eventListenerFunc, eventTypeString } of JSEvents.eventHandlers) {
      if (eventTypeString === type) {
        try {
          eventListenerFunc({ code, target: this.options.element });
        } catch {
        }
      }
    }
  }
  getCurrentRetroarchConfig() {
    const configContent = this.fs.readFile(EmulatorFileSystem.configPath);
    return ini.parse(configContent);
  }
  getElementSize() {
    const { element, size } = this.options;
    return !size || size === "auto" ? { height: element.offsetHeight, width: element.offsetWidth } : size;
  }
  getKeyboardCode(button, player = 1) {
    const config = this.getCurrentRetroarchConfig();
    const configName = `input_player${player}_${button}`;
    const key = config[configName];
    if (!key || key === "nul") {
      return;
    }
    const { length } = key;
    if (length === 1) {
      return `Key${key.toUpperCase()}`;
    }
    if (key[0] === "f" && (length === 2 || length === 3)) {
      return key.toUpperCase();
    }
    if (length === 4 && key.startsWith("num")) {
      return `Numpad${key.at(-1)}`;
    }
    if (length === 7 && key.startsWith("keypad")) {
      return `Digit${key.at(-1)}`;
    }
    return keyboardCodeMap[key] || "";
  }
  guessScreenshotFileName() {
    const date = /* @__PURE__ */ new Date();
    const year = date.getFullYear() % 1e3;
    const month = padZero(date.getMonth() + 1);
    const day = padZero(date.getDate());
    const hour = padZero(date.getHours());
    const minute = padZero(date.getMinutes());
    const second = padZero(date.getSeconds());
    const dateString = `${year}${month}${day}-${hour}${minute}${second}`;
    const baseName = this.romBaseName;
    return `${baseName}-${dateString}.png`;
  }
  keyboardDown(code) {
    this.fireKeyboardEvent("keydown", code);
  }
  async keyboardPress(code, time = 100) {
    this.keyboardDown(code);
    await delay(time);
    this.keyboardUp(code);
  }
  keyboardUp(code) {
    this.fireKeyboardEvent("keyup", code);
  }
  postRun() {
    this.resize(this.canvasInitialSize);
    for (const gamepad of navigator.getGamepads?.() ?? []) {
      if (gamepad) {
        globalThis.dispatchEvent(new GamepadEvent("gamepadconnected", { gamepad }));
      }
    }
    this.updateKeyboardEventHandlers();
  }
  async runEventListeners(event) {
    const { [event]: eventListeners } = this.eventListeners;
    for (const eventListener of eventListeners) {
      await eventListener();
    }
  }
  runMain() {
    checkIsAborted(this.options.signal);
    const { Module } = this.getEmscripten();
    const { arguments: raArgs = [] } = Module;
    const { rom, signal } = this.options;
    if (!Module.arguments && rom.length > 0) {
      const [{ name }] = rom;
      raArgs.push(path.join(EmulatorFileSystem.contentDirectory, name));
    }
    raArgs.push("-c", EmulatorFileSystem.configPath);
    installSetImmediatePolyfill();
    Module.callMain(raArgs);
    signal?.addEventListener("abort", () => {
      this.exit();
    });
    this.gameStatus = "running";
    this.postRun();
  }
  async setupEmscripten() {
    const { core, element, emscriptenModule } = this.options;
    const { wasm } = core;
    const moduleOptions = { canvas: element, preRun: [], wasmBinary: await wasm.getArrayBuffer(), ...emscriptenModule };
    const initialModule = getEmscriptenModuleOverrides(moduleOptions);
    initialModule.preRun?.push(() => initialModule.FS.init(() => this.stdin()));
    const { getEmscripten } = await importCoreJsAsESM(core);
    checkIsAborted(this.options.signal);
    const emscripten = await getEmscripten({ Module: initialModule });
    checkIsAborted(this.options.signal);
    this.emscripten = emscripten;
    const { Module } = emscripten;
    await Module.monitorRunDependencies();
    checkIsAborted(this.options.signal);
  }
  async setupFileSystem() {
    const { Module } = this.getEmscripten();
    const { bios, rom, signal, sram, state } = this.options;
    for (const { name } of bios) {
      if (!name) {
        throw new Error("file name is required for bios");
      }
    }
    const fileSystem = await EmulatorFileSystem.create({ emscriptenModule: Module, signal });
    this.fileSystem = fileSystem;
    if (state) {
      this.fs.mkdirTree(this.stateFileDirectory);
    }
    if (sram) {
      this.fs.mkdirTree(this.sramFileDirectory);
    }
    const filePromises = [];
    filePromises.push(
      ...rom.map((file) => this.fs.writeFile(path.join(EmulatorFileSystem.contentDirectory, file.name), file)),
      ...bios.map((file) => this.fs.writeFile(path.join(EmulatorFileSystem.systemDirectory, file.name), file))
    );
    if (state) {
      filePromises.push(this.fs.writeFile(`${this.stateFilePath}.auto`, state));
    }
    if (sram) {
      filePromises.push(this.fs.writeFile(this.sramFilePath, sram));
    }
    await Promise.all(filePromises);
    checkIsAborted(signal);
    await this.setupRaConfigFiles();
    checkIsAborted(this.options.signal);
  }
  async setupRaConfigFiles() {
    await this.fs.writeIni(EmulatorFileSystem.configPath, this.options.retroarchConfig);
    await this.fs.writeIni(EmulatorFileSystem.coreConfigPath, this.options.retroarchCoreConfig);
    await this.setupRaShaderFiles();
  }
  async setupRaShaderFiles() {
    const { shader } = this.options;
    if (shader.length === 0) {
      return;
    }
    const glslpFiles = shader.filter((file) => file.name.endsWith(".glslp"));
    if (glslpFiles.length === 0) {
      return;
    }
    for (const { name } of shader) {
      if (!name) {
        throw new Error("file name is required for shader");
      }
    }
    const globalGlslpContent = glslpFiles.map((file) => `#reference "${path.join(EmulatorFileSystem.shaderDirectory, file.name)}"`).join("\n");
    await this.fs.writeFile(path.join(EmulatorFileSystem.configDirectory, "global.glslp"), globalGlslpContent);
    await Promise.all(
      shader.map(async (resolvable) => {
        const directory = resolvable.extension === ".glslp" ? EmulatorFileSystem.shaderDirectory : path.join(EmulatorFileSystem.shaderDirectory, "shaders");
        await this.fs.writeFile(path.join(directory, resolvable.name), resolvable);
      })
    );
  }
  // copied from https://github.com/libretro/RetroArch/pull/15017
  stdin() {
    const { messageQueue } = this;
    while (messageQueue.length > 0) {
      const msg = messageQueue[0][0];
      const index = messageQueue[0][1];
      if (index >= msg.length) {
        messageQueue.shift();
      } else {
        messageQueue[0][1] = index + 1;
        return msg[index];
      }
    }
    return null;
  }
  updateKeyboardEventHandlers() {
    const { JSEvents } = this.getEmscripten();
    const { element, respondToGlobalEvents } = this.options;
    if (!respondToGlobalEvents) {
      if (!element.getAttribute("tabindex")) {
        element.tabIndex = -1;
      }
      element.focus();
      element.addEventListener("click", () => {
        element.focus();
      });
    }
    const keyboardEvents = /* @__PURE__ */ new Set(["keydown", "keypress", "keyup"]);
    const globalKeyboardEventHandlers = JSEvents.eventHandlers.filter(
      ({ eventTypeString, target }) => keyboardEvents.has(eventTypeString) && (target === document || target === element)
    );
    for (const globalKeyboardEventHandler of globalKeyboardEventHandlers) {
      const { eventTypeString, handlerFunc, target } = globalKeyboardEventHandler;
      JSEvents.registerOrRemoveHandler({ eventTypeString, target });
      JSEvents.registerOrRemoveHandler({
        ...globalKeyboardEventHandler,
        handlerFunc: (...args) => {
          const [event] = args;
          if (respondToGlobalEvents || event?.target === element) {
            handlerFunc(...args);
          }
        },
        target: respondToGlobalEvents ? document : element
      });
    }
  }
}
class Nostalgist {
  static Nostalgist = Nostalgist;
  static vendors = vendors;
  emulator;
  emulatorOptions;
  options;
  constructor(options) {
    const mergedOptions = {};
    merge(mergedOptions, getGlobalOptions(), options);
    this.options = mergedOptions;
  }
  static clearCache() {
    EmulatorOptions.resetCacheStore();
  }
  /**
   * Update the global options for `Nostalgist`, so everytime the `Nostalgist.launch` method or shortcuts like `Nostalgist.nes` is called, the default options specified here will be used.
   *
   * You may want to specify how to resolve ROMs and RetroArch cores here.
   *
   * @see {@link https://nostalgist.js.org/apis/configure/}
   *
   * @example
   * ```js
   * Nostalgist.configure({
   *   resolveRom({ file }) {
   *     return `https://example.com/roms/${file}`
   *   },
   *   // other configuation can also be specified here
   * })
   * ```
   */
  static configure(options) {
    updateGlobalOptions(options);
  }
  /**
   * A shortcut method for Nostalgist.launch method, with some additional default options for GB emulation.
   *
   * It will use mgba as the default core for emulation.
   *
   * @see {@link https://nostalgist.js.org/apis/gb/}
   */
  static async gb(options) {
    return await Nostalgist.launchSystem("gb", options);
  }
  /**
   * A shortcut method for Nostalgist.launch method, with some additional default options for GBA emulation.
   *
   * It will use mgba as the default core for emulation.
   *
   * @see {@link https://nostalgist.js.org/apis/gba/}
   */
  static async gba(options) {
    return await Nostalgist.launchSystem("gba", options);
  }
  /**
   * A shortcut method for Nostalgist.launch method, with some additional default options for GBC emulation.
   *
   * It will use mgba as the default core for emulation.
   *
   * @see {@link https://nostalgist.js.org/apis/gbc/}
   */
  static async gbc(options) {
    return await Nostalgist.launchSystem("gbc", options);
  }
  /**
   * Launch an emulator and return a `Promise` of the instance of the emulator.
   *
   * @see {@link https://nostalgist.js.org/apis/launch/}
   *
   * @example
   * A simple example:
   * ```js
   * const nostalgist = await Nostalgist.launch({
   *   core: 'fceumm',
   *   rom: 'flappybird.nes',
   * })
   * ```
   *
   * @example
   * A more complex one:
   * ```js
   * const nostalgist = await Nostalgist.launch({
   *   element: document.querySelector('.emulator-canvas'),
   *   core: 'fbneo',
   *   rom: ['mslug.zip'],
   *   bios: ['neogeo.zip'],
   *   retroarchConfig: {
   *     rewind_enable: true,
   *     savestate_thumbnail_enable: true,
   *   }
   *   runEmulatorManually: false,
   *   resolveCoreJs(core) {
   *     return `https://example.com/core/${core}_libretro.js`
   *   },
   *   resolveCoreWasm(core) {
   *     return `https://example.com/core/${core}_libretro.wasm`
   *   },
   *   resolveRom(file) {
   *     return `https://example.com/roms/${file}`
   *   },
   *   resolveBios(bios) {
   *     return `https://example.com/system/${bios}`
   *   },
   * })
   * ```
   */
  static async launch(options) {
    const nostalgist = new Nostalgist(options);
    await nostalgist.load();
    return nostalgist;
  }
  /**
   * A shortcut method for Nostalgist.launch method, with some additional default options for Sega Genesis / Megadrive emulation.
   *
   * It will use genesis_plus_gx as the default core for emulation.
   *
   * @see {@link https://nostalgist.js.org/apis/megadrive/}
   */
  static async megadrive(options) {
    return await Nostalgist.launchSystem("megadrive", options);
  }
  /**
   * A shortcut method for Nostalgist.launch method, with some additional default options for NES emulation.
   *
   * It will use fceumm as the default core for emulation.
   *
   * @see {@link https://nostalgist.js.org/apis/nes/}
   */
  static async nes(options) {
    return await Nostalgist.launchSystem("nes", options);
  }
  static async prepare(options) {
    const nostalgist = new Nostalgist({ ...options, runEmulatorManually: true });
    await nostalgist.load();
    return nostalgist;
  }
  /**
   * Reset the global configuation set by `Nostalgist.configure` to default.
   *
   * @see {@link https://nostalgist.js.org/apis/reset-to-default/}
   */
  static resetToDefault() {
    resetGlobalOptions();
  }
  /**
   * A shortcut method for Nostalgist.launch method, with some additional default options for SNES emulation.
   *
   * It will use snes9x as the default core for emulation.
   *
   * @see {@link https://nostalgist.js.org/apis/snes/}
   */
  static async snes(options) {
    return await Nostalgist.launchSystem("snes", options);
  }
  static async launchSystem(system, options) {
    const optionsResult = await getResult(options);
    const launchOptions = isResolvableFileInput(optionsResult) ? { rom: optionsResult } : optionsResult;
    return await Nostalgist.launch({ ...launchOptions, core: systemCoreMap[system] });
  }
  /**
   * Exit the current running game and the emulator. Remove the canvas element used by the emulator if needed.
   *
   * @see {@link https://nostalgist.js.org/apis/exit/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * nostalgist.exit()
   * ```
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * // the canvas element will not be removed
   * nostalgist.exit({ removeCanvas: false })
   * ```
   */
  exit({ removeCanvas = true } = {}) {
    this.getEmulator().exit();
    if (removeCanvas) {
      this.getCanvas().remove();
    }
  }
  /**
   * Get the canvas DOM element that the current emulator is using.
   *
   * @see {@link https://nostalgist.js.org/apis/get-canvas/}
   */
  getCanvas() {
    return this.getEmulatorOptions().element;
  }
  /**
   * Get the Emscripten object exposed by RetroArch.
   *
   * @see {@link https://nostalgist.js.org/apis/get-emscripten-module/}
   */
  getEmscripten() {
    const emulator = this.getEmulator();
    return emulator.getEmscripten();
  }
  /**
   * Get the Emscripten AL object exposed by RetroArch.
   *
   * @see {@link https://nostalgist.js.org/apis/get-emscripten-module/}
   */
  getEmscriptenAL() {
    const emulator = this.getEmulator();
    return emulator.getEmscripten().AL;
  }
  /**
   * Get the Emscripten FS object of the current running emulator.
   *
   * @see {@link https://nostalgist.js.org/apis/get-emscripten-fs/}
   */
  getEmscriptenFS() {
    const emulator = this.getEmulator();
    const emscripten = emulator.getEmscripten();
    return emscripten.Module.FS;
  }
  /**
   * Get the Emscripten Module object of the current running emulator.
   *
   * @see {@link https://nostalgist.js.org/apis/get-emscripten-module/}
   */
  getEmscriptenModule() {
    const emulator = this.getEmulator();
    const emscripten = emulator.getEmscripten();
    return emscripten.Module;
  }
  getEmulator() {
    const { emulator } = this;
    if (!emulator) {
      throw new Error("emulator is not ready");
    }
    return emulator;
  }
  getEmulatorOptions() {
    if (!this.emulatorOptions) {
      throw new Error("emulator options are not ready");
    }
    return this.emulatorOptions;
  }
  getOptions() {
    return this.options;
  }
  /**
     * Get the status of current emulation.
     *
     * @see {@link https://nostalgist.js.org/apis/get-status/}
     *
     * @returns One of 'initial' | 'paused' | 'running' | 'terminated'
     * @example
     * ```js
     * const nostalgist = await Nostalgist.prepare('flappybird.nes')
     * console.log(nostalgist.getStatus()) // 'initial'
  
     * await nostalgist.launch()
     * console.log(nostalgist.getStatus()) // 'running'
  
     * await nostalgist.pause()
     * console.log(nostalgist.getStatus()) // 'paused'
  
     * nostalgist.exit()
     * console.log(nostalgist.getStatus()) // 'terminated'
     * ```
     */
  getStatus() {
    return this.getEmulator().getStatus();
  }
  /**
   * Launch the emulator, if it's not launched, because of the launch option `runEmulatorManually` being set to `true`.
   * @deprecated Use the `start` method instead.
   * @see {@link https://nostalgist.js.org/apis/launch-emulator/}
   */
  async launchEmulator() {
    return await this.start();
  }
  /**
   * Load a state for the current running emulator and game.
   *
   * @see {@link https://nostalgist.js.org/apis/load-state/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * // save the state
   * const { state } = await nostalgist.saveState()
   *
   * // load the state
   * await nostalgist.loadState(state)
   * ```
   */
  async loadState(state) {
    const resolvable = await ResolvableFile.create(state);
    await this.getEmulator().loadState(resolvable);
  }
  /**
   * Pause the current running game.
   *
   * @see {@link https://nostalgist.js.org/apis/pause/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * nostalgist.pause()
   * ```
   */
  pause() {
    this.getEmulator().pause();
  }
  /**
   * Press a button and then release it programmatically. Analog Joysticks are not supported by now.
   *
   * @see {@link https://nostalgist.js.org/apis/press/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * await nostalgist.press('start')
   * ```
   */
  async press(options) {
    const emulator = this.getEmulator();
    await (typeof options === "string" ? emulator.press(options) : emulator.press(options.button, options.player, options.time));
  }
  /**
   * Press a button programmatically. Analog Joysticks are not supported by now.
   *
   * @see {@link https://nostalgist.js.org/apis/press-down/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * nostalgist.pressDown('start')
   * ```
   */
  pressDown(options) {
    const emulator = this.getEmulator();
    if (typeof options === "string") {
      return emulator.pressDown(options);
    }
    return emulator.pressDown(options.button, options.player);
  }
  /**
   * Release it programmatically. Analog Joysticks are not supported by now.
   *
   * @see {@link https://nostalgist.js.org/apis/press-up/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * nostalgist.pressUp('start')
   * ```
   */
  pressUp(options) {
    const emulator = this.getEmulator();
    if (typeof options === "string") {
      return emulator.pressUp(options);
    }
    return emulator.pressUp(options.button, options.player);
  }
  /**
   * Resize the canvas element of the emulator.
   *
   * @see {@link https://nostalgist.js.org/apis/resize/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * nostalgist.resize({ width: 1000, height: 800 })
   * ```
   */
  resize(size) {
    return this.getEmulator().resize(size);
  }
  /**
   * Restart the current running game.
   *
   * @see {@link https://nostalgist.js.org/apis/restart/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * nostalgist.restart()
   * ```
   */
  restart() {
    this.getEmulator().restart();
  }
  /**
   * Resume the current running game, if it has been paused by `pause`.
   *
   * @see {@link https://nostalgist.js.org/apis/resume/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * nostalgist.pause()
   * await new Promise(resolve => setTimeout(resolve, 1000))
   * nostalgist.resume()
   * ```
   */
  resume() {
    this.getEmulator().resume();
  }
  /**
   * Save the SRAM of the current running game.
   *
   * @see {@link https://nostalgist.js.org/apis/save-sram/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * const sram = await nostalgist.saveSRAM()
   * ```
   */
  async saveSRAM() {
    const emulator = this.getEmulator();
    return await emulator.saveSRAM();
  }
  /**
   * Save the state of the current running game.
   *
   * @see {@link https://nostalgist.js.org/apis/save-state/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * // save the state
   * const { state } = await nostalgist.saveState()
   *
   * // load the state
   * await nostalgist.loadState(state)
   * ```
   * @returns
   * A Promise of the state of the current running game.
   *
   * Its type is like `Promise<{ state: Blob, thumbnail: Blob | undefined }>`.
   *
   * If RetroArch is launched with the option `savestate_thumbnail_enable` set to `true`, which is the default value inside Nostalgist.js, then the `thumbnail` will be a `Blob`. Otherwise the `thumbnail` will be `undefined`.
   */
  async saveState() {
    return await this.getEmulator().saveState();
  }
  /**
   * Take a screenshot for the current running game.
   *
   * @see {@link https://nostalgist.js.org/apis/screenshot/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * const blob = await nostalgist.screenshot()
   * ```
   */
  async screenshot() {
    const emulator = this.getEmulator();
    return await emulator.screenshot();
  }
  /**
   * Send a command to RetroArch.
   * The commands are listed here: https://docs.libretro.com/development/retroarch/network-control-interface/#commands .
   * But not all of them are supported inside a browser.
   *
   * @see {@link https://nostalgist.js.org/apis/send-command/}
   *
   * @example
   * ```js
   * const nostalgist = await Nostalgist.nes('flappybird.nes')
   *
   * nostalgist.sendCommand('FAST_FORWARD')
   * ```
   */
  sendCommand(command) {
    const emulator = this.getEmulator();
    return emulator.sendCommand(command);
  }
  /**
   * Start the emulator if it's not started because of the instance is returned by `Nostalgist.prepare` rather than `Nostalgist.launch`, or the option `runEmulatorManually` for `Nostalgist.launch` being set to `true`.
   *
   * @see {@link https://nostalgist.js.org/apis/start/}
   */
  async start() {
    return await this.getEmulator().launch();
  }
  /**
   * Load options and then launch corresponding emulator if should
   */
  async load() {
    this.emulatorOptions = await EmulatorOptions.create(this.options);
    checkIsAborted(this.options.signal);
    if (this.options.setupEmulatorManually) {
      return;
    }
    await this.setupEmulator();
    if (this.options.runEmulatorManually) {
      return;
    }
    await this.start();
  }
  async setupEmulator() {
    const emulatorOptions = this.getEmulatorOptions();
    this.emulator = new Emulator(emulatorOptions);
    this.emulator.on("onLaunch", () => this.options.onLaunch?.(this)).on("beforeLaunch", () => this.options.beforeLaunch?.(this));
    await this.emulator.setup();
  }
}
export {
  Nostalgist
};