Refactor code structure for improved readability and maintainability

This commit is contained in:
Michael Green 2025-12-16 19:14:01 +11:00
parent 41d5f9420d
commit d587c20273
5 changed files with 223 additions and 51 deletions

View File

@ -90,7 +90,7 @@ class EJS_GameManager {
await new Promise(async (done) => {
try {
const url = this.EJS.config.externalFiles[key];
const cacheItem = await this.EJS.downloadFile(url, this.EJS.downloadType.support, "GET", {}, null, null, null, 30000, "arraybuffer", false, true);
const cacheItem = await this.EJS.downloadFile(url, this.EJS.downloadType.support.name, "GET", {}, null, null, null, 30000, "arraybuffer", false, this.EJS.downloadType.support.dontCache);
let path = key;
if (key.trim().endsWith("/")) {
@ -347,7 +347,7 @@ IF EXIST AUTORUN.BAT AUTORUN.BAT
loadPpssppAssets() {
return new Promise(async (resolve, reject) => {
try {
const cacheItem = await this.EJS.downloader.downloadFile("data/cores/ppsspp-assets.zip", this.EJS.downloadType.support, "GET", {}, null, null, null, 30000, "arraybuffer", false, false);
const cacheItem = await this.EJS.downloader.downloadFile("data/cores/ppsspp-assets.zip", this.EJS.downloadType.support.name, "GET", {}, null, null, null, 30000, "arraybuffer", false, this.EJS.downloadType.support.dontCache);
console.log(cacheItem);

View File

@ -36,6 +36,11 @@ class EJS_Download {
* @returns {Promise<EJS_CacheItem>} - The downloaded file as an EJS_CacheItem.
*/
downloadFile(url, type, method = "GET", headers = {}, body = null, onProgress = null, onComplete = null, timeout = 30000, responseType = "arraybuffer", forceExtract = false, dontCache = false) {
let cacheActiveText = " (cache usage requested)"
if (dontCache) {
cacheActiveText = "";
}
console.log("[EJS Download] Downloading " + responseType + " file: " + url + cacheActiveText);
return new Promise(async (resolve, reject) => {
try {
// Use the provided storageCache or create a temporary one

View File

@ -87,13 +87,13 @@ class EJSCompression {
path = "compression/libunrar.js";
obj = "rar";
}
const res = await this.EJS.downloadFile(path, this.EJS.downloadType.support, null, false, { responseType: "text", method: "GET" }, false, true);
const res = await this.EJS.downloadFile(path, this.EJS.downloadType.support.name, null, false, { responseType: "text", method: "GET" }, false, true);
if (res === -1) {
this.EJS.startGameError(this.EJS.localization("Network Error"));
return;
}
if (method === "rar") {
const res2 = await this.EJS.downloadFile("compression/libunrar.wasm", this.EJS.downloadType.support, null, false, { responseType: "arraybuffer", method: "GET" }, false, true);
const res2 = await this.EJS.downloadFile("compression/libunrar.wasm", this.EJS.downloadType.support.name, null, false, { responseType: "arraybuffer", method: "GET" }, false, true);
if (res2 === -1) {
this.EJS.startGameError(this.EJS.localization("Network Error"));
return;

View File

@ -44,13 +44,15 @@ class EmulatorJS {
return rv;
}
downloadType = {
"rom": "ROM",
"core": "Core",
"bios": "BIOS",
"reports": "Reports",
"states": "States",
"support": "Support",
"unknown": "Unknown"
"rom": { "name": "ROM", "dontCache": false },
"core": { "name": "Core", "dontCache": false },
"bios": { "name": "BIOS", "dontCache": false },
"parent": { "name": "Parent", "dontCache": false },
"patch": { "name": "Patch", "dontCache": false },
"reports": { "name": "Reports", "dontCache": false },
"states": { "name": "States", "dontCache": true },
"support": { "name": "Support", "dontCache": false },
"unknown": { "name": "Unknown", "dontCache": true }
}
requiresThreads(core) {
const requiresThreads = ["ppsspp", "dosbox_pure"];
@ -761,7 +763,7 @@ class EmulatorJS {
const cacheBustParam = Math.floor(Date.now() / cacheBustInterval);
const reportUrl = `${report}?v=${cacheBustParam}`;
this.downloadFile(reportUrl, this.downloadType.reports, null, false, { responseType: "text", method: "GET" }, false, true).then(async rep => {
this.downloadFile(reportUrl, this.downloadType.reports.name, null, false, { responseType: "text", method: "GET" }, false, this.downloadType.reports.dontCache).then(async rep => {
if (rep === -1 || typeof rep === "string" || typeof rep.data === "string") {
rep = {};
} else {
@ -793,16 +795,16 @@ class EmulatorJS {
// Download the core
console.log("[EJS Core] Downloading core:", filename);
const corePath = "cores/" + filename;
let res = await this.downloadFile(corePath, this.downloadType.core, (progress) => {
let res = await this.downloadFile(corePath, this.downloadType.core.name, (progress) => {
this.textElem.innerText = this.localization("Download Game Core") + progress;
}, false, { responseType: "arraybuffer", method: "GET" }, true, false);
}, false, { responseType: "arraybuffer", method: "GET" }, true, this.downloadType.core.dontCache);
if (res === -1) {
console.log("File not found, attemping to fetch from emulatorjs cdn.");
console.error("**THIS METHOD IS A FAILSAFE, AND NOT OFFICIALLY SUPPORTED. USE AT YOUR OWN RISK**");
let version = this.ejs_version.endsWith("-beta") ? "nightly" : this.ejs_version;
res = await this.downloadFile(`https://cdn.emulatorjs.org/${version}/data/${corePath}`, this.downloadType.core, (progress) => {
res = await this.downloadFile(`https://cdn.emulatorjs.org/${version}/data/${corePath}`, this.downloadType.core.name, (progress) => {
this.textElem.innerText = this.localization("Download Game Core") + progress;
}, true, { responseType: "arraybuffer", method: "GET" }, true, false);
}, true, { responseType: "arraybuffer", method: "GET" }, true, this.downloadType.core.dontCache);
if (res === -1) {
if (!this.supportsWebgl2) {
this.startGameError(this.localization("Outdated graphics driver"));
@ -866,9 +868,9 @@ class EmulatorJS {
}
this.textElem.innerText = this.localization("Download Game State");
this.downloadFile(this.config.loadState, this.downloadType.states, (progress) => {
this.downloadFile(this.config.loadState, this.downloadType.states.name, (progress) => {
this.textElem.innerText = this.localization("Download Game State") + progress;
}, true, { responseType: "arraybuffer", method: "GET" }, false, true).then((res) => {
}, true, { responseType: "arraybuffer", method: "GET" }, false, this.downloadType.states.dontCache).then((res) => {
if (res === -1) {
this.startGameError(this.localization("Error downloading game state"));
return;
@ -884,11 +886,10 @@ class EmulatorJS {
}
downloadGameFile(assetUrl, type, progressMessage, decompressProgressMessage) {
return new Promise(async (resolve, reject) => {
if ((typeof assetUrl !== "string" || !assetUrl.trim()) && !this.toData(assetUrl, true)) {
if ((typeof assetUrl !== "string" || !assetUrl.trim()) && !this.toData(assetUrl, true) && !(assetUrl instanceof File)) {
return resolve(assetUrl);
}
const gotData = async (input) => {
console.log(input);
const coreFilename = "/" + this.fileName;
const coreFilePath = coreFilename.substring(0, coreFilename.length - coreFilename.split("/").pop().length);
if (this.config.dontExtractBIOS === true) {
@ -918,12 +919,12 @@ class EmulatorJS {
}
}
console.log("[EJS " + type.toUpperCase() + "] Downloading " + assetUrl + " (cached by EJS_Download)");
console.log("[EJS " + type.toUpperCase() + "] Downloading " + assetUrl);
this.textElem.innerText = progressMessage;
const res = await this.downloadFile(assetUrl, type, (progress) => {
const res = await this.downloadFile(assetUrl, type.name, (progress) => {
this.textElem.innerText = progressMessage + progress;
}, true, { responseType: "arraybuffer", method: "GET" });
}, true, { responseType: "arraybuffer", method: "GET" }, false, type.dontCache);
if (res === -1) {
this.startGameError(this.localization("Network Error"));
reject();
@ -941,19 +942,19 @@ class EmulatorJS {
}
downloadGamePatch() {
return new Promise(async (resolve) => {
this.config.gamePatchUrl = await this.downloadGameFile(this.config.gamePatchUrl, "patch", this.localization("Download Game Patch"), this.localization("Decompress Game Patch"));
this.config.gamePatchUrl = await this.downloadGameFile(this.config.gamePatchUrl, this.downloadType.patch, this.localization("Download Game Patch"), this.localization("Decompress Game Patch"));
resolve();
});
}
downloadGameParent() {
return new Promise(async (resolve) => {
this.config.gameParentUrl = await this.downloadGameFile(this.config.gameParentUrl, "parent", this.localization("Download Game Parent"), this.localization("Decompress Game Parent"));
this.config.gameParentUrl = await this.downloadGameFile(this.config.gameParentUrl, this.downloadType.parent, this.localization("Download Game Parent"), this.localization("Decompress Game Parent"));
resolve();
});
}
downloadBios() {
return new Promise(async (resolve) => {
this.config.biosUrl = await this.downloadGameFile(this.config.biosUrl, "bios", this.localization("Download Game BIOS"), this.localization("Decompress Game BIOS"));
this.config.biosUrl = await this.downloadGameFile(this.config.biosUrl, this.downloadType.bios, this.localization("Download Game BIOS"), this.localization("Decompress Game BIOS"));
resolve();
});
}
@ -967,6 +968,15 @@ class EmulatorJS {
return new Promise(resolve => {
this.textElem.innerText = this.localization("Download Game Data");
// List of cores to generate a CUE file for, if it doesn't exist.
const cueGeneration = ["mednafen_psx_hw"];
const prioritizeExtensions = ["cue", "ccd", "toc", "m3u"];
let createCueFile = cueGeneration.includes(this.getCore());
if (this.config.disableCue === true) {
createCueFile = false;
}
const writeFilesToFS = (fileName, fileData, altName) => {
if (fileName.includes("/")) {
const paths = fileName.split("/");
@ -1005,21 +1015,21 @@ class EmulatorJS {
if (isoFile === null && ["iso", "cso", "chd", "elf"].includes(ext)) {
isoFile = fileName;
}
if (["cue", "ccd", "toc", "m3u"].includes(ext)) {
if (prioritizeExtensions.includes(ext)) {
const currentCueExt = (cueFile === null) ? null : cueFile.split(".").pop().toLowerCase();
if (coreName === "psx") {
//always prefer m3u files for psx cores
if (selectedCueExt !== "m3u") {
// Always prefer m3u files for psx cores
if (currentCueExt !== "m3u") {
if (cueFile === null || ext === "m3u") {
cueFile = fileName;
selectedCueExt = ext;
}
}
} else {
//prefer cue or ccd files over toc or m3u
if (!["cue", "ccd"].includes(selectedCueExt)) {
if (cueFile === null || ["cue", "ccd"].includes(ext)) {
const priority = ["cue", "ccd"]
// Prefer cue or ccd files over toc or m3u
if (!priority.includes(currentCueExt)) {
if (cueFile === null || priority.includes(ext)) {
cueFile = fileName;
selectedCueExt = ext;
}
}
}
@ -1030,14 +1040,16 @@ class EmulatorJS {
} else {
this.fileName = fileNames[0];
}
if (isoFile !== null && (supportsExt("iso") || supportsExt("cso") || supportsExt("chd") || supportsExt("elf"))) {
if (isoFile !== null && supportsExt(isoFile.split(".").pop().toLowerCase())) {
this.fileName = isoFile;
} else if (supportsExt("cue") || supportsExt("ccd") || supportsExt("toc") || supportsExt("m3u")) {
if (cueFile !== null) {
this.fileName = cueFile;
} else if (!disableCue) {
this.fileName = this.gameManager.createCueFile(fileNames);
}
}
if (cueFile !== null && supportsExt(cueFile.split(".").pop().toLowerCase())) {
this.fileName = cueFile;
} else if (createCueFile && supportsExt("m3u") && supportsExt("cue")) {
this.fileName = this.gameManager.createCueFile(fileNames);
}
if (this.getCore(true) === "dos" && !this.config.disableBatchBootup) {
this.fileName = this.gameManager.writeBootupBatchFile();
}
resolve();
};
@ -1056,6 +1068,7 @@ class EmulatorJS {
let disableCue = false;
if (["pcsx_rearmed", "genesis_plus_gx", "picodrive", "mednafen_pce", "smsplus", "vice_x64", "vice_x64sc", "vice_x128", "vice_xvic", "vice_xpet", "puae"].includes(coreName) && this.config.disableCue === undefined) {
disableCue = true;
console.log("DISABLING CUE!");
} else {
disableCue = this.config.disableCue;
}
@ -1104,7 +1117,7 @@ class EmulatorJS {
cacheKey,
cacheFiles,
Date.now(),
"ROM",
this.downloadType.rom.name,
"arraybuffer",
romFilename,
romFilename,
@ -1119,9 +1132,9 @@ class EmulatorJS {
const downloadFile = async () => {
console.log("[EJS ROM] Downloading ROM " + this.config.gameUrl.name);
// create new download class
const res = await this.downloadFile(this.config.gameUrl, this.downloadType.rom, (progress) => {
const res = await this.downloadFile(this.config.gameUrl, this.downloadType.rom.name, (progress) => {
this.textElem.innerText = this.localization("Download Game Data") + progress;
}, true, { responseType: "arraybuffer", method: "GET" }, false, false);
}, true, { responseType: "arraybuffer", method: "GET" }, false, this.downloadType.rom.dontCache);
if (res === -1) {
this.startGameError(this.localization("Network Error"));
return;
@ -1210,6 +1223,29 @@ class EmulatorJS {
if (this.debug) args.push("-v");
args.push("/" + this.fileName);
if (this.debug) console.log(args);
if (this.debug && this.gameManager && this.gameManager.FS) {
const FS = this.gameManager.FS;
console.log("File system directory");
function listDir(path, indent = "") {
try {
const entries = FS.readdir(path);
for (const entry of entries) {
if (entry === "." || entry === "..") continue;
const fullPath = path === "/" ? `/${entry}` : `${path}/${entry}`;
const stat = FS.stat(fullPath);
if (FS.isDir(stat.mode)) {
console.log(`${indent}[DIR] ${fullPath}`);
listDir(fullPath, indent + " ");
} else {
console.log(`${indent}${fullPath}`);
}
}
} catch (e) {
console.warn("Error reading directory:", path, e);
}
}
listDir("/");
}
this.Module.callMain(args);
if (typeof this.config.softLoad === "number" && this.config.softLoad > 0) {
this.resetTimeout = setTimeout(() => {

View File

@ -49,6 +49,35 @@
color: #ddd
}
#advanced {
color: #aaa;
text-align: left;
width: 30em;
max-width: 80%;
background-color: #333;
border-radius: 0.4em;
border: 2px solid #555;
position: relative;
flex-direction: column;
transition-duration: 0.2s;
overflow: hidden;
font-family: monospace;
font-weight: bold;
font-size: 20px;
margin: 5px;
padding: 10px;
}
#advancedOptionsBox {
margin-top: 10px;
font-size: 14px;
margin-left: 20px;
}
.advancedOptionsBoxRow {
margin-top: 10px;
}
#input {
cursor: pointer;
position: absolute;
@ -107,34 +136,132 @@
<input type="file" id ="input" title="Upload" />
Drag ROM file or click here
</div>
<div id="advanced">
<div id="advancedToggleBox">
<input type="checkbox" id="advancedToggle" />
<label for="advancedToggle"> Show Advanced Options</label>
</div>
<div id="advancedOptionsBox">
<div class="advancedOptionsBoxRow">
<input type="file" id ="inputAdvanced" title="BIOS" />
<label for="inputAdvanced"> Upload BIOS file (if required)</label>
</div>
<div class="advancedOptionsBoxRow">
<input type="checkbox" id="debugToggle" />
<label for="debugToggle"> Enable Debug Mode</label>
</div>
<div id="advancedOptionsBoxThreads" class="advancedOptionsBoxRow">
<input type="checkbox" id="threadsToggle" />
<label for="threadsToggle"> Enable Threads</label>
</div>
<div class="advancedOptionsBoxRow">
<span>Browser mode: </span>
<input type="radio" id="browserModeAuto" name="browserMode" value="auto">
<label for="browserModeAuto"> Auto </label>
<input type="radio" id="browserModeDesktop" name="browserMode" value="desktop">
<label for="browserModeDesktop"> Desktop </label>
<input type="radio" id="browserModeMobile" name="browserMode" value="mobile">
<label for="browserModeMobile"> Mobile </label>
</div>
</div>
</div>
<script>
let enableDebug = false;
let enableThreads = false;
let browserMode;
const biosFileInput = document.getElementById("inputAdvanced");
const advancedToggle = document.getElementById("advancedToggle");
const advanced = document.getElementById("advanced");
const advancedOptionsBox = document.getElementById("advancedOptionsBox");
advancedOptionsBox.style.display = "none";
advancedToggle.addEventListener("change", (event) => {
if (event.target.checked) {
advancedOptionsBox.style.display = "block";
} else {
advancedOptionsBox.style.display = "none";
}
});
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const debugToggle = document.getElementById("debugToggle");
debugToggle.addEventListener("change", (event) => {
enableDebug = event.target.checked;
console.log(`Debug is ${enableDebug ? "enabled" : "disabled"}`);
});
const threadsToggleAvailable = document.getElementById("advancedOptionsBoxThreads");
const threadsToggle = document.getElementById("threadsToggle");
threadsToggle.addEventListener("change", (event) => {
enableThreads = event.target.checked;
console.log(`Threads are ${enableThreads ? "enabled" : "disabled"}`);
});
if (parseInt(urlParams.get("debug")) === 1 || urlParams.get("debug") === "true") {
enableDebug = true;
debugToggle.checked = true;
console.log("Debug is enabled");
} else {
debugToggle.checked = false;
console.log("Debug is disabled");
}
if (parseInt(urlParams.get("threads")) === 1 || urlParams.get("threads") === "true") {
if (window.SharedArrayBuffer) {
if (window.SharedArrayBuffer) {
threadsToggleAvailable.style.display = "block";
if (parseInt(urlParams.get("threads")) === 1 || urlParams.get("threads") === "true") {
enableThreads = true;
threadsToggle.checked = true;
console.log("Threads are enabled");
} else {
console.warn("Threads are disabled as SharedArrayBuffer is not available. Threads requires two headers to be set when sending you html page. See https://stackoverflow.com/a/68630724");
threadsToggle.checked = false;
console.log("Threads are disabled");
}
} else {
console.log("Threads are disabled");
threadsToggleAvailable.style.display = "none";
console.warn("Threads are disabled as SharedArrayBuffer is not available. Threads requires two headers to be set when sending you html page. See https://stackoverflow.com/a/68630724");
}
const browserModeAuto = document.getElementById("browserModeAuto");
browserModeAuto.addEventListener("change", (event) => {
if (event.target.checked) {
browserMode = undefined;
console.log("Browser mode set to auto");
}
});
const browserModeDesktop = document.getElementById("browserModeDesktop");
browserModeDesktop.addEventListener("change", (event) => {
if (event.target.checked) {
browserMode = "desktop";
console.log("Browser mode set to desktop");
}
});
const browserModeMobile = document.getElementById("browserModeMobile");
browserModeMobile.addEventListener("change", (event) => {
if (event.target.checked) {
browserMode = "mobile";
console.log("Browser mode set to mobile");
}
});
if (urlParams.get("browserMode")) {
browserMode = urlParams.get("browserMode");
switch (browserMode) {
case 1:
case "1":
case "mobile":
browserModeMobile.checked = true;
break;
case 2:
case "2":
case "desktop":
browserModeDesktop.checked = true;
break;
default:
browserModeAuto.checked = true;
break;
}
} else {
browserModeAuto.checked = true;
}
if (urlParams.get("rom")) {
@ -146,6 +273,8 @@
const url = upload ? input.files[0] : `roms/${file}`;
const parts = upload ? input.files[0].name.split(".") : file.split(".");
const biosUrl = biosFileInput.files.length > 0 ? biosFileInput.files[0] : "";
const core = await (async (ext) => {
if (["fds", "nes", "unif", "unf"].includes(ext))
return "nes"
@ -208,6 +337,7 @@
"Commodore VIC20": "vice_xvic",
"Commodore Plus/4": "vice_xplus4",
"Commodore PET": "vice_xpet",
"Amiga": "puae",
"PlayStation Portable": "ppsspp",
"DOSBOX": "dosbox_pure"
}
@ -260,13 +390,14 @@
const top = document.getElementById("top");
top.remove();
box.remove()
div.appendChild(sub)
box.remove();
div.appendChild(sub);
advanced.remove();
document.body.appendChild(div)
window.EJS_player = "#game";
window.EJS_gameName = parts.shift();
window.EJS_biosUrl = "";
window.EJS_biosUrl = biosUrl;
window.EJS_gameUrl = url;
window.EJS_core = core;
window.EJS_pathtodata = "data/";