From a8d0284e061161d2ce543347d62fc6f817c914df Mon Sep 17 00:00:00 2001 From: Ethan O'Brien <77750390+ethanaobrien@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:54:17 -0600 Subject: [PATCH 001/137] Catch undefined EJS_Runtime as a start error --- data/src/emulator.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/src/emulator.js b/data/src/emulator.js index 1f63296d..07ed789d 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -989,6 +989,11 @@ class EmulatorJS { })(); } initModule(wasmData, threadData) { + if (typeof window.EJS_Runtime !== "function") { + console.warn("EJS_Runtime is not defined!"); + this.startGameError(this.localization("Failed to start game")); + return; + } window.EJS_Runtime({ noInitialRun: true, onRuntimeInitialized: null, From c0ae2e2cafefac53b10a0898a6f021479c77b536 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Tue, 10 Dec 2024 07:37:36 -0600 Subject: [PATCH 002/137] Fix default webgl2 option --- data/src/emulator.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 07ed789d..cda1fbb9 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -555,7 +555,7 @@ class EmulatorJS { } const report = "cores/reports/" + this.getCore() + ".json"; this.downloadFile(report, (rep) => { - if (rep === -1 || typeof report === "string") { + if (rep === -1 || typeof rep === "string") { rep = {}; } else { rep = rep.data; @@ -3832,7 +3832,7 @@ class EmulatorJS { }; } saveSettings() { - if (!window.localStorage || this.config.disableLocalStorage || !this.settingsLoaded) return; + if (!window.localStorage || this.config.disableLocalStorage || !this.settingsLoaded || !this.started) return; const coreSpecific = { controlSettings: this.controls, settings: this.settings, @@ -3846,23 +3846,22 @@ class EmulatorJS { localStorage.setItem("ejs-"+this.getCore()+"-settings", JSON.stringify(coreSpecific)); } preGetSetting(setting) { - if (!window.localStorage || this.config.disableLocalStorage) { - if (this.config.defaultOptions && this.config.defaultOptions[setting]) { - return this.config.defaultOptions[setting]; + if (window.localStorage && !this.config.disableLocalStorage) { + let coreSpecific = localStorage.getItem("ejs-"+this.getCore()+"-settings"); + try { + coreSpecific = JSON.parse(coreSpecific); + if (!coreSpecific || !coreSpecific.settings) { + return false; + } + return coreSpecific.settings[setting]; + } catch (e) { + console.warn("Could not load previous settings", e); } - return false; } - let coreSpecific = localStorage.getItem("ejs-"+this.getCore()+"-settings"); - try { - coreSpecific = JSON.parse(coreSpecific); - if (!coreSpecific || !coreSpecific.settings) { - return false; - } - return coreSpecific.settings[setting]; - } catch (e) { - console.warn("Could not load previous settings", e); - return false; + if (this.config.defaultOptions && this.config.defaultOptions[setting]) { + return this.config.defaultOptions[setting]; } + return false; } loadSettings() { if (!window.localStorage || this.config.disableLocalStorage) return; @@ -3908,16 +3907,11 @@ class EmulatorJS { } } } - menuOptionChanged(option, value) { - this.saveSettings(); - if (this.debug) console.log(option, value); - if (!this.gameManager) return; + handleSpecialOptions(option, value) { if (option === "shader") { this.enableShader(value); - return; } else if (option === "disk") { this.gameManager.setCurrentDisk(value); - return; } else if (option === "virtual-gamepad") { this.toggleVirtualGamepad(value !== "disabled"); } else if (option === "virtual-gamepad-left-handed-mode") { @@ -3961,6 +3955,12 @@ class EmulatorJS { } else if (option === "vsync") { this.gameManager.setVSync(value === "enabled"); } + } + menuOptionChanged(option, value) { + this.saveSettings(); + if (this.debug) console.log(option, value); + if (!this.gameManager) return; + this.handleSpecialOptions(option, value); this.gameManager.setVariable(option, value); this.saveSettings(); } From c5645a7ef94579d126a802bfa64941c5ec49b24b Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Tue, 10 Dec 2024 08:36:43 -0600 Subject: [PATCH 003/137] Organize settings menu --- data/src/emulator.js | 147 +++++++++++++++++++++++++++++++------------ 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index cda1fbb9..da8d989c 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -4155,10 +4155,65 @@ class EmulatorJS { const nested = this.createElement("div"); nested.classList.add("ejs_settings_transition"); this.settings = {}; - - const home = this.createElement("div"); - home.style.overflow = "auto"; const menus = []; + + const createSettingParent = (child, title, parentElement) => { + const rv = this.createElement("div"); + rv.style.overflow = "auto"; + rv.classList.add("ejs_setting_menu"); + + if (child) { + const menuOption = this.createElement("div"); + menuOption.classList.add("ejs_settings_main_bar"); + const span = this.createElement("span"); + span.innerText = title; + + menuOption.appendChild(span); + parentElement.appendChild(menuOption); + + const menu = this.createElement("div"); + menus.push(menu); + menu.style.overflow = "auto"; + menu.setAttribute("hidden", ""); + const button = this.createElement("button"); + const goToHome = () => { + const homeSize = this.getElementSize(parentElement); + nested.style.width = (homeSize.width+20) + "px"; + nested.style.height = homeSize.height + "px"; + menu.setAttribute("hidden", ""); + parentElement.removeAttribute("hidden"); + } + this.addEventListener(menuOption, "click", (e) => { + const targetSize = this.getElementSize(menu); + nested.style.width = (targetSize.width+20) + "px"; + nested.style.height = targetSize.height + "px"; + menu.removeAttribute("hidden"); + parentElement.setAttribute("hidden", ""); + }) + this.addEventListener(button, "click", goToHome); + + button.type = "button"; + button.classList.add("ejs_back_button"); + menu.appendChild(button); + const pageTitle = this.createElement("span"); + pageTitle.innerText = title; + pageTitle.classList.add("ejs_menu_text_a"); + button.appendChild(pageTitle); +/* + const optionsMenu = this.createElement("div"); + optionsMenu.classList.add("ejs_setting_menu"); + + menu.appendChild(optionsMenu);*/ + + menu.appendChild(rv); + nested.appendChild(menu); + } + + return rv; + } + + const home = createSettingParent(); + this.handleSettingsResize = () => { let needChange = false; if (this.settingsMenu.style.display !== "") { @@ -4184,9 +4239,8 @@ class EmulatorJS { this.settingsMenu.style.opacity = ""; } } - - home.classList.add("ejs_setting_menu"); nested.appendChild(home); + let funcs = []; this.changeSettingOption = (title, newValue) => { this.settings[title] = newValue; @@ -4194,7 +4248,9 @@ class EmulatorJS { } let allOpts = {}; - const addToMenu = (title, id, options, defaultOption) => { + const addToMenu = (title, id, options, defaultOption, parentElement, useParentParent) => { + parentElement = parentElement || home; + const transitionElement = useParentParent ? parentElement.parentElement : parentElement; const menuOption = this.createElement("div"); menuOption.classList.add("ejs_settings_main_bar"); const span = this.createElement("span"); @@ -4206,7 +4262,7 @@ class EmulatorJS { span.appendChild(current); menuOption.appendChild(span); - home.appendChild(menuOption); + parentElement.appendChild(menuOption); const menu = this.createElement("div"); menus.push(menu); @@ -4214,18 +4270,18 @@ class EmulatorJS { menu.setAttribute("hidden", ""); const button = this.createElement("button"); const goToHome = () => { - const homeSize = this.getElementSize(home); + const homeSize = this.getElementSize(transitionElement); nested.style.width = (homeSize.width+20) + "px"; nested.style.height = homeSize.height + "px"; menu.setAttribute("hidden", ""); - home.removeAttribute("hidden"); + transitionElement.removeAttribute("hidden"); } this.addEventListener(menuOption, "click", (e) => { const targetSize = this.getElementSize(menu); nested.style.width = (targetSize.width+20) + "px"; nested.style.height = targetSize.height + "px"; menu.removeAttribute("hidden"); - home.setAttribute("hidden", ""); + transitionElement.setAttribute("hidden", ""); }) this.addEventListener(button, "click", goToHome); @@ -4296,6 +4352,8 @@ class EmulatorJS { nested.appendChild(menu); } + const graphicsOptions = createSettingParent(true, "Graphics Settings", home); + if (this.config.shaders) { const builtinShaders = { '2xScaleHQ.glslp': this.localization("2xScaleHQ"), @@ -4323,67 +4381,72 @@ class EmulatorJS { shaderMenu[shaderName] = shaderName; } } - addToMenu(this.localization('Shaders'), 'shader', shaderMenu, 'disabled'); + addToMenu(this.localization('Shaders'), 'shader', shaderMenu, 'disabled', graphicsOptions, true); } if (this.supportsWebgl2) { - addToMenu(this.localization('WebGL2') + " (" + this.localization('Requires page reload') + ")", 'webgl2Enabled', { + addToMenu(this.localization('WebGL2') + " (" + this.localization('Requires restart') + ")", 'webgl2Enabled', { 'enabled': this.localization("Enabled"), 'disabled': this.localization("Disabled") - }, this.webgl2Enabled ? "enabled" : "disabled"); + }, this.webgl2Enabled ? "enabled" : "disabled", graphicsOptions, true); } addToMenu(this.localization('FPS'), 'fps', { 'show': this.localization("show"), 'hide': this.localization("hide") - }, 'hide'); + }, 'hide', graphicsOptions, true); addToMenu(this.localization("VSync"), "vsync", { 'enabled': this.localization("Enabled"), 'disabled': this.localization("Disabled") - }, "enabled"); - - addToMenu(this.localization('Fast Forward Ratio'), 'ff-ratio', [ - "1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "6.0", "6.5", "7.0", "7.5", "8.0", "8.5", "9.0", "9.5", "10.0", "unlimited" - ], "3.0"); + }, "enabled", graphicsOptions, true); - addToMenu(this.localization('Slow Motion Ratio'), 'sm-ratio', [ - "1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "6.0", "6.5", "7.0", "7.5", "8.0", "8.5", "9.0", "9.5", "10.0" - ], "3.0"); + addToMenu(this.localization('Video Rotation (requires reload)'), 'videoRotation', { + '0': "0 deg", + '1': "90 deg", + '2': "180 deg", + '3': "270 deg" + }, this.videoRotation.toString(), graphicsOptions, true); + + const speedOptions = createSettingParent(true, "Speed Options", home); addToMenu(this.localization('Fast Forward'), 'fastForward', { 'enabled': this.localization("Enabled"), - 'disabled': this.localization("Disabled") - }, "disabled"); + 'disabled': this.localization("Disabled") + }, "disabled", speedOptions, true); + + addToMenu(this.localization('Fast Forward Ratio'), 'ff-ratio', [ + "1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "6.0", "6.5", "7.0", "7.5", "8.0", "8.5", "9.0", "9.5", "10.0", "unlimited" + ], "3.0", speedOptions, true); addToMenu(this.localization('Slow Motion'), 'slowMotion', { 'enabled': this.localization("Enabled"), - 'disabled': this.localization("Disabled") - }, "disabled"); + 'disabled': this.localization("Disabled") + }, "disabled", speedOptions, true); - addToMenu(this.localization('Rewind Enabled (requires restart)'), 'rewindEnabled', { + addToMenu(this.localization('Slow Motion Ratio'), 'sm-ratio', [ + "1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "6.0", "6.5", "7.0", "7.5", "8.0", "8.5", "9.0", "9.5", "10.0" + ], "3.0", speedOptions, true); + + addToMenu(this.localization('Rewind Enabled (requires reload)'), 'rewindEnabled', { 'enabled': this.localization("Enabled"), 'disabled': this.localization("Disabled") - }, 'disabled'); + }, 'disabled', speedOptions, true); - addToMenu(this.localization('Rewind Granularity'), 'rewind-granularity', [ - '1', '3', '6', '12', '25', '50', '100' - ], '6'); + if (this.rewindEnabled) { + addToMenu(this.localization('Rewind Granularity'), 'rewind-granularity', [ + '1', '3', '6', '12', '25', '50', '100' + ], '6', speedOptions, true); + } if (this.saveInBrowserSupported()) { - addToMenu(this.localization('Save State Slot'), 'save-state-slot', ["1", "2", "3", "4", "5", "6", "7", "8", "9"], "1"); + const saveStateOpts = createSettingParent(true, "Save States", home); + addToMenu(this.localization('Save State Slot'), 'save-state-slot', ["1", "2", "3", "4", "5", "6", "7", "8", "9"], "1", saveStateOpts, true); addToMenu(this.localization('Save State Location'), 'save-state-location', { 'download': this.localization("Download"), 'browser': this.localization("Keep in Browser") - }, 'download'); + }, 'download', saveStateOpts, true); } - - addToMenu(this.localization('Video Rotation (requires restart)'), 'videoRotation', { - '0': "0 deg", - '1': "90 deg", - '2': "180 deg", - '3': "270 deg" - }, this.videoRotation.toString()); if (this.touch || navigator.maxTouchPoints > 0) { addToMenu(this.localization('Virtual Gamepad'), 'virtual-gamepad', { @@ -4395,6 +4458,8 @@ class EmulatorJS { 'disabled': this.localization("Disabled") }, 'disabled'); } + + const coreOptions = createSettingParent(true, "Core Options", home); let coreOpts; try { coreOpts = this.gameManager.getCoreOptions(); @@ -4413,7 +4478,7 @@ class EmulatorJS { } addToMenu(this.localization(optionName, this.settingsLanguage), name.split("|")[0], availableOptions, - (name.split("|").length > 1) ? name.split("|")[1] : options[0].replace('(Default) ', '')); + (name.split("|").length > 1) ? name.split("|")[1] : options[0].replace('(Default) ', ''), coreOptions, true); }) } From 0f9030c686f215e75771122c964ac40dff033f0e Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Tue, 10 Dec 2024 08:37:01 -0600 Subject: [PATCH 004/137] Fix emulatorjs exit event --- data/src/GameManager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/src/GameManager.js b/data/src/GameManager.js index 40da2195..8fcd5cee 100644 --- a/data/src/GameManager.js +++ b/data/src/GameManager.js @@ -46,7 +46,11 @@ class EJS_GameManager { this.toggleMainLoop(0); this.functions.saveSaveFiles(); setTimeout(() => { - try {window.abort()} catch(e){}; + try { + this.Module.abort(); + } catch(e) { + console.warn(e); + }; }, 1000); }) } From b865862bdb2635ee3f280ab3cfe5d2e6c5b275f5 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Tue, 10 Dec 2024 08:39:36 -0600 Subject: [PATCH 005/137] Add beta to version number --- data/src/emulator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index da8d989c..2e6d7f04 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -227,7 +227,7 @@ class EmulatorJS { return parseInt(rv.join("")); } constructor(element, config) { - this.ejs_version = "4.1.1"; + this.ejs_version = "4.2.0-beta"; this.extensions = []; this.initControlVars(); this.debug = (window.EJS_DEBUG_XX === true); From d3fd718a9319ea148937474a3e39990df04296b8 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Tue, 10 Dec 2024 09:16:58 -0600 Subject: [PATCH 006/137] Add ability to ship retroarch.cfg configurable values --- data/src/GameManager.js | 41 ++++++++++++++++++++++++++-------------- data/src/emulator.js | 42 ++++++++++++++++++++++++++++++++++------- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/data/src/GameManager.js b/data/src/GameManager.js index 8fcd5cee..30730e4d 100644 --- a/data/src/GameManager.js +++ b/data/src/GameManager.js @@ -119,20 +119,33 @@ class EJS_GameManager { } catch(e) {} } getRetroArchCfg() { - return "autosave_interval = 60\n" + - "screenshot_directory = \"/\"\n" + - "block_sram_overwrite = false\n" + - "video_gpu_screenshot = false\n" + - "audio_latency = 64\n" + - "video_top_portrait_viewport = true\n" + - "video_vsync = true\n" + - "video_smooth = false\n" + - "fastforward_ratio = 3.0\n" + - "slowmotion_ratio = 3.0\n" + - (this.EJS.rewindEnabled ? "rewind_enable = true\n" : "") + - (this.EJS.rewindEnabled ? "rewind_granularity = 6\n" : "") + - "savefile_directory = \"/data/saves\"\n" + - "video_rotation = " + this.EJS.videoRotation + "\n"; + let cfg = "autosave_interval = 60\n" + + "screenshot_directory = \"/\"\n" + + "block_sram_overwrite = false\n" + + "video_gpu_screenshot = false\n" + + "audio_latency = 64\n" + + "video_top_portrait_viewport = true\n" + + "video_vsync = true\n" + + "video_smooth = false\n" + + "fastforward_ratio = 3.0\n" + + "slowmotion_ratio = 3.0\n" + + (this.EJS.rewindEnabled ? "rewind_enable = true\n" : "") + + (this.EJS.rewindEnabled ? "rewind_granularity = 6\n" : "") + + "savefile_directory = \"/data/saves\"\n" + + "video_rotation = " + this.EJS.videoRotation + "\n"; + + if (this.EJS.retroarchOpts && Array.isArray(this.EJS.retroarchOpts)) { + this.EJS.retroarchOpts.forEach(option => { + let selected = this.EJS.preGetSetting(option.name); + console.log(selected); + if (!selected) { + selected = option.default; + } + const value = option.isString === false ? selected : '"' + selected + '"'; + cfg += option.name + " = " + value + "\n" + }) + } + return cfg; } initShaders() { if (!this.EJS.config.shaders) return; diff --git a/data/src/emulator.js b/data/src/emulator.js index 2e6d7f04..d99a4703 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -233,8 +233,8 @@ class EmulatorJS { this.debug = (window.EJS_DEBUG_XX === true); if (this.debug || (window.location && ['localhost', '127.0.0.1'].includes(location.hostname))) this.checkForUpdates(); this.netplayEnabled = (window.EJS_DEBUG_XX === true) && (window.EJS_EXPERIMENTAL_NETPLAY === true); - this.settingsLanguage = window.EJS_settingsLanguage || false; this.config = config; + this.config.settingsLanguage = window.EJS_settingsLanguage || false; this.currentPopup = null; this.isFastForward = false; this.isSlowMotion = false; @@ -546,6 +546,7 @@ class EmulatorJS { this.coreName = core.name; this.repository = core.repo; this.defaultCoreOpts = core.options; + this.retroarchOpts = core.retroarchOpts; } else if (k === "license.txt") { this.license = new TextDecoder().decode(data[k]); } @@ -3851,7 +3852,7 @@ class EmulatorJS { try { coreSpecific = JSON.parse(coreSpecific); if (!coreSpecific || !coreSpecific.settings) { - return false; + return null; } return coreSpecific.settings[setting]; } catch (e) { @@ -3861,7 +3862,7 @@ class EmulatorJS { if (this.config.defaultOptions && this.config.defaultOptions[setting]) { return this.config.defaultOptions[setting]; } - return false; + return null; } loadSettings() { if (!window.localStorage || this.config.disableLocalStorage) return; @@ -4459,12 +4460,12 @@ class EmulatorJS { }, 'disabled'); } - const coreOptions = createSettingParent(true, "Core Options", home); let coreOpts; try { coreOpts = this.gameManager.getCoreOptions(); } catch(e){} if (coreOpts) { + const coreOptions = createSettingParent(true, "Core Options", home); coreOpts.split('\n').forEach((line, index) => { let option = line.split('; '); let name = option[0]; @@ -4474,11 +4475,38 @@ class EmulatorJS { if (options.length === 1) return; let availableOptions = {}; for (let i=0; i 1) ? name.split("|")[1] : options[0].replace('(Default) ', ''), coreOptions, true); + (name.split("|").length > 1) ? name.split("|")[1] : options[0].replace('(Default) ', ''), + coreOptions, + true); + }) + } + + /* + this.retroarchOpts = [ + { + title: "Audio Latency", // String + name: "audio_latency", // String - value to be set in retroarch.cfg + // options should ALWAYS be strings here... + options: ["8", "16", "32", "64", "128"], // values + options: {"8": "eight", "16": "sixteen", "32": "thirty-two", "64": "sixty-four", "128": "one hundred-twenty-eight"}, // This also works + default: "128", // Default + isString: false // Surround value with quotes in retroarch.cfg file? + } + ];*/ + + if (this.retroarchOpts && Array.isArray(this.retroarchOpts)) { + const retroarchOptsMenu = createSettingParent(true, "RetroArch Core Options (requires reload)", home); + this.retroarchOpts.forEach(option => { + addToMenu(this.localization(option.title, this.config.settingsLanguage), + option.name, + option.options, + option.default, + retroarchOptsMenu, + true); }) } From ad530da5f5b6b9c21c5a7b6d67b829e641f35469 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Tue, 10 Dec 2024 10:23:28 -0600 Subject: [PATCH 007/137] Do not set this.settings[value] until after the initial load --- data/src/GameManager.js | 2 +- data/src/emulator.js | 29 ++++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/data/src/GameManager.js b/data/src/GameManager.js index 30730e4d..15c046b8 100644 --- a/data/src/GameManager.js +++ b/data/src/GameManager.js @@ -328,7 +328,7 @@ class EJS_GameManager { for (let i=0; i { - this.settings[title] = newValue; + let settings = {}; + this.changeSettingOption = (title, newValue, startup) => { + if (startup !== true) { + this.settings[title] = newValue; + } + settings[title] = newValue; funcs.forEach(e => e(title)); } let allOpts = {}; @@ -4310,10 +4313,10 @@ class EmulatorJS { funcs.push((title) => { if (id !== title) return; for (let j=0; j { - this.settings[id] = opt; + this.changeSettingOption(id, opt); for (let j=0; j Date: Thu, 12 Dec 2024 07:12:04 -0600 Subject: [PATCH 008/137] Fix setting settings when game errored --- data/src/emulator.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 008f8d3f..836f60b7 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -238,6 +238,7 @@ class EmulatorJS { this.currentPopup = null; this.isFastForward = false; this.isSlowMotion = false; + this.failedToStart = false; this.rewindEnabled = this.preGetSetting("rewindEnabled") === 'enabled'; this.touch = false; this.cheats = []; @@ -519,6 +520,7 @@ class EmulatorJS { this.menu.failedToStart(); this.handleResize(); + this.failedToStart = true; } downloadGameCore() { this.textElem.innerText = this.localization("Download Game Core"); @@ -993,7 +995,7 @@ class EmulatorJS { if (typeof window.EJS_Runtime !== "function") { console.warn("EJS_Runtime is not defined!"); this.startGameError(this.localization("Failed to start game")); - return; + throw new Error("EJS_Runtime is not defined!"); } window.EJS_Runtime({ noInitialRun: true, @@ -3833,7 +3835,8 @@ class EmulatorJS { }; } saveSettings() { - if (!window.localStorage || this.config.disableLocalStorage || !this.settingsLoaded || !this.started) return; + if (!window.localStorage || this.config.disableLocalStorage || !this.settingsLoaded) return; + if (!this.started && !this.failedToStart) return; const coreSpecific = { controlSettings: this.controls, settings: this.settings, From 627fba2f9c3853fb6b0a51765f793f421e49f8c8 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Fri, 13 Dec 2024 10:32:47 -0600 Subject: [PATCH 009/137] Add call to mount file systems before loading game assets --- data/src/GameManager.js | 14 +++++++++----- data/src/emulator.js | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/data/src/GameManager.js b/data/src/GameManager.js index 15c046b8..8ca4fd34 100644 --- a/data/src/GameManager.js +++ b/data/src/GameManager.js @@ -31,14 +31,9 @@ class EJS_GameManager { getFrameNum: this.Module.cwrap('get_current_frame_count', 'number', ['']), setVSync: this.Module.cwrap('set_vsync', 'null', ['number']) } - this.mkdir("/data"); - this.mkdir("/data/saves"); this.writeFile("/home/web_user/retroarch/userdata/retroarch.cfg", this.getRetroArchCfg()); - this.FS.mount(this.FS.filesystems.IDBFS, {autoPersist: true}, '/data/saves'); - //this.FS.syncfs(true, () => {}); - this.writeConfigFile(); this.initShaders(); @@ -47,6 +42,7 @@ class EJS_GameManager { this.functions.saveSaveFiles(); setTimeout(() => { try { + this.FS.unmount('/data/saves'); this.Module.abort(); } catch(e) { console.warn(e); @@ -54,6 +50,14 @@ class EJS_GameManager { }, 1000); }) } + mountFileSystems() { + return new Promise(async resolve => { + this.mkdir("/data"); + this.mkdir("/data/saves"); + this.FS.mount(this.FS.filesystems.IDBFS, {autoPersist: true}, '/data/saves'); + this.FS.syncfs(true, resolve); + }); + } writeConfigFile() { if (!this.EJS.defaultCoreOpts.file || !this.EJS.defaultCoreOpts.settings) { return; diff --git a/data/src/emulator.js b/data/src/emulator.js index 836f60b7..507ee0e4 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -980,6 +980,7 @@ class EmulatorJS { (async () => { this.gameManager = new window.EJS_GameManager(this.Module, this); await this.gameManager.loadExternalFiles(); + await this.gameManager.mountFileSystems(); if (this.getCore() === "ppsspp") { await this.gameManager.loadPpssppAssets(); } From 5d54014ce3df6720bf508562c0e6f3b76da17f53 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 14 Dec 2024 14:05:53 -0600 Subject: [PATCH 010/137] Read state from memory instead of saving to disk --- data/src/GameManager.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/data/src/GameManager.js b/data/src/GameManager.js index 8ca4fd34..02328b56 100644 --- a/data/src/GameManager.js +++ b/data/src/GameManager.js @@ -5,7 +5,7 @@ class EJS_GameManager { this.FS = this.Module.FS; this.functions = { restart: this.Module.cwrap('system_restart', '', []), - saveStateInfo: this.Module.cwrap('save_state_info', 'null', []), + saveStateInfo: this.Module.cwrap('save_state_info', 'string', []), loadState: this.Module.cwrap('load_state', 'number', ['string', 'number']), screenshot: this.Module.cwrap('cmd_take_screenshot', '', []), simulateInput: this.Module.cwrap('simulate_input', 'null', ['number', 'number', 'number']), @@ -172,8 +172,15 @@ class EJS_GameManager { this.functions.restart(); } getState() { - this.functions.saveStateInfo(); - return this.FS.readFile("/current.state"); + const state = this.functions.saveStateInfo().split("|"); + if (state[2] !== "1") { + console.error(state[0]); + return state[0]; + } + const size = parseInt(state[0]); + const dataStart = parseInt(state[1]); + const data = this.Module.HEAPU8.subarray(dataStart, dataStart + size); + return new Uint8Array(data); } loadState(state) { try { From b4a06cef6c565d5efea2e856f5ea7c980c39430c Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 14 Dec 2024 15:08:46 -0600 Subject: [PATCH 011/137] Use this.enableMouseLock --- data/src/emulator.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 507ee0e4..43eb43dd 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -268,6 +268,7 @@ class EmulatorJS { this.bindListeners(); this.config.netplayUrl = this.config.netplayUrl || "https://netplay.emulatorjs.org"; this.fullscreen = false; + this.enableMouseLock = false; this.supportsWebgl2 = !!document.createElement('canvas').getContext('webgl2') && (this.config.forceLegacyCores !== true); this.webgl2Enabled = (() => { let setting = this.preGetSetting("webgl2Enabled"); @@ -548,6 +549,7 @@ class EmulatorJS { this.coreName = core.name; this.repository = core.repo; this.defaultCoreOpts = core.options; + this.enableMouseLock = core.options.supportsMouse; this.retroarchOpts = core.retroarchOpts; } else if (k === "license.txt") { this.license = new TextDecoder().decode(data[k]); @@ -1654,7 +1656,7 @@ class EmulatorJS { this.gameManager.toggleMainLoop(this.paused ? 0 : 1); //I now realize its not easy to pause it while the cursor is locked, just in case I guess - if (this.defaultCoreOpts.supportsMouse) { + if (this.enableMouseLock) { if (this.canvas.exitPointerLock) { this.canvas.exitPointerLock(); } else if (this.canvas.mozExitPointerLock) { @@ -1880,7 +1882,7 @@ class EmulatorJS { this.addEventListener(this.canvas, "click", (e) => { if (e.pointerType === "touch") return; - if (this.defaultCoreOpts.supportsMouse && !this.paused) { + if (this.enableMouseLock && !this.paused) { if (this.canvas.requestPointerLock) { this.canvas.requestPointerLock(); } else if (this.canvas.mozRequestPointerLock) { @@ -4506,7 +4508,7 @@ class EmulatorJS { ];*/ if (this.retroarchOpts && Array.isArray(this.retroarchOpts)) { - const retroarchOptsMenu = createSettingParent(true, "RetroArch Core Options (requires reload)", home); + const retroarchOptsMenu = createSettingParent(true, "RetroArch Options (requires reload)", home); this.retroarchOpts.forEach(option => { addToMenu(this.localization(option.title, this.config.settingsLanguage), option.name, From e13522eacf0a75bfb032fa29f243c61b50111ea1 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien <77750390+ethanaobrien@users.noreply.github.com> Date: Sun, 15 Dec 2024 08:57:59 -0600 Subject: [PATCH 012/137] Add another check on response data --- data/src/emulator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 43eb43dd..44ae2328 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -560,7 +560,7 @@ class EmulatorJS { } const report = "cores/reports/" + this.getCore() + ".json"; this.downloadFile(report, (rep) => { - if (rep === -1 || typeof rep === "string") { + if (rep === -1 || typeof rep === "string" || typeof rep.data === "string") { rep = {}; } else { rep = rep.data; From b3da3270b8290df49df5622b5d244a9d7e470f26 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Tue, 17 Dec 2024 12:56:07 -0600 Subject: [PATCH 013/137] Fix error when loading ppsspp assets --- data/src/GameManager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/src/GameManager.js b/data/src/GameManager.js index 02328b56..8e594f3e 100644 --- a/data/src/GameManager.js +++ b/data/src/GameManager.js @@ -343,7 +343,9 @@ class EJS_GameManager { this.FS.mkdir(cp); } } - this.FS.writeFile(path, data); + if (!path.endsWith("/")) { + this.FS.writeFile(path, data); + } } resolve(); }) From 97f5b4bd7e87fd0864de037bdb2e775926df0d94 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Fri, 20 Dec 2024 10:51:33 -0600 Subject: [PATCH 014/137] Redo getCore function --- data/src/GameManager.js | 2 +- data/src/emulator.js | 149 +++++++++++++++++----------------------- 2 files changed, 63 insertions(+), 88 deletions(-) diff --git a/data/src/GameManager.js b/data/src/GameManager.js index 8e594f3e..2c626e0d 100644 --- a/data/src/GameManager.js +++ b/data/src/GameManager.js @@ -40,9 +40,9 @@ class EJS_GameManager { this.EJS.on("exit", () => { this.toggleMainLoop(0); this.functions.saveSaveFiles(); + this.FS.unmount('/data/saves'); setTimeout(() => { try { - this.FS.unmount('/data/saves'); this.Module.abort(); } catch(e) { console.warn(e); diff --git a/data/src/emulator.js b/data/src/emulator.js index 44ae2328..2c0a3e1e 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -1,93 +1,62 @@ class EmulatorJS { + getCores() { + let rv = { + "atari5200": ["a5200"], + "vb": ["beetle_vb"], + "nds": ["melonds", "desmume", "desmume2015"], + "arcade": ["fbneo", "fbalpha2012_cps1", "fbalpha2012_cps2"], + "nes": ["fceumm", "nestopia"], + "gb": ["gambatte"], + "coleco": ["gearcoleco"], + "sega": ["genesis_plus_gx", "picodrive"], + "segaMS": ["smsplus"], + "segaMD": ["genesis_plus_gx"], + "segaGG": ["genesis_plus_gx"], + "segaCD": ["genesis_plus_gx"], + "sega32x": ["picodrive"], + "lynx": ["handy"], + "mame": ["mame2003_plus", "mame2003"], + "ngp": ["mednafen_ngp"], + "pce": ["mednafen_pce"], + "pcfx": ["mednafen_pcfx"], + "psx": ["pcsx_rearmed", "mednafen_psx_hw"], + "ws": ["mednafen_wswan"], + "gba": ["mgba"], + "n64": ["mupen64plus_next", "parallel_n64"], + "3do": ["opera"], + "psp": ["ppsspp"], + "atari7800": ["prosystem"], + "snes": ["snes9x"], + "atari2600": ["stella2014"], + "jaguar": ["virtualjaguar"], + "segaSaturn": ["yabause"], + "amiga": ["puae"], + "c64": ["vice_x64sc"], + "c128": ["vice_x128"], + "pet": ["vice_xpet"], + "plus4": ["vice_xplus4"], + "vic20": ["vice_xvic"] + }; + if (this.isSafari && this.isMobile) { + rv.n64 = rv.n64.reverse(); + } + return rv; + } getCore(generic) { + const cores = this.getCores(); const core = this.config.system; if (generic) { - const options = { - 'a5200': 'atari5200', - 'beetle_vb': 'vb', - 'desmume': 'nds', - 'desmume2015': 'nds', - 'fbalpha2012_cps1': 'arcade', - 'fbalpha2012_cps2': 'arcade', - 'fbneo': 'arcade', - 'fceumm': 'nes', - 'gambatte': 'gb', - 'gearcoleco': 'coleco', - 'genesis_plus_gx': 'sega', - 'handy': 'lynx', - 'mame2003': 'mame', - 'mame2003_plus': 'mame', - 'mednafen_ngp': 'ngp', - 'mednafen_pce': 'pce', - 'mednafen_pcfx': 'pcfx', - 'mednafen_psx_hw': 'psx', - 'mednafen_wswan': 'ws', - 'melonds': 'nds', - 'mgba': 'gba', - 'mupen64plus_next': 'n64', - 'nestopia': 'nes', - 'opera': '3do', - 'parallel_n64': 'n64', - 'pcsx_rearmed': 'psx', - 'picodrive': 'sega', - 'ppsspp': 'psp', - 'prosystem': 'atari7800', - 'snes9x': 'snes', - 'stella2014': 'atari2600', - 'virtualjaguar': 'jaguar', - 'yabause': 'segaSaturn', - 'puae': 'amiga', - 'vice_x64sc': 'c64', - 'vice_x128': 'c128', - 'vice_xpet': 'pet', - 'vice_xplus4': 'plus4', - 'vice_xvic': 'vic20' - } - return options[core] || core; - } - const options = { - 'jaguar': 'virtualjaguar', - 'lynx': 'handy', - 'segaSaturn': 'yabause', - 'segaMS': 'smsplus', - 'segaMD': 'genesis_plus_gx', - 'segaGG': 'genesis_plus_gx', - 'segaCD': 'genesis_plus_gx', - 'sega32x': 'picodrive', - 'atari2600': 'stella2014', - 'atari7800': 'prosystem', - 'nes': 'fceumm', - 'snes': 'snes9x', - 'atari5200': 'a5200', - 'gb': 'gambatte', - 'gba': 'mgba', - 'vb': 'beetle_vb', - 'n64': 'mupen64plus_next', - 'nds': 'melonds', - 'mame': 'mame2003_plus', - 'arcade': 'fbneo', - 'psx': 'pcsx_rearmed', - '3do': 'opera', - 'psp': 'ppsspp', - 'pce': 'mednafen_pce', - 'pcfx': 'mednafen_pcfx', - 'ngp': 'mednafen_ngp', - 'ws': 'mednafen_wswan', - 'coleco': 'gearcoleco', - 'amiga': 'puae', - 'c64': 'vice_x64sc', - 'c128': 'vice_x128', - 'pet': 'vice_xpet', - 'plus4': 'vice_xplus4', - 'vic20': 'vice_xvic' - } - if (this.isSafari && this.isMobile && this.getCore(true) === "n64") { - return "parallel_n64"; - } - if (!this.supportsWebgl2 && this.getCore(true) === "psx") { - return "mednafen_psx_hw"; - } - return options[core] || core; + for (const k in cores) { + if (cores[k].includes(core)) { + return k; + } + } + return core; + } + if (cores[core]) { + return cores[core][0]; + } + return core; } createElement(type) { return document.createElement(type); @@ -1083,8 +1052,9 @@ class EmulatorJS { this.checkStarted(); } } catch(e) { - console.warn("failed to start game", e); + console.warn("Failed to start game", e); this.startGameError(this.localization("Failed to start game")); + this.callEvent("exit"); return; } this.callEvent("start"); @@ -1143,6 +1113,10 @@ class EmulatorJS { if (this.config.noAutoFocus !== true) this.elements.parent.focus(); }, 0); }); + this.addEventListener(window, "beforeunload", (e) => { + if (!this.started) return; + this.callEvent("exit"); + }); this.addEventListener(this.elements.parent, "dragenter", (e) => { e.preventDefault(); if (!this.started) return; @@ -1166,6 +1140,7 @@ class EmulatorJS { counter = 0; this.elements.statePopupPanel.parentElement.style.display = "none"; }); + this.addEventListener(this.elements.parent, "drop", (e) => { e.preventDefault(); if (!this.started) return; From cae8e2fb4b31d4f1e678c9d779ce3f2d95741ec5 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Fri, 20 Dec 2024 11:47:26 -0600 Subject: [PATCH 015/137] Check if core requires webgl2/threads. Refactor downloadFile function to use promise --- data/src/GameManager.js | 14 +- data/src/compression.js | 44 +++-- data/src/emulator.js | 400 +++++++++++++++++++++------------------- 3 files changed, 236 insertions(+), 222 deletions(-) diff --git a/data/src/GameManager.js b/data/src/GameManager.js index 2c626e0d..df980e54 100644 --- a/data/src/GameManager.js +++ b/data/src/GameManager.js @@ -39,7 +39,9 @@ class EJS_GameManager { this.EJS.on("exit", () => { this.toggleMainLoop(0); - this.functions.saveSaveFiles(); + if (!this.EJS.failedToStart) { + this.functions.saveSaveFiles(); + } this.FS.unmount('/data/saves'); setTimeout(() => { try { @@ -74,7 +76,7 @@ class EJS_GameManager { if (this.EJS.config.externalFiles && this.EJS.config.externalFiles.constructor.name === 'Object') { for (const key in this.EJS.config.externalFiles) { await new Promise(done => { - this.EJS.downloadFile(this.EJS.config.externalFiles[key], async (res) => { + this.EJS.downloadFile(this.EJS.config.externalFiles[key], null, true, {responseType: "arraybuffer", method: "GET"}).then(async (res) => { if (res === -1) { if (this.EJS.debug) console.warn("Failed to fetch file from '" + this.EJS.config.externalFiles[key] + "'. Make sure the file exists."); return done(); @@ -100,7 +102,7 @@ class EJS_GameManager { if (this.EJS.debug) console.warn("Failed to write file to '" + path + "'. Make sure there are no conflicting files."); } done(); - }, null, true, {responseType: "arraybuffer", method: "GET"}); + }); }) } } @@ -322,7 +324,7 @@ class EJS_GameManager { } loadPpssppAssets() { return new Promise(resolve => { - this.EJS.downloadFile('cores/ppsspp-assets.zip', (res) => { + this.EJS.downloadFile('cores/ppsspp-assets.zip', null, false, {responseType: "arraybuffer", method: "GET"}).then((res) => { this.EJS.checkCompression(new Uint8Array(res.data), this.EJS.localization("Decompress Game Data")).then((pspassets) => { if (pspassets === -1) { this.EJS.textElem.innerText = this.localization('Network Error'); @@ -330,7 +332,7 @@ class EJS_GameManager { return; } this.mkdir("/PPSSPP"); - + for (const file in pspassets) { const data = pspassets[file]; const path = "/PPSSPP/"+file; @@ -349,7 +351,7 @@ class EJS_GameManager { } resolve(); }) - }, null, false, {responseType: "arraybuffer", method: "GET"}); + }); }) } setVSync(enabled) { diff --git a/data/src/compression.js b/data/src/compression.js index 2b8d7a09..0317ecaa 100644 --- a/data/src/compression.js +++ b/data/src/compression.js @@ -24,7 +24,7 @@ class EJS_COMPRESSION { return this.decompressFile(compressed, data, updateMsg, fileCbFunc); } getWorkerFile(method) { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { let path, obj; if (method === "7z") { path = "compression/extract7z.js"; @@ -36,31 +36,29 @@ class EJS_COMPRESSION { path = "compression/libunrar.js"; obj = "rar"; } - this.EJS.downloadFile(path, (res) => { - if (res === -1) { + const res = await this.EJS.downloadFile(path, null, false, {responseType: "arraybuffer", method: "GET"}); + if (res === -1) { + this.EJS.startGameError(this.EJS.localization('Network Error')); + return; + } + if (method === "rar") { + const res2 = await this.EJS.downloadFile("compression/libunrar.wasm", null, false, {responseType: "arraybuffer", method: "GET"}); + if (res2 === -1) { this.EJS.startGameError(this.EJS.localization('Network Error')); return; } - if (method === "rar") { - this.EJS.downloadFile("compression/libunrar.wasm", (res2) => { - if (res2 === -1) { - this.EJS.startGameError(this.EJS.localization('Network Error')); - return; - } - const path = URL.createObjectURL(new Blob([res2.data], {type: "application/wasm"})); - let data = '\nlet dataToPass = [];\nModule = {\n monitorRunDependencies: function(left) {\n if (left == 0) {\n setTimeout(function() {\n unrar(dataToPass, null);\n }, 100);\n }\n },\n onRuntimeInitialized: function() {\n },\n locateFile: function(file) {\n return \''+path+'\';\n }\n};\n'+res.data+'\nlet unrar = function(data, password) {\n let cb = function(fileName, fileSize, progress) {\n postMessage({"t":4,"current":progress,"total":fileSize, "name": fileName});\n };\n\n let rarContent = readRARContent(data.map(function(d) {\n return {\n name: d.name,\n content: new Uint8Array(d.content)\n }\n }), password, cb)\n let rec = function(entry) {\n if (!entry) return;\n if (entry.type === \'file\') {\n postMessage({"t":2,"file":entry.fullFileName,"size":entry.fileSize,"data":entry.fileContent});\n } else if (entry.type === \'dir\') {\n Object.keys(entry.ls).forEach(function(k) {\n rec(entry.ls[k]);\n })\n } else {\n throw "Unknown type";\n }\n }\n rec(rarContent);\n postMessage({"t":1});\n return rarContent;\n};\nonmessage = function(data) {\n dataToPass.push({name: \'test.rar\', content: data.data});\n};\n '; - const blob = new Blob([data], { - type: 'application/javascript' - }) - resolve(blob); - }, null, false, {responseType: "arraybuffer", method: "GET"}); - } else { - const blob = new Blob([res.data], { - type: 'application/javascript' - }) - resolve(blob); - } - }, null, false, {responseType: "arraybuffer", method: "GET"}); + const path = URL.createObjectURL(new Blob([res2.data], {type: "application/wasm"})); + let data = '\nlet dataToPass = [];\nModule = {\n monitorRunDependencies: function(left) {\n if (left == 0) {\n setTimeout(function() {\n unrar(dataToPass, null);\n }, 100);\n }\n },\n onRuntimeInitialized: function() {\n },\n locateFile: function(file) {\n return \''+path+'\';\n }\n};\n'+res.data+'\nlet unrar = function(data, password) {\n let cb = function(fileName, fileSize, progress) {\n postMessage({"t":4,"current":progress,"total":fileSize, "name": fileName});\n };\n\n let rarContent = readRARContent(data.map(function(d) {\n return {\n name: d.name,\n content: new Uint8Array(d.content)\n }\n }), password, cb)\n let rec = function(entry) {\n if (!entry) return;\n if (entry.type === \'file\') {\n postMessage({"t":2,"file":entry.fullFileName,"size":entry.fileSize,"data":entry.fileContent});\n } else if (entry.type === \'dir\') {\n Object.keys(entry.ls).forEach(function(k) {\n rec(entry.ls[k]);\n })\n } else {\n throw "Unknown type";\n }\n }\n rec(rarContent);\n postMessage({"t":1});\n return rarContent;\n};\nonmessage = function(data) {\n dataToPass.push({name: \'test.rar\', content: data.data});\n};\n '; + const blob = new Blob([data], { + type: 'application/javascript' + }) + resolve(blob); + } else { + const blob = new Blob([res.data], { + type: 'application/javascript' + }) + resolve(blob); + } }) } decompressFile(method, data, updateMsg, fileCbFunc) { diff --git a/data/src/emulator.js b/data/src/emulator.js index 2c0a3e1e..5ddf3c36 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -42,6 +42,14 @@ class EmulatorJS { } return rv; } + requiresThreads(core) { + const requiresThreads = ["ppsspp"]; + return requiresThreads.includes(core); + } + requiresWebGL2(core) { + const requiresWebGL2 = ["ppsspp"]; + return requiresWebGL2.includes(core); + } getCore(generic) { const cores = this.getCores(); const core = this.config.system; @@ -77,30 +85,47 @@ class EmulatorJS { data[i].elem.removeEventListener(data[i].listener, data[i].cb); } } - downloadFile(path, cb, progressCB, notWithPath, opts) { - const data = this.toData(path);//check other data types - if (data) { - data.then((game) => { + downloadFile(path, progressCB, notWithPath, opts) { + return new Promise(async cb => { + const data = this.toData(path);//check other data types + if (data) { + data.then((game) => { + if (opts.method === 'HEAD') { + cb({headers:{}}); + } else { + cb({headers:{}, data:game}); + } + }) + return; + } + const basePath = notWithPath ? '' : this.config.dataPath; + path = basePath + path; + if (!notWithPath && this.config.filePaths && typeof this.config.filePaths[path.split('/').pop()] === "string") { + path = this.config.filePaths[path.split('/').pop()]; + } + let url; + try {url=new URL(path)}catch(e){}; + if (!url || !['http:', 'https:'].includes(url.protocol)) { + //Most commonly blob: urls. Not sure what else it could be if (opts.method === 'HEAD') { cb({headers:{}}); return; - } else { - cb({headers:{}, data:game}); - return; } - }) - return; - } - const basePath = notWithPath ? '' : this.config.dataPath; - path = basePath + path; - if (!notWithPath && this.config.filePaths) { - if (typeof this.config.filePaths[path.split('/').pop()] === "string") { - path = this.config.filePaths[path.split('/').pop()]; + try { + let res = await fetch(path) + if ((opts.type && opts.type.toLowerCase() === 'arraybuffer') || !opts.type) { + res = await res.arrayBuffer(); + } else { + res = await res.text(); + try {res = JSON.parse(res)} catch(e) {} + } + if (path.startsWith('blob:')) URL.revokeObjectURL(path); + cb({data: res, headers: {}}); + } catch(e) { + cb(-1); + } + return; } - } - let url; - try {url=new URL(path)}catch(e){}; - if ((url && ['http:', 'https:'].includes(url.protocol)) || !url) { const xhr = new XMLHttpRequest(); if (progressCB instanceof Function) { xhr.addEventListener('progress', (e) => { @@ -128,32 +153,7 @@ class EmulatorJS { xhr.onerror = () => cb(-1); xhr.open(opts.method, path, true); xhr.send(); - } else { - (async () => { - //Most commonly blob: urls. Not sure what else it could be - if (opts.method === 'HEAD') { - cb({headers:{}}); - return; - } - let res; - try { - res = await fetch(path); - if ((opts.type && opts.type.toLowerCase() === 'arraybuffer') || !opts.type) { - res = await res.arrayBuffer(); - } else { - res = await res.text(); - try {res = JSON.parse(res)} catch(e) {} - } - } catch(e) { - cb(-1); - } - if (path.startsWith('blob:')) URL.revokeObjectURL(path); - cb({ - data: res, - headers: {} - }); - })(); - } + }) } toData(data, rv) { if (!(data instanceof ArrayBuffer) && !(data instanceof Uint8Array) && !(data instanceof Blob)) return null; @@ -494,7 +494,16 @@ class EmulatorJS { } downloadGameCore() { this.textElem.innerText = this.localization("Download Game Core"); - if (this.config.threads && ((typeof window.SharedArrayBuffer) !== "function")) { + if (!this.config.threads && this.requiresThreads(this.getCore())) { + this.startGameError(this.localization('Error for site owner')+"\n"+this.localization("Check console")); + console.warn("This core requires threads, but EJS_threads is not set!"); + return; + } + if (!this.supportsWebgl2 && this.requiresWebGL2(this.getCore())) { + this.startGameError(this.localization("Outdated graphics driver")); + return; + } + if (this.config.threads && typeof window.SharedArrayBuffer !== "function") { this.startGameError(this.localization('Error for site owner')+"\n"+this.localization("Check console")); console.warn("Threads is set to true, but the SharedArrayBuffer function is not exposed. Threads requires 2 headers to be set when sending you html page. See https://stackoverflow.com/a/68630724"); return; @@ -528,7 +537,7 @@ class EmulatorJS { }); } const report = "cores/reports/" + this.getCore() + ".json"; - this.downloadFile(report, (rep) => { + this.downloadFile(report, null, false, {responseType: "text", method: "GET"}).then(async rep => { if (rep === -1 || typeof rep === "string" || typeof rep.data === "string") { rep = {}; } else { @@ -542,45 +551,38 @@ class EmulatorJS { } let legacy = (this.supportsWebgl2 && this.webgl2Enabled ? "" : "-legacy"); let filename = this.getCore()+(this.config.threads ? "-thread" : "")+legacy+"-wasm.data"; - this.storage.core.get(filename).then((result) => { - if (result && result.version === rep.buildStart && !this.debug) { + if (!this.debug) { + const result = await this.storage.core.get(filename); + if (result && result.version === rep.buildStart) { gotCore(result.data); return; } - const corePath = 'cores/'+filename; - this.downloadFile(corePath, (res) => { - if (res === -1) { - console.log("File not found, attemping to fetch from emulatorjs cdn"); - this.downloadFile("https://cdn.emulatorjs.org/stable/data/"+corePath, (res) => { - if (res === -1) { - if (!this.supportsWebgl2) { - this.startGameError(this.localization('Outdated graphics driver')); - } else { - this.startGameError(this.localization('Network Error')); - } - return; - } - console.warn("File was not found locally, but was found on the emulatorjs cdn.\nIt is recommended to download the stable release from here: https://cdn.emulatorjs.org/releases/"); - gotCore(res.data); - this.storage.core.put(filename, { - version: rep.buildStart, - data: res.data - }); - }, (progress) => { - this.textElem.innerText = this.localization("Download Game Core") + progress; - }, true, {responseType: "arraybuffer", method: "GET"}) - return; - } - gotCore(res.data); - this.storage.core.put(filename, { - version: rep.buildStart, - data: res.data - }); - }, (progress) => { + } + const corePath = 'cores/'+filename; + let res = await this.downloadFile(corePath, (progress) => { + this.textElem.innerText = this.localization("Download Game Core") + progress; + }, false, {responseType: "arraybuffer", method: "GET"}); + if (res === -1) { + console.log("File not found, attemping to fetch from emulatorjs cdn"); + res = await this.downloadFile("https://cdn.emulatorjs.org/stable/data/"+corePath, (progress) => { this.textElem.innerText = this.localization("Download Game Core") + progress; - }, false, {responseType: "arraybuffer", method: "GET"}); - }) - }, null, false, {responseType: "text", method: "GET"}); + }, true, {responseType: "arraybuffer", method: "GET"}); + if (res === -1) { + if (!this.supportsWebgl2) { + this.startGameError(this.localization('Outdated graphics driver')); + } else { + this.startGameError(this.localization('Network Error')); + } + return; + } + console.warn("File was not found locally, but was found on the emulatorjs cdn.\nIt is recommended to download the stable release from here: https://cdn.emulatorjs.org/releases/"); + } + gotCore(res.data); + this.storage.core.put(filename, { + version: rep.buildStart, + data: res.data + }); + }); } initGameCore(js, wasm, thread) { let script = this.createElement("script"); @@ -629,7 +631,9 @@ class EmulatorJS { } this.textElem.innerText = this.localization("Download Game State"); - this.downloadFile(this.config.loadState, (res) => { + this.downloadFile(this.config.loadState, (progress) => { + this.textElem.innerText = this.localization("Download Game State") + progress; + }, true, {responseType: "arraybuffer", method: "GET"}).then((res) => { if (res === -1) { this.startGameError(this.localization('Network Error')); return; @@ -640,9 +644,7 @@ class EmulatorJS { }, 10); }) resolve(); - }, (progress) => { - this.textElem.innerText = this.localization("Download Game State") + progress; - }, true, {responseType: "arraybuffer", method: "GET"}); + }); }) } downloadGamePatch() { @@ -665,36 +667,41 @@ class EmulatorJS { resolve(); }) } - - this.downloadFile(this.config.gamePatchUrl, (res) => { - this.storage.rom.get(this.config.gamePatchUrl.split("/").pop()).then((result) => { - if (result && result['content-length'] === res.headers['content-length'] && !this.debug) { + const downloadFile = async () => { + const res = await this.downloadFile(this.config.gamePatchUrl, (progress) => { + this.textElem.innerText = this.localization("Download Game Patch") + progress; + }, true, {responseType: "arraybuffer", method: "GET"}); + if (res === -1) { + this.startGameError(this.localization('Network Error')); + return; + } + if (this.config.gamePatchUrl instanceof File) { + this.config.gamePatchUrl = this.config.gamePatchUrl.name; + } else if (this.toData(this.config.gamePatchUrl, true)) { + this.config.gamePatchUrl = "game"; + } + gotData(res.data); + const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824; + if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gamePatchUrl !== "game") { + this.storage.rom.put(this.config.gamePatchUrl.split("/").pop(), { + "content-length": res.headers['content-length'], + data: res.data + }) + } + + } + if (!this.debug) { + this.downloadFile(this.config.gamePatchUrl, null, true, {method: "HEAD"}).then(async (res) => { + const result = await this.storage.rom.get(this.config.gamePatchUrl.split("/").pop()) + if (result && result['content-length'] === res.headers['content-length']) { gotData(result.data); return; } - this.downloadFile(this.config.gamePatchUrl, (res) => { - if (res === -1) { - this.startGameError(this.localization('Network Error')); - return; - } - if (this.config.gamePatchUrl instanceof File) { - this.config.gamePatchUrl = this.config.gamePatchUrl.name; - } else if (this.toData(this.config.gamePatchUrl, true)) { - this.config.gamePatchUrl = "game"; - } - gotData(res.data); - const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824; - if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gamePatchUrl !== "game") { - this.storage.rom.put(this.config.gamePatchUrl.split("/").pop(), { - "content-length": res.headers['content-length'], - data: res.data - }) - } - }, (progress) => { - this.textElem.innerText = this.localization("Download Game Patch") + progress; - }, true, {responseType: "arraybuffer", method: "GET"}); + downloadFile(); }) - }, null, true, {method: "HEAD"}) + } else { + downloadFile(); + } }) } downloadGameParent() { @@ -717,37 +724,42 @@ class EmulatorJS { resolve(); }) } + const downloadFile = async () => { + const res = await this.downloadFile(this.config.gameParentUrl, (progress) => { + this.textElem.innerText = this.localization("Download Game Parent") + progress; + }, true, {responseType: "arraybuffer", method: "GET"}); + if (res === -1) { + this.startGameError(this.localization('Network Error')); + return; + } + if (this.config.gameParentUrl instanceof File) { + this.config.gameParentUrl = this.config.gameParentUrl.name; + } else if (this.toData(this.config.gameParentUrl, true)) { + this.config.gameParentUrl = "game"; + } + gotData(res.data); + const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824; + if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gameParentUrl !== "game") { + this.storage.rom.put(this.config.gameParentUrl.split("/").pop(), { + "content-length": res.headers['content-length'], + data: res.data + }) + } + } - this.downloadFile(this.config.gameParentUrl, (res) => { - this.storage.rom.get(this.config.gameParentUrl.split("/").pop()).then((result) => { - if (result && result['content-length'] === res.headers['content-length'] && !this.debug) { + if (!this.debug) { + this.downloadFile(this.config.gameParentUrl, null, true, {method: "HEAD"}).then(async res => { + const result = await this.storage.rom.get(this.config.gameParentUrl.split("/").pop()) + if (result && result['content-length'] === res.headers['content-length']) { gotData(result.data); return; } - this.downloadFile(this.config.gameParentUrl, (res) => { - if (res === -1) { - this.startGameError(this.localization('Network Error')); - return; - } - if (this.config.gameParentUrl instanceof File) { - this.config.gameParentUrl = this.config.gameParentUrl.name; - } else if (this.toData(this.config.gameParentUrl, true)) { - this.config.gameParentUrl = "game"; - } - gotData(res.data); - const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824; - if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gameParentUrl !== "game") { - this.storage.rom.put(this.config.gameParentUrl.split("/").pop(), { - "content-length": res.headers['content-length'], - data: res.data - }) - } - }, (progress) => { - this.textElem.innerText = this.localization("Download Game Parent") + progress; - }, true, {responseType: "arraybuffer", method: "GET"}); + downloadFile(); }) - }, null, true, {method: "HEAD"}) - }) + } else { + downloadFile(); + } + }); } downloadBios() { return new Promise((resolve, reject) => { @@ -774,40 +786,40 @@ class EmulatorJS { resolve(); }) } - - this.downloadFile(this.config.biosUrl, (res) => { + const downloadFile = async () => { + const res = await this.downloadFile(this.config.biosUrl, (progress) => { + this.textElem.innerText = this.localization("Download Game BIOS") + progress; + }, true, {responseType: "arraybuffer", method: "GET"}); if (res === -1) { this.startGameError(this.localization('Network Error')); return; } - this.storage.bios.get(this.config.biosUrl.split("/").pop()).then((result) => { - if (result && result['content-length'] === res.headers['content-length'] && !this.debug) { + if (this.config.biosUrl instanceof File) { + this.config.biosUrl = this.config.biosUrl.name; + } else if (this.toData(this.config.biosUrl, true)) { + this.config.biosUrl = "game"; + } + gotBios(res.data); + if (this.saveInBrowserSupported() && this.config.biosUrl !== "game") { + this.storage.bios.put(this.config.biosUrl.split("/").pop(), { + "content-length": res.headers['content-length'], + data: res.data + }) + } + } + if (!this.debug) { + this.downloadFile(this.config.biosUrl, null, true, {method: "HEAD"}).then(async (res) => { + const result = await this.storage.bios.get(this.config.biosUrl.split("/").pop()); + if (result && result['content-length'] === res.headers['content-length']) { gotBios(result.data); return; } - this.downloadFile(this.config.biosUrl, (res) => { - if (res === -1) { - this.startGameError(this.localization('Network Error')); - return; - } - if (this.config.biosUrl instanceof File) { - this.config.biosUrl = this.config.biosUrl.name; - } else if (this.toData(this.config.biosUrl, true)) { - this.config.biosUrl = "game"; - } - gotBios(res.data); - if (this.saveInBrowserSupported() && this.config.biosUrl !== "game") { - this.storage.bios.put(this.config.biosUrl.split("/").pop(), { - "content-length": res.headers['content-length'], - data: res.data - }) - } - }, (progress) => { - this.textElem.innerText = this.localization("Download Game BIOS") + progress; - }, true, {responseType: "arraybuffer", method: "GET"}); + downloadFile(); }) - }, null, true, {method: "HEAD"}) - }) + } else { + downloadFile(); + } + }); } downloadRom() { const supportsExt = (ext) => { @@ -910,41 +922,42 @@ class EmulatorJS { resolve(); }); } - - this.downloadFile(this.config.gameUrl, (res) => { + const downloadFile = async () => { + const res = await this.downloadFile(this.config.gameUrl, (progress) => { + this.textElem.innerText = this.localization("Download Game Data") + progress; + }, true, {responseType: "arraybuffer", method: "GET"}); if (res === -1) { this.startGameError(this.localization('Network Error')); return; } - const name = (typeof this.config.gameUrl === "string") ? this.config.gameUrl.split('/').pop() : "game"; - this.storage.rom.get(name).then((result) => { - if (result && result['content-length'] === res.headers['content-length'] && !this.debug && name !== "game") { + if (this.config.gameUrl instanceof File) { + this.config.gameUrl = this.config.gameUrl.name; + } else if (this.toData(this.config.gameUrl, true)) { + this.config.gameUrl = "game"; + } + gotGameData(res.data); + const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824; + if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gameUrl !== "game") { + this.storage.rom.put(this.config.gameUrl.split("/").pop(), { + "content-length": res.headers['content-length'], + data: res.data + }) + } + } + + if (!this.debug) { + this.downloadFile(this.config.gameUrl, null, true, {method: "HEAD"}).then(async (res) => { + const name = (typeof this.config.gameUrl === "string") ? this.config.gameUrl.split('/').pop() : "game"; + const result = await this.storage.rom.get(name); + if (result && result['content-length'] === res.headers['content-length'] && name !== "game") { gotGameData(result.data); return; } - this.downloadFile(this.config.gameUrl, (res) => { - if (res === -1) { - this.startGameError(this.localization('Network Error')); - return; - } - if (this.config.gameUrl instanceof File) { - this.config.gameUrl = this.config.gameUrl.name; - } else if (this.toData(this.config.gameUrl, true)) { - this.config.gameUrl = "game"; - } - gotGameData(res.data); - const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824; - if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gameUrl !== "game") { - this.storage.rom.put(this.config.gameUrl.split("/").pop(), { - "content-length": res.headers['content-length'], - data: res.data - }) - } - }, (progress) => { - this.textElem.innerText = this.localization("Download Game Data") + progress; - }, true, {responseType: "arraybuffer", method: "GET"}); + downloadFile(); }) - }, null, true, {method: "HEAD"}) + } else { + downloadFile(); + } }) } downloadFiles() { @@ -984,9 +997,7 @@ class EmulatorJS { printErr: (msg) => { if (this.debug) { console.log(msg); - } - }, totalDependencies: 0, monitorRunDependencies: () => {}, @@ -1001,6 +1012,9 @@ class EmulatorJS { }).then(module => { this.Module = module; this.downloadFiles(); + }).catch(e => { + console.warn(e); + this.startGameError(this.localization("Failed to start game")); }); } startGame() { From 6fc50e2488d4b2835a4172b2a77b64fa225daaee Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 21 Dec 2024 10:17:37 -0600 Subject: [PATCH 016/137] Add ability to switch between cores in settings menu --- data/src/emulator.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 5ddf3c36..15f0ed76 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -61,6 +61,10 @@ class EmulatorJS { } return core; } + const gen = this.getCore(true); + if (cores[gen].includes(this.preGetSetting("retroarch_core", cores[gen][0]))) { + return this.preGetSetting("retroarch_core", cores[gen][0]); + } if (cores[core]) { return cores[core][0]; } @@ -549,6 +553,9 @@ class EmulatorJS { if (this.webgl2Enabled === null) { this.webgl2Enabled = rep.options ? rep.options.defaultWebGL2 : false; } + if (this.requiresWebGL2(this.getCore())) { + this.webgl2Enabled = true; + } let legacy = (this.supportsWebgl2 && this.webgl2Enabled ? "" : "-legacy"); let filename = this.getCore()+(this.config.threads ? "-thread" : "")+legacy+"-wasm.data"; if (!this.debug) { @@ -3841,9 +3848,10 @@ class EmulatorJS { localStorage.setItem("ejs-settings", JSON.stringify(ejs_settings)); localStorage.setItem("ejs-"+this.getCore()+"-settings", JSON.stringify(coreSpecific)); } - preGetSetting(setting) { + preGetSetting(setting, core) { + core = core || this.getCore(); if (window.localStorage && !this.config.disableLocalStorage) { - let coreSpecific = localStorage.getItem("ejs-"+this.getCore()+"-settings"); + let coreSpecific = localStorage.getItem("ejs-"+core+"-settings"); try { coreSpecific = JSON.parse(coreSpecific); if (coreSpecific && coreSpecific.settings) { @@ -4350,6 +4358,11 @@ class EmulatorJS { nested.appendChild(menu); } + const cores = this.getCores(); + const core = cores[this.getCore(true)]; + if (core && core.length > 1) { + addToMenu(this.localization("Core" + " (" + this.localization('Requires restart') + ")"), 'retroarch_core', core, this.getCore(), home); + } const graphicsOptions = createSettingParent(true, "Graphics Settings", home); @@ -4383,7 +4396,7 @@ class EmulatorJS { addToMenu(this.localization('Shaders'), 'shader', shaderMenu, 'disabled', graphicsOptions, true); } - if (this.supportsWebgl2) { + if (this.supportsWebgl2 && !this.requiresWebGL2(this.getCore())) { addToMenu(this.localization('WebGL2') + " (" + this.localization('Requires restart') + ")", 'webgl2Enabled', { 'enabled': this.localization("Enabled"), 'disabled': this.localization("Disabled") @@ -4400,7 +4413,7 @@ class EmulatorJS { 'disabled': this.localization("Disabled") }, "enabled", graphicsOptions, true); - addToMenu(this.localization('Video Rotation (requires reload)'), 'videoRotation', { + addToMenu(this.localization("Video Rotation" + " (" + this.localization('Requires restart') + ")"), 'videoRotation', { '0': "0 deg", '1': "90 deg", '2': "180 deg", @@ -4427,7 +4440,7 @@ class EmulatorJS { "1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "6.0", "6.5", "7.0", "7.5", "8.0", "8.5", "9.0", "9.5", "10.0" ], "3.0", speedOptions, true); - addToMenu(this.localization('Rewind Enabled (requires reload)'), 'rewindEnabled', { + addToMenu(this.localization('Rewind Enabled' + " (" + this.localization('Requires restart') + ")"), 'rewindEnabled', { 'enabled': this.localization("Enabled"), 'disabled': this.localization("Disabled") }, 'disabled', speedOptions, true); @@ -4497,7 +4510,7 @@ class EmulatorJS { ];*/ if (this.retroarchOpts && Array.isArray(this.retroarchOpts)) { - const retroarchOptsMenu = createSettingParent(true, "RetroArch Options (requires reload)", home); + const retroarchOptsMenu = createSettingParent(true, "RetroArch Options" + " (" + this.localization('Requires restart') + ")", home); this.retroarchOpts.forEach(option => { addToMenu(this.localization(option.title, this.config.settingsLanguage), option.name, From 60680e74dac6c30a29beed2a93fb0589d69f5dca Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 21 Dec 2024 10:25:41 -0600 Subject: [PATCH 017/137] Add ability to disable or enable threads if sharedarraybuffer is defined --- data/src/emulator.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 15f0ed76..c3c93a27 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -556,8 +556,18 @@ class EmulatorJS { if (this.requiresWebGL2(this.getCore())) { this.webgl2Enabled = true; } + let threads = false; + if (SharedArrayBuffer instanceof Function) { + const opt = this.preGetSetting("ejs_threads"); + if (opt) { + threads = (opt === "enabled"); + } else { + threads = this.config.threads; + } + } + let legacy = (this.supportsWebgl2 && this.webgl2Enabled ? "" : "-legacy"); - let filename = this.getCore()+(this.config.threads ? "-thread" : "")+legacy+"-wasm.data"; + let filename = this.getCore()+(threads ? "-thread" : "")+legacy+"-wasm.data"; if (!this.debug) { const result = await this.storage.core.get(filename); if (result && result.version === rep.buildStart) { @@ -4363,6 +4373,12 @@ class EmulatorJS { if (core && core.length > 1) { addToMenu(this.localization("Core" + " (" + this.localization('Requires restart') + ")"), 'retroarch_core', core, this.getCore(), home); } + if (SharedArrayBuffer instanceof Function && !this.requiresThreads(this.getCore())) { + addToMenu(this.localization("Threads"), "ejs_threads", { + 'enabled': this.localization("Enabled"), + 'disabled': this.localization("Disabled") + }, this.config.threads ? "enabled" : "disabled", home); + } const graphicsOptions = createSettingParent(true, "Graphics Settings", home); From 65a3aade74401a8333d45f493a7c0c0a824cb724 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 21 Dec 2024 11:11:30 -0600 Subject: [PATCH 018/137] psp control scheme --- data/src/emulator.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/data/src/emulator.js b/data/src/emulator.js index c3c93a27..8a58429c 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -2481,6 +2481,25 @@ class EmulatorJS { {id: 6, label: this.localization('LEFT')}, {id: 7, label: this.localization('RIGHT')}, ]; + } else if ('psp' === this.getControlScheme()) { + buttons = [ + {id: 9, label: this.localization('\u25B3')}, // △ + {id: 1, label: this.localization('\u25A1')}, // □ + {id: 0, label: this.localization('\uFF58')}, // x + {id: 8, label: this.localization('\u25CB')}, // ○ + {id: 2, label: this.localization('SELECT')}, + {id: 3, label: this.localization('START')}, + {id: 4, label: this.localization('UP')}, + {id: 5, label: this.localization('DOWN')}, + {id: 6, label: this.localization('LEFT')}, + {id: 7, label: this.localization('RIGHT')}, + {id: 10, label: this.localization('L')}, + {id: 11, label: this.localization('R')}, + {id: 19, label: this.localization('STICK UP')}, + {id: 18, label: this.localization('STICK DOWN')}, + {id: 17, label: this.localization('STICK LEFT')}, + {id: 16, label: this.localization('STICK RIGHT')}, + ]; } else { buttons = [ {id: 0, label: this.localization('B')}, From 39c9ec9e6c534705a59a69512340792da1b9b889 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 21 Dec 2024 11:30:55 -0600 Subject: [PATCH 019/137] Fix volume slider color in firefox --- data/emulator.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/emulator.css b/data/emulator.css index 68e362a8..6ab06b2c 100644 --- a/data/emulator.css +++ b/data/emulator.css @@ -1198,13 +1198,11 @@ .ejs_volume_parent { padding-right: 15px; } -.ejs_volume_parent input[type='range'] { - display: block; -} .ejs_volume_parent::-webkit-media-controls{ display: none; } -.ejs_volume_parent input[type='range']{ +.ejs_volume_parent input[type='range'] { + display: block; -webkit-appearance:none; appearance: none; border:0; @@ -1213,7 +1211,9 @@ margin:0; padding:0; transition:box-shadow 0.3s ease; - width:100% + width:100%; + background: white; + height:6px; } .ejs_volume_parent input[type='range']::-webkit-slider-runnable-track{ From 35639bb896a3c85d76723cffb0417d659255688c Mon Sep 17 00:00:00 2001 From: Allan Niles Date: Sat, 21 Dec 2024 11:39:08 -0700 Subject: [PATCH 020/137] Fix SharedArrayBuffer check & video rotation without reload (#923) * video_roation without reload * fix SharedArrayBuffer check * remove notCoreOption check --- data/src/GameManager.js | 10 +++++++++- data/src/emulator.js | 9 ++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/data/src/GameManager.js b/data/src/GameManager.js index df980e54..8b6382f4 100644 --- a/data/src/GameManager.js +++ b/data/src/GameManager.js @@ -29,7 +29,8 @@ class EJS_GameManager { toggleSlowMotion: this.Module.cwrap('toggle_slow_motion', 'null', ['number']), setSlowMotionRatio: this.Module.cwrap('set_sm_ratio', 'null', ['number']), getFrameNum: this.Module.cwrap('get_current_frame_count', 'number', ['']), - setVSync: this.Module.cwrap('set_vsync', 'null', ['number']) + setVSync: this.Module.cwrap('set_vsync', 'null', ['number']), + setVideoRoation: this.Module.cwrap('set_video_rotation', 'null', ['number']) } this.writeFile("/home/web_user/retroarch/userdata/retroarch.cfg", this.getRetroArchCfg()); @@ -421,6 +422,13 @@ class EJS_GameManager { getFrameNum() { return this.functions.getFrameNum(); } + setVideoRotation(rotation) { + try { + this.functions.setVideoRoation(rotation); + } catch(e) { + console.warn(e); + } + } } window.EJS_GameManager = EJS_GameManager; diff --git a/data/src/emulator.js b/data/src/emulator.js index 8a58429c..62ab8aff 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -557,7 +557,7 @@ class EmulatorJS { this.webgl2Enabled = true; } let threads = false; - if (SharedArrayBuffer instanceof Function) { + if (typeof window.SharedArrayBuffer === "function") { const opt = this.preGetSetting("ejs_threads"); if (opt) { threads = (opt === "enabled"); @@ -3986,6 +3986,8 @@ class EmulatorJS { } } else if (option === "vsync") { this.gameManager.setVSync(value === "enabled"); + } else if (option === "videoRotation") { + this.gameManager.setVideoRotation(value); } } menuOptionChanged(option, value) { @@ -4392,7 +4394,7 @@ class EmulatorJS { if (core && core.length > 1) { addToMenu(this.localization("Core" + " (" + this.localization('Requires restart') + ")"), 'retroarch_core', core, this.getCore(), home); } - if (SharedArrayBuffer instanceof Function && !this.requiresThreads(this.getCore())) { + if (typeof window.SharedArrayBuffer === "function" && !this.requiresThreads(this.getCore())) { addToMenu(this.localization("Threads"), "ejs_threads", { 'enabled': this.localization("Enabled"), 'disabled': this.localization("Disabled") @@ -4448,7 +4450,7 @@ class EmulatorJS { 'disabled': this.localization("Disabled") }, "enabled", graphicsOptions, true); - addToMenu(this.localization("Video Rotation" + " (" + this.localization('Requires restart') + ")"), 'videoRotation', { + addToMenu(this.localization('Video Rotation'), 'videoRotation', { '0': "0 deg", '1': "90 deg", '2': "180 deg", @@ -4530,6 +4532,7 @@ class EmulatorJS { true); }) } + /* this.retroarchOpts = [ From a4e4aef907fb2a26ae7ced441fa6bdfedef24ee8 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 21 Dec 2024 15:16:59 -0600 Subject: [PATCH 021/137] Fix detection of method to use to fetch url in downloadFile function --- data/src/emulator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 62ab8aff..09493ddb 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -109,7 +109,7 @@ class EmulatorJS { } let url; try {url=new URL(path)}catch(e){}; - if (!url || !['http:', 'https:'].includes(url.protocol)) { + if (url && !['http:', 'https:'].includes(url.protocol)) { //Most commonly blob: urls. Not sure what else it could be if (opts.method === 'HEAD') { cb({headers:{}}); From 8d42d53d4fdf0166f71eaa07529cadf93350b76e Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 21 Dec 2024 15:32:38 -0600 Subject: [PATCH 022/137] Version 4.2.0 changelog --- CHANGES.md | 29 ++++++++++++++++++++++++++++- data/src/emulator.js | 2 +- data/version.json | 2 +- package.json | 2 +- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 392f81b6..bd8d2a84 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,33 @@ # Changes -# 4.1.1 +# 4.2.0 + +In my opinion, this is the most stable release we've had in a while. Many features added in this release were just side affects of fixing up some bugs. + +- Let same_cdi core handle zipped bios file directly (Thanks to [@pastisme](https://github.com/pastisme)) +- Fix audio sliders/mute button (Thanks to [@n-at](https://github.com/n-at)) +- Add ability to rotate video (Thanks to [@allancoding](https://github.com/allancoding)) +- Added persian `fa-AF` language (Thanks to [@iits-reza](https://github.com/iits-reza)) +- Added more catches for a start-game error +- Fixed default webgl2 option +- Organized settings menu, by dividing entries into sub-menus +- Fixed the EmulatorJS exit button +- Added the ability to internally add configurable retroarch.cfg variables +- Fixed default settings being saved to localstorage +- Fixed in browser SRM saves (finally) +- Read save state from WebAssembly Memory +- Fixed an issue when loading PPSSPP assets +- Refactored the internal downloadFile function to be promised based instead of callback based +- Added checks if core requires threads or webgl2 +- Added ability to switch between cores in the settings menu +- Added the ability to enable/disable threads if SharedArrayBuffer is defined +- Added a PSP controller scheme for the control settings menu +- Fixed the volume slider background height in firefox +- Added handling for lightgun devices +- Refactored the EmulatorJS `build-emulatorjs.sh` build script +- Added `ppsspp` core + +# 4.1.1 [View Tree](https://github.com/EmulatorJS/EmulatorJS/tree/4951a28de05e072acbe939f46147645a91664a07) - Fixed 2xScaleHQ and 2xScaleHQ shaders (Thanks to [@n-at](https://github.com/n-at)) - Added Vietnamese (`vi-VN`) (Thanks to [@TimKieu](https://github.com/TimKieu)) - Disable CUE generation for the PUAE core (Thanks to [@michael-j-green](https://github.com/michael-j-green)) diff --git a/data/src/emulator.js b/data/src/emulator.js index 09493ddb..da51227b 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -200,7 +200,7 @@ class EmulatorJS { return parseInt(rv.join("")); } constructor(element, config) { - this.ejs_version = "4.2.0-beta"; + this.ejs_version = "4.2.0"; this.extensions = []; this.initControlVars(); this.debug = (window.EJS_DEBUG_XX === true); diff --git a/data/version.json b/data/version.json index b4867f83..7b1f1e19 100644 --- a/data/version.json +++ b/data/version.json @@ -1 +1 @@ -{ "read_me": "CURRENT_VERSION IS NO LONGER UPDATED. USE VERION", "current_version": 999999, "version": "4.1.1" } +{ "read_me": "CURRENT_VERSION IS NO LONGER UPDATED. USE VERION", "current_version": 999999, "version": "4.2.0" } diff --git a/package.json b/package.json index a02ac677..9dca3f9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@emulatorjs/emulatorjs", - "version": "4.1.1", + "version": "4.2.0", "repository": { "type": "git", "url": "https://github.com/EmulatorJS/EmulatorJS.git" From cac45c93814923037239b477d498489336cfb739 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Sat, 21 Dec 2024 18:05:43 -0500 Subject: [PATCH 023/137] Use pinned ejs_version when fetching cores/version --- data/src/emulator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index da51227b..ddaadf67 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -178,7 +178,7 @@ class EmulatorJS { console.warn("Using EmulatorJS beta. Not checking for updates. This instance may be out of date. Using stable is highly recommended unless you build and ship your own cores."); return; } - fetch('https://cdn.emulatorjs.org/stable/data/version.json').then(response => { + fetch(`https://cdn.emulatorjs.org/${this.ejs_version}/data/version.json`).then(response => { if (response.ok) { response.text().then(body => { let version = JSON.parse(body); @@ -581,7 +581,7 @@ class EmulatorJS { }, false, {responseType: "arraybuffer", method: "GET"}); if (res === -1) { console.log("File not found, attemping to fetch from emulatorjs cdn"); - res = await this.downloadFile("https://cdn.emulatorjs.org/stable/data/"+corePath, (progress) => { + res = await this.downloadFile(`https://cdn.emulatorjs.org/${this.ejs_version}/data/${corePath}`, (progress) => { this.textElem.innerText = this.localization("Download Game Core") + progress; }, true, {responseType: "arraybuffer", method: "GET"}); if (res === -1) { From 6ab560d09fc003005e65b513d7b83c5d045c8d18 Mon Sep 17 00:00:00 2001 From: Allan Niles Date: Mon, 23 Dec 2024 19:32:03 -0700 Subject: [PATCH 024/137] remove legacy button (#927) --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5cfc84e5..5fb41c9f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Self-hosted **Javascript** emulation for various system. [![Button Usage]][Usage]
[![Button Configurator]][Configurator]
[![Button Demo]][Demo]  -[![Button Legacy]][Legacy] [![Button Contributors]][Contributors] @@ -169,6 +168,16 @@ npm start
+## Star History + + + + + + Star History Chart + + + [License]: LICENSE @@ -186,7 +195,6 @@ npm start [Configurator]: https://emulatorjs.org/editor [Contributors]: docs/Contributors.md [Website]: https://emulatorjs.org/ -[Legacy]: https://coldcast.org/games/1/Super-Mario-Bros [Usage]: https://emulatorjs.org/docs/ [Demo]: https://demo.emulatorjs.org/ @@ -242,7 +250,6 @@ npm start [Button Configurator]: https://img.shields.io/badge/Configurator-992cb3?style=for-the-badge [Button Contributors]: https://img.shields.io/badge/Contributors-54b7dd?style=for-the-badge [Button Website]: https://img.shields.io/badge/Website-736e9b?style=for-the-badge -[Button Legacy]: https://img.shields.io/badge/Legacy-ab910b?style=for-the-badge [Button Usage]: https://img.shields.io/badge/Usage-2478b5?style=for-the-badge [Button Demo]: https://img.shields.io/badge/Demo-528116?style=for-the-badge [Button Beta]: https://img.shields.io/badge/Beta-bb044f?style=for-the-badge From ea19b8d7649431d2880d457464c25d9e89a58a26 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien <77750390+ethanaobrien@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:03:08 -0600 Subject: [PATCH 025/137] Emphasize in the console, that fetching the core from the CDN is meant to function as a failsafe --- data/src/emulator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/data/src/emulator.js b/data/src/emulator.js index da51227b..a473fc22 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -593,6 +593,7 @@ class EmulatorJS { return; } console.warn("File was not found locally, but was found on the emulatorjs cdn.\nIt is recommended to download the stable release from here: https://cdn.emulatorjs.org/releases/"); + console.warn("**THIS METHOD IS A FAILSAFE, AND NOT OFFICIALLY SUPPORTED. USE AT YOUR OWN RISK**"); } gotCore(res.data); this.storage.core.put(filename, { From 6ee9f559381ac0e70cd88d9a40466bb4be6514c9 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 28 Dec 2024 19:36:41 -0800 Subject: [PATCH 026/137] Move warning for failsafe message --- data/src/emulator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 392ed374..be2662ef 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -581,6 +581,7 @@ class EmulatorJS { }, false, {responseType: "arraybuffer", method: "GET"}); 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**"); res = await this.downloadFile(`https://cdn.emulatorjs.org/${this.ejs_version}/data/${corePath}`, (progress) => { this.textElem.innerText = this.localization("Download Game Core") + progress; }, true, {responseType: "arraybuffer", method: "GET"}); @@ -593,7 +594,6 @@ class EmulatorJS { return; } console.warn("File was not found locally, but was found on the emulatorjs cdn.\nIt is recommended to download the stable release from here: https://cdn.emulatorjs.org/releases/"); - console.warn("**THIS METHOD IS A FAILSAFE, AND NOT OFFICIALLY SUPPORTED. USE AT YOUR OWN RISK**"); } gotCore(res.data); this.storage.core.put(filename, { From 3acb06a0f0aebbe282f2311287b9e1c2c5671717 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 28 Dec 2024 20:01:12 -0800 Subject: [PATCH 027/137] Combine game patch,parent,and bios download functions --- data/src/emulator.js | 215 +++++++++++-------------------------------- 1 file changed, 55 insertions(+), 160 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index be2662ef..e47c3cf0 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -665,178 +665,73 @@ class EmulatorJS { }); }) } - downloadGamePatch() { - return new Promise((resolve, reject) => { - if ((typeof this.config.gamePatchUrl !== "string" || !this.config.gamePatchUrl.trim()) && !this.toData(this.config.gamePatchUrl, true)) { - resolve(); - return; - } - this.textElem.innerText = this.localization("Download Game Patch"); - const gotData = (data) => { - this.checkCompression(new Uint8Array(data), this.localization("Decompress Game Patch")).then((data) => { - for (const k in data) { - if (k === "!!notCompressedData") { - this.gameManager.FS.writeFile(this.config.gamePatchUrl.split('/').pop().split("#")[0].split("?")[0], data[k]); - break; - } - if (k.endsWith('/')) continue; - this.gameManager.FS.writeFile("/" + k.split('/').pop(), data[k]); + downloadGameFile(assetUrl, type, progressMessage, decompressProgressMessage) { + return new Promise(async (resolve, reject) => { + if ((typeof assetUrl !== "string" || !assetUrl.trim()) && !this.toData(assetUrl, true)) { + return resolve(assetUrl); + } + const gotData = async (input) => { + const data = await this.checkCompression(new Uint8Array(input), decompressProgressMessage); + for (const k in data) { + if (k === "!!notCompressedData") { + this.gameManager.FS.writeFile(assetUrl.split('/').pop().split("#")[0].split("?")[0], data[k]); + break; } - resolve(); - }) - } - const downloadFile = async () => { - const res = await this.downloadFile(this.config.gamePatchUrl, (progress) => { - this.textElem.innerText = this.localization("Download Game Patch") + progress; - }, true, {responseType: "arraybuffer", method: "GET"}); - if (res === -1) { - this.startGameError(this.localization('Network Error')); - return; - } - if (this.config.gamePatchUrl instanceof File) { - this.config.gamePatchUrl = this.config.gamePatchUrl.name; - } else if (this.toData(this.config.gamePatchUrl, true)) { - this.config.gamePatchUrl = "game"; - } - gotData(res.data); - const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824; - if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gamePatchUrl !== "game") { - this.storage.rom.put(this.config.gamePatchUrl.split("/").pop(), { - "content-length": res.headers['content-length'], - data: res.data - }) + if (k.endsWith('/')) continue; + this.gameManager.FS.writeFile("/" + k.split('/').pop(), data[k]); } - } + + this.textElem.innerText = progressMessage; if (!this.debug) { - this.downloadFile(this.config.gamePatchUrl, null, true, {method: "HEAD"}).then(async (res) => { - const result = await this.storage.rom.get(this.config.gamePatchUrl.split("/").pop()) - if (result && result['content-length'] === res.headers['content-length']) { - gotData(result.data); - return; - } - downloadFile(); - }) - } else { - downloadFile(); + const res = await this.downloadFile(assetUrl, null, true, {method: "HEAD"}); + const result = await this.storage.rom.get(assetUrl.split("/").pop()); + if (result && result['content-length'] === res.headers['content-length'] && result.type === type) { + await gotData(result.data); + return resolve(assetUrl); + } } - }) - } - downloadGameParent() { - return new Promise((resolve, reject) => { - if ((typeof this.config.gameParentUrl !== "string" || !this.config.gameParentUrl.trim()) && !this.toData(this.config.gameParentUrl, true)) { - resolve(); + const res = await this.downloadFile(assetUrl, (progress) => { + this.textElem.innerText = progressMessage + progress; + }, true, {responseType: "arraybuffer", method: "GET"}); + if (res === -1) { + this.startGameError(this.localization("Network Error")); + resolve(assetUrl); return; } - this.textElem.innerText = this.localization("Download Game Parent"); - const gotData = (data) => { - this.checkCompression(new Uint8Array(data), this.localization("Decompress Game Parent")).then((data) => { - for (const k in data) { - if (k === "!!notCompressedData") { - this.gameManager.FS.writeFile(this.config.gameParentUrl.split('/').pop().split("#")[0].split("?")[0], data[k]); - break; - } - if (k.endsWith('/')) continue; - this.gameManager.FS.writeFile("/" + k.split('/').pop(), data[k]); - } - resolve(); + if (assetUrl instanceof File) { + assetUrl = assetUrl.name; + } else if (this.toData(assetUrl, true)) { + assetUrl = "game"; + } + await gotData(res.data); + resolve(assetUrl); + const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824; + if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && assetUrl !== "game") { + this.storage.rom.put(assetUrl.split("/").pop(), { + "content-length": res.headers['content-length'], + data: res.data, + type: type }) } - const downloadFile = async () => { - const res = await this.downloadFile(this.config.gameParentUrl, (progress) => { - this.textElem.innerText = this.localization("Download Game Parent") + progress; - }, true, {responseType: "arraybuffer", method: "GET"}); - if (res === -1) { - this.startGameError(this.localization('Network Error')); - return; - } - if (this.config.gameParentUrl instanceof File) { - this.config.gameParentUrl = this.config.gameParentUrl.name; - } else if (this.toData(this.config.gameParentUrl, true)) { - this.config.gameParentUrl = "game"; - } - gotData(res.data); - const limit = (typeof this.config.cacheLimit === "number") ? this.config.cacheLimit : 1073741824; - if (parseFloat(res.headers['content-length']) < limit && this.saveInBrowserSupported() && this.config.gameParentUrl !== "game") { - this.storage.rom.put(this.config.gameParentUrl.split("/").pop(), { - "content-length": res.headers['content-length'], - data: res.data - }) - } - } - - if (!this.debug) { - this.downloadFile(this.config.gameParentUrl, null, true, {method: "HEAD"}).then(async res => { - const result = await this.storage.rom.get(this.config.gameParentUrl.split("/").pop()) - if (result && result['content-length'] === res.headers['content-length']) { - gotData(result.data); - return; - } - downloadFile(); - }) - } else { - downloadFile(); - } + }); + } + 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")); + 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")); + resolve(); }); } downloadBios() { - return new Promise((resolve, reject) => { - if ((typeof this.config.biosUrl !== "string" || !this.config.biosUrl.trim()) && !this.toData(this.config.biosUrl, true)) { - resolve(); - return; - } - this.textElem.innerText = this.localization("Download Game BIOS"); - const gotBios = (data) => { - if (this.getCore() === "same_cdi") { - this.gameManager.FS.writeFile(this.config.biosUrl.split('/').pop().split("#")[0].split("?")[0], new Uint8Array(data)); - resolve(); - return; - } - this.checkCompression(new Uint8Array(data), this.localization("Decompress Game BIOS")).then((data) => { - for (const k in data) { - if (k === "!!notCompressedData") { - this.gameManager.FS.writeFile(this.config.biosUrl.split('/').pop().split("#")[0].split("?")[0], data[k]); - break; - } - if (k.endsWith('/')) continue; - this.gameManager.FS.writeFile("/" + k.split('/').pop(), data[k]); - } - resolve(); - }) - } - const downloadFile = async () => { - const res = await this.downloadFile(this.config.biosUrl, (progress) => { - this.textElem.innerText = this.localization("Download Game BIOS") + progress; - }, true, {responseType: "arraybuffer", method: "GET"}); - if (res === -1) { - this.startGameError(this.localization('Network Error')); - return; - } - if (this.config.biosUrl instanceof File) { - this.config.biosUrl = this.config.biosUrl.name; - } else if (this.toData(this.config.biosUrl, true)) { - this.config.biosUrl = "game"; - } - gotBios(res.data); - if (this.saveInBrowserSupported() && this.config.biosUrl !== "game") { - this.storage.bios.put(this.config.biosUrl.split("/").pop(), { - "content-length": res.headers['content-length'], - data: res.data - }) - } - } - if (!this.debug) { - this.downloadFile(this.config.biosUrl, null, true, {method: "HEAD"}).then(async (res) => { - const result = await this.storage.bios.get(this.config.biosUrl.split("/").pop()); - if (result && result['content-length'] === res.headers['content-length']) { - gotBios(result.data); - return; - } - downloadFile(); - }) - } else { - downloadFile(); - } + 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")); + resolve(); }); } downloadRom() { From be29680d57015482612f3b492b7952455494be5a Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 28 Dec 2024 21:18:11 -0800 Subject: [PATCH 028/137] Always pull from stable for update checks --- data/src/emulator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index e47c3cf0..22842c6a 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -178,7 +178,7 @@ class EmulatorJS { console.warn("Using EmulatorJS beta. Not checking for updates. This instance may be out of date. Using stable is highly recommended unless you build and ship your own cores."); return; } - fetch(`https://cdn.emulatorjs.org/${this.ejs_version}/data/version.json`).then(response => { + fetch("https://cdn.emulatorjs.org/stable/data/version.json").then(response => { if (response.ok) { response.text().then(body => { let version = JSON.parse(body); From cf4b6804905419f63683f35666cac55636d15abe Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sun, 29 Dec 2024 14:08:49 -0800 Subject: [PATCH 029/137] Fix setting/getting core setting --- data/src/emulator.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 22842c6a..4a871f9f 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -62,8 +62,8 @@ class EmulatorJS { return core; } const gen = this.getCore(true); - if (cores[gen].includes(this.preGetSetting("retroarch_core", cores[gen][0]))) { - return this.preGetSetting("retroarch_core", cores[gen][0]); + if (cores[gen].includes(this.getCoreSetting(gen))) { + return this.getCoreSetting(gen); } if (cores[core]) { return cores[core][0]; @@ -3772,11 +3772,29 @@ class EmulatorJS { } localStorage.setItem("ejs-settings", JSON.stringify(ejs_settings)); localStorage.setItem("ejs-"+this.getCore()+"-settings", JSON.stringify(coreSpecific)); + this.saveCoreSettings(); } - preGetSetting(setting, core) { - core = core || this.getCore(); + saveCoreSettings() { + let settings = localStorage.getItem("ejs-core-settings"); + try { + settings = JSON.parse(settings); + } catch(e) { + settings = {}; + } + settings[this.getCore(true)] = this.settings.retroarch_core; + localStorage.setItem("ejs-core-settings", JSON.stringify(settings)); + } + getCoreSetting(core) { + let settings = localStorage.getItem("ejs-core-settings"); + try { + return JSON.parse(settings)[core]; + } catch(e) { + return ""; + } + } + preGetSetting(setting) { if (window.localStorage && !this.config.disableLocalStorage) { - let coreSpecific = localStorage.getItem("ejs-"+core+"-settings"); + let coreSpecific = localStorage.getItem("ejs-"+this.getCore()+"-settings"); try { coreSpecific = JSON.parse(coreSpecific); if (coreSpecific && coreSpecific.settings) { From 4ae4d5c3bffbea677db207027c4aae86e13e9deb Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sun, 29 Dec 2024 14:21:48 -0800 Subject: [PATCH 030/137] Get/save core settings based off of the game filename instead of the core --- data/src/emulator.js | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 4a871f9f..79e4b616 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -62,8 +62,8 @@ class EmulatorJS { return core; } const gen = this.getCore(true); - if (cores[gen].includes(this.getCoreSetting(gen))) { - return this.getCoreSetting(gen); + if (cores[gen].includes(this.preGetSetting("retroarch_core"))) { + return this.preGetSetting("retroarch_core"); } if (cores[core]) { return cores[core][0]; @@ -3771,30 +3771,22 @@ class EmulatorJS { muted: this.muted } localStorage.setItem("ejs-settings", JSON.stringify(ejs_settings)); - localStorage.setItem("ejs-"+this.getCore()+"-settings", JSON.stringify(coreSpecific)); - this.saveCoreSettings(); + localStorage.setItem(this.getLocalStorageKey(), JSON.stringify(coreSpecific)); } - saveCoreSettings() { - let settings = localStorage.getItem("ejs-core-settings"); - try { - settings = JSON.parse(settings); - } catch(e) { - settings = {}; - } - settings[this.getCore(true)] = this.settings.retroarch_core; - localStorage.setItem("ejs-core-settings", JSON.stringify(settings)); - } - getCoreSetting(core) { - let settings = localStorage.getItem("ejs-core-settings"); - try { - return JSON.parse(settings)[core]; - } catch(e) { - return ""; - } + getLocalStorageKey() { + let identifier = (this.config.gameId || 1) + "-" + this.getCore(true); + if (typeof this.config.gameUrl === "string" && !this.config.gameUrl.toLowerCase().startsWith("blob:")) { + identifier += "-" + this.config.gameUrl; + } else if (this.config.gameUrl instanceof File) { + identifier += "-" + this.config.gameUrl.name; + } else if (typeof this.config.gameId !== "number") { + console.warn("gameId (EJS_gameID) is not set. This may result in settings persisting across games."); + } + return "ejs-"+identifier+"-settings"; } preGetSetting(setting) { if (window.localStorage && !this.config.disableLocalStorage) { - let coreSpecific = localStorage.getItem("ejs-"+this.getCore()+"-settings"); + let coreSpecific = localStorage.getItem(this.getLocalStorageKey()); try { coreSpecific = JSON.parse(coreSpecific); if (coreSpecific && coreSpecific.settings) { @@ -3813,7 +3805,7 @@ class EmulatorJS { if (!window.localStorage || this.config.disableLocalStorage) return; this.settingsLoaded = true; let ejs_settings = localStorage.getItem("ejs-settings"); - let coreSpecific = localStorage.getItem("ejs-"+this.getCore()+"-settings"); + let coreSpecific = localStorage.getItem(this.getLocalStorageKey()); if (coreSpecific) { try { coreSpecific = JSON.parse(coreSpecific); From 27374b52d6de9a5ce0bd779fa11f844757ab00f2 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Fri, 3 Jan 2025 21:24:34 -0600 Subject: [PATCH 031/137] Fix down input on gamepad --- data/src/emulator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 79e4b616..c6ff7a26 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -3053,7 +3053,7 @@ class EmulatorJS { this.gameManager.simulateInput(i, 22, 0x7fff * e.value); this.gameManager.simulateInput(i, 23, 0); } else { - this.gameManager.simulateInput(i, 23, 0x7fff * e.value); + this.gameManager.simulateInput(i, 23, -0x7fff * e.value); this.gameManager.simulateInput(i, 22, 0); } } From 07847743af62686581e85e4cc3ada706829da544 Mon Sep 17 00:00:00 2001 From: Allan Niles Date: Sun, 5 Jan 2025 09:41:26 -0700 Subject: [PATCH 032/137] Fix video rotation & add Arcade Core --- data/loader.js | 1 + data/src/emulator.js | 10 +++++++++- index.html | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/data/loader.js b/data/loader.js index b0dd84fc..b1ef78b3 100644 --- a/data/loader.js +++ b/data/loader.js @@ -33,6 +33,7 @@ document.head.appendChild(script); }) } + function loadStyle(file) { return new Promise(function(resolve, reject) { let css = document.createElement('link'); diff --git a/data/src/emulator.js b/data/src/emulator.js index c6ff7a26..21e9af68 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -238,6 +238,7 @@ class EmulatorJS { this.canvas = this.createElement('canvas'); this.canvas.classList.add('ejs_canvas'); this.videoRotation = ([0, 1, 2, 3].includes(this.config.videoRotation)) ? this.config.videoRotation : this.preGetSetting("videoRotation") || 0; + this.videoRotationChanged = false; this.bindListeners(); this.config.netplayUrl = this.config.netplayUrl || "https://netplay.emulatorjs.org"; this.fullscreen = false; @@ -3893,7 +3894,14 @@ class EmulatorJS { } else if (option === "vsync") { this.gameManager.setVSync(value === "enabled"); } else if (option === "videoRotation") { - this.gameManager.setVideoRotation(value); + value = parseInt(value); + if (this.videoRotationChanged === true || value !== 0) { + this.gameManager.setVideoRotation(value); + this.videoRotationChanged = true; + } else if (this.videoRotationChanged === true && value === 0) { + this.gameManager.setVideoRotation(0); + this.videoRotationChanged = true; + } } } menuOptionChanged(option, value) { diff --git a/index.html b/index.html index 62afa032..95eb26e0 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - EmulatorJS Demo + EmulatorJS - diff --git a/data/localization/en-US.json b/data/localization/en-US.json new file mode 100644 index 00000000..726b418b --- /dev/null +++ b/data/localization/en-US.json @@ -0,0 +1,336 @@ +{ + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9", + "Restart": "Restart", + "Pause": "Pause", + "Play": "Play", + "Save State": "Save State", + "Load State": "Load State", + "Control Settings": "Control Settings", + "Cheats": "Cheats", + "Cache Manager": "Cache Manager", + "Export Save File": "Export Save File", + "Import Save File": "Import Save File", + "Netplay": "Netplay", + "Mute": "Mute", + "Unmute": "Unmute", + "Settings": "Settings", + "Enter Fullscreen": "Enter Fullscreen", + "Exit Fullscreen": "Exit Fullscreen", + "Context Menu": "Context Menu", + "Reset": "Reset", + "Clear": "Clear", + "Close": "Close", + "QUICK SAVE STATE": "QUICK SAVE STATE", + "QUICK LOAD STATE": "QUICK LOAD STATE", + "CHANGE STATE SLOT": "CHANGE STATE SLOT", + "FAST FORWARD": "FAST FORWARD", + "Player": "Player", + "Connected Gamepad": "Connected Gamepad", + "Gamepad": "Gamepad", + "Keyboard": "Keyboard", + "Set": "Set", + "Add Cheat": "Add Cheat", + "Note that some cheats require a restart to disable": "Note that some cheats require a restart to disable", + "Create a Room": "Create a Room", + "Rooms": "Rooms", + "Start Game": "Start Game", + "Click to resume Emulator": "Click to resume Emulator", + "Drop save state here to load": "Drop save state here to load", + "Loading...": "Loading...", + "Download Game Core": "Download Game Core", + "Outdated graphics driver": "Outdated graphics driver", + "Decompress Game Core": "Decompress Game Core", + "Download Game Data": "Download Game Data", + "Decompress Game Data": "Decompress Game Data", + "Shaders": "Shaders", + "Disabled": "Disabled", + "2xScaleHQ": "2xScaleHQ", + "4xScaleHQ": "4xScaleHQ", + "CRT easymode": "CRT easymode", + "CRT aperture": "CRT aperture", + "CRT geom": "CRT geom", + "CRT mattias": "CRT mattias", + "FPS": "FPS", + "show": "show", + "hide": "hide", + "Fast Forward Ratio": "Fast Forward Ratio", + "Fast Forward": "Fast Forward", + "Enabled": "Enabled", + "Save State Slot": "Save State Slot", + "Save State Location": "Save State Location", + "Download": "Download", + "Keep in Browser": "Keep in Browser", + "Auto": "Auto", + "NTSC": "NTSC", + "PAL": "PAL", + "Dendy": "Dendy", + "8:7 PAR": "8:7 PAR", + "4:3": "4:3", + "Low": "Low", + "High": "High", + "Very High": "Very High", + "None": "None", + "Player 1": "Player 1", + "Player 2": "Player 2", + "Both": "Both", + "SAVED STATE TO SLOT": "SAVED STATE TO SLOT", + "LOADED STATE FROM SLOT": "LOADED STATE FROM SLOT", + "SET SAVE STATE SLOT TO": "SET SAVE STATE SLOT TO", + "Network Error": "Network Error", + "Submit": "Submit", + "Description": "Description", + "Code": "Code", + "Add Cheat Code": "Add Cheat Code", + "Leave Room": "Leave Room", + "Password": "Password", + "Password (optional)": "Password (optional)", + "Max Players": "Max Players", + "Room Name": "Room Name", + "Join": "Join", + "Player Name": "Player Name", + "Set Player Name": "Set Player Name", + "Left Handed Mode": "Left Handed Mode", + "Virtual Gamepad": "Virtual Gamepad", + "Disk": "Disk", + "Press Keyboard": "Press Keyboard", + "INSERT COIN": "INSERT COIN", + "Remove": "Remove", + "SAVE LOADED FROM BROWSER": "SAVE LOADED FROM BROWSER", + "SAVE SAVED TO BROWSER": "SAVE SAVED TO BROWSER", + "Join the discord": "Join the discord", + "View on GitHub": "View on GitHub", + "Failed to start game": "Failed to start game", + "Download Game BIOS": "Download Game BIOS", + "Decompress Game BIOS": "Decompress Game BIOS", + "Download Game Parent": "Download Game Parent", + "Decompress Game Parent": "Decompress Game Parent", + "Download Game Patch": "Download Game Patch", + "Decompress Game Patch": "Decompress Game Patch", + "Download Game State": "Download Game State", + "Check console": "Check console", + "Error for site owner": "Error for site owner", + "EmulatorJS": "EmulatorJS", + "Clear All": "Clear All", + "Take Screenshot": "Take Screenshot", + "Start screen recording": "Start screen recording", + "Stop screen recording": "Stop screen recording", + "Quick Save": "Quick Save", + "Quick Load": "Quick Load", + "REWIND": "REWIND", + "Rewind Enabled (requires restart)": "Rewind Enabled (requires restart)", + "Rewind Granularity": "Rewind Granularity", + "Slow Motion Ratio": "Slow Motion Ratio", + "Slow Motion": "Slow Motion", + "Home": "Home", + "EmulatorJS License": "EmulatorJS License", + "RetroArch License": "RetroArch License", + "This project is powered by": "This project is powered by", + "View the RetroArch license here": "View the RetroArch license here", + "SLOW MOTION": "SLOW MOTION", + "A": "A", + "B": "B", + "SELECT": "SELECT", + "START": "START", + "UP": "UP", + "DOWN": "DOWN", + "LEFT": "LEFT", + "RIGHT": "RIGHT", + "X": "X", + "Y": "Y", + "L": "L", + "R": "R", + "Z": "Z", + "STICK UP": "STICK UP", + "STICK DOWN": "STICK DOWN", + "STICK LEFT": "STICK LEFT", + "STICK RIGHT": "STICK RIGHT", + "C-PAD UP": "C-PAD UP", + "C-PAD DOWN": "C-PAD DOWN", + "C-PAD LEFT": "C-PAD LEFT", + "C-PAD RIGHT": "C-PAD RIGHT", + "MICROPHONE": "MICROPHONE", + "BUTTON 1 / START": "BUTTON 1 / START", + "BUTTON 2": "BUTTON 2", + "BUTTON": "BUTTON", + "LEFT D-PAD UP": "LEFT D-PAD UP", + "LEFT D-PAD DOWN": "LEFT D-PAD DOWN", + "LEFT D-PAD LEFT": "LEFT D-PAD LEFT", + "LEFT D-PAD RIGHT": "LEFT D-PAD RIGHT", + "RIGHT D-PAD UP": "RIGHT D-PAD UP", + "RIGHT D-PAD DOWN": "RIGHT D-PAD DOWN", + "RIGHT D-PAD LEFT": "RIGHT D-PAD LEFT", + "RIGHT D-PAD RIGHT": "RIGHT D-PAD RIGHT", + "C": "C", + "MODE": "MODE", + "FIRE": "FIRE", + "RESET": "RESET", + "LEFT DIFFICULTY A": "LEFT DIFFICULTY A", + "LEFT DIFFICULTY B": "LEFT DIFFICULTY B", + "RIGHT DIFFICULTY A": "RIGHT DIFFICULTY A", + "RIGHT DIFFICULTY B": "RIGHT DIFFICULTY B", + "COLOR": "COLOR", + "B/W": "B/W", + "PAUSE": "PAUSE", + "OPTION": "OPTION", + "OPTION 1": "OPTION 1", + "OPTION 2": "OPTION 2", + "L2": "L2", + "R2": "R2", + "L3": "L3", + "R3": "R3", + "L STICK UP": "L STICK UP", + "L STICK DOWN": "L STICK DOWN", + "L STICK LEFT": "L STICK LEFT", + "L STICK RIGHT": "L STICK RIGHT", + "R STICK UP": "R STICK UP", + "R STICK DOWN": "R STICK DOWN", + "R STICK LEFT": "R STICK LEFT", + "R STICK RIGHT": "R STICK RIGHT", + "Start": "Start", + "Select": "Select", + "Fast": "Fast", + "Slow": "Slow", + "a": "a", + "b": "b", + "c": "c", + "d": "d", + "e": "e", + "f": "f", + "g": "g", + "h": "h", + "i": "i", + "j": "j", + "k": "k", + "l": "l", + "m": "m", + "n": "n", + "o": "o", + "p": "p", + "q": "q", + "r": "r", + "s": "s", + "t": "t", + "u": "u", + "v": "v", + "w": "w", + "x": "x", + "y": "y", + "z": "z", + "enter": "enter", + "escape": "escape", + "space": "space", + "tab": "tab", + "backspace": "backspace", + "delete": "delete", + "arrowup": "arrowup", + "arrowdown": "arrowdown", + "arrowleft": "arrowleft", + "arrowright": "arrowright", + "f1": "f1", + "f2": "f2", + "f3": "f3", + "f4": "f4", + "f5": "f5", + "f6": "f6", + "f7": "f7", + "f8": "f8", + "f9": "f9", + "f10": "f10", + "f11": "f11", + "f12": "f12", + "shift": "shift", + "control": "control", + "alt": "alt", + "meta": "meta", + "capslock": "capslock", + "insert": "insert", + "home": "home", + "end": "end", + "pageup": "pageup", + "pagedown": "pagedown", + "!": "!", + "@": "@", + "#": "#", + "$": "$", + "%": "%", + "^": "^", + "&": "&", + "*": "*", + "(": "(", + ")": ")", + "-": "-", + "_": "_", + "+": "+", + "=": "=", + "[": "[", + "]": "]", + "{": "{", + "}": "}", + ";": ";", + ":": ":", + "'": "'", + "\"": "\"", + ",": ",", + ".": ".", + "<": "<", + ">": ">", + "/": "/", + "?": "?", + "LEFT_STICK_X": "LEFT_STICK_X", + "LEFT_STICK_Y": "LEFT_STICK_Y", + "RIGHT_STICK_X": "RIGHT_STICK_X", + "RIGHT_STICK_Y": "RIGHT_STICK_Y", + "LEFT_TRIGGER": "LEFT_TRIGGER", + "RIGHT_TRIGGER": "RIGHT_TRIGGER", + "A_BUTTON": "A_BUTTON", + "B_BUTTON": "B_BUTTON", + "X_BUTTON": "X_BUTTON", + "Y_BUTTON": "Y_BUTTON", + "START_BUTTON": "START_BUTTON", + "SELECT_BUTTON": "SELECT_BUTTON", + "L1_BUTTON": "L1_BUTTON", + "R1_BUTTON": "R1_BUTTON", + "L2_BUTTON": "L2_BUTTON", + "R2_BUTTON": "R2_BUTTON", + "LEFT_THUMB_BUTTON": "LEFT_THUMB_BUTTON", + "RIGHT_THUMB_BUTTON": "RIGHT_THUMB_BUTTON", + "DPAD_UP": "DPAD_UP", + "DPAD_DOWN": "DPAD_DOWN", + "DPAD_LEFT": "DPAD_LEFT", + "DPAD_RIGHT": "DPAD_RIGHT", + "Disks": "Disks", + "Exit EmulatorJS": "Exit EmulatorJS", + "BUTTON_1": "BUTTON_1", + "BUTTON_2": "BUTTON_2", + "BUTTON_3": "BUTTON_3", + "BUTTON_4": "BUTTON_4", + "up arrow": "up arrow", + "down arrow": "down arrow", + "left arrow": "left arrow", + "right arrow": "right arrow", + "LEFT_TOP_SHOULDER": "LEFT_TOP_SHOULDER", + "RIGHT_TOP_SHOULDER": "RIGHT_TOP_SHOULDER", + "CRT beam": "CRT beam", + "CRT caligari": "CRT caligari", + "CRT lottes": "CRT lottes", + "CRT yeetron": "CRT yeetron", + "CRT zfast": "CRT zfast", + "SABR": "SABR", + "Bicubic": "Bicubic", + "Mix frames": "Mix frames", + "WebGL2": "WebGL2", + "Requires restart": "Requires restart", + "VSync": "VSync", + "Video Rotation": "Video Rotation", + "Rewind Enabled (Requires restart)": "Rewind Enabled (Requires restart)", + "System Save interval": "System Save interval" +} diff --git a/data/localization/en.json b/data/localization/en.json deleted file mode 100644 index 50333f51..00000000 --- a/data/localization/en.json +++ /dev/null @@ -1,310 +0,0 @@ -{ - "0": "-0", - "1": "-1", - "2": "-2", - "3": "-3", - "4": "-4", - "5": "-5", - "6": "-6", - "7": "-7", - "8": "-8", - "9": "-9", - "Restart": "-Restart", - "Pause": "-Pause", - "Play": "-Play", - "Save State": "-Save State", - "Load State": "-Load State", - "Control Settings": "-Control Settings", - "Cheats": "-Cheats", - "Cache Manager": "-Cache Manager", - "Export Save File": "-Export Save File", - "Import Save File": "-Import Save File", - "Netplay": "-Netplay", - "Mute": "-Mute", - "Unmute": "-Unmute", - "Settings": "-Settings", - "Enter Fullscreen": "-Enter Fullscreen", - "Exit Fullscreen": "-Exit Fullscreen", - "Context Menu": "-Context Menu", - "Reset": "-Reset", - "Clear": "-Clear", - "Close": "-Close", - "QUICK SAVE STATE": "-QUICK SAVE STATE", - "QUICK LOAD STATE": "-QUICK LOAD STATE", - "CHANGE STATE SLOT": "-CHANGE STATE SLOT", - "FAST FORWARD": "-FAST FORWARD", - "Player": "-Player", - "Connected Gamepad": "-Connected Gamepad", - "Gamepad": "-Gamepad", - "Keyboard": "-Keyboard", - "Set": "-Set", - "Add Cheat": "-Add Cheat", - "Note that some cheats require a restart to disable": "-Note that some cheats require a restart to disable", - "Create a Room": "-Create a Room", - "Rooms": "-Rooms", - "Start Game": "-Start Game", - "Click to resume Emulator": "-Click to resume Emulator", - "Drop save state here to load": "-Drop save state here to load", - "Loading...": "-Loading...", - "Download Game Core": "-Download Game Core", - "Outdated graphics driver": "-Outdated graphics driver", - "Decompress Game Core": "-Decompress Game Core", - "Download Game Data": "-Download Game Data", - "Decompress Game Data": "-Decompress Game Data", - "Shaders": "-Shaders", - "Disabled": "-Disabled", - "2xScaleHQ": "-2xScaleHQ", - "4xScaleHQ": "-4xScaleHQ", - "CRT easymode": "-CRT easymode", - "CRT aperture": "-CRT aperture", - "CRT geom": "-CRT geom", - "CRT mattias": "-CRT mattias", - "FPS": "-FPS", - "show": "-show", - "hide": "-hide", - "Fast Forward Ratio": "-Fast Forward Ratio", - "Fast Forward": "-Fast Forward", - "Enabled": "-Enabled", - "Save State Slot": "-Save State Slot", - "Save State Location": "-Save State Location", - "Download": "-Download", - "Keep in Browser": "-Keep in Browser", - "Auto": "-Auto", - "NTSC": "-NTSC", - "PAL": "-PAL", - "Dendy": "-Dendy", - "8:7 PAR": "-8:7 PAR", - "4:3": "-4:3", - "Low": "-Low", - "High": "-High", - "Very High": "-Very High", - "None": "-None", - "Player 1": "-Player 1", - "Player 2": "-Player 2", - "Both": "-Both", - "SAVED STATE TO SLOT": "-SAVED STATE TO SLOT", - "LOADED STATE FROM SLOT": "-LOADED STATE FROM SLOT", - "SET SAVE STATE SLOT TO": "-SET SAVE STATE SLOT TO", - "Network Error": "-Network Error", - "Submit": "-Submit", - "Description": "-Description", - "Code": "-Code", - "Add Cheat Code": "-Add Cheat Code", - "Leave Room": "-Leave Room", - "Password": "-Password", - "Password (optional)": "-Password (optional)", - "Max Players": "-Max Players", - "Room Name": "-Room Name", - "Join": "-Join", - "Player Name": "-Player Name", - "Set Player Name": "-Set Player Name", - "Left Handed Mode": "-Left Handed Mode", - "Virtual Gamepad": "-Virtual Gamepad", - "Disk": "-Disk", - "Press Keyboard": "-Press Keyboard", - "INSERT COIN": "-INSERT COIN", - "Remove": "-Remove", - "SAVE LOADED FROM BROWSER": "-SAVE LOADED FROM BROWSER", - "SAVE SAVED TO BROWSER": "-SAVE SAVED TO BROWSER", - "Join the discord": "-Join the discord", - "View on GitHub": "-View on GitHub", - "Failed to start game": "-Failed to start game", - "Download Game BIOS": "-Download Game BIOS", - "Decompress Game BIOS": "-Decompress Game BIOS", - "Download Game Parent": "-Download Game Parent", - "Decompress Game Parent": "-Decompress Game Parent", - "Download Game Patch": "-Download Game Patch", - "Decompress Game Patch": "-Decompress Game Patch", - "Download Game State": "-Download Game State", - "Check console": "-Check console", - "Error for site owner": "-Error for site owner", - "EmulatorJS": "-EmulatorJS", - "Clear All": "-Clear All", - "Take Screenshot": "-Take Screenshot", - "Start screen recording": "-Start screen recording", - "Stop screen recording": "-Stop screen recording", - "Quick Save": "-Quick Save", - "Quick Load": "-Quick Load", - "REWIND": "-REWIND", - "Rewind Enabled (requires restart)": "-Rewind Enabled (requires restart)", - "Rewind Granularity": "-Rewind Granularity", - "Slow Motion Ratio": "-Slow Motion Ratio", - "Slow Motion": "-Slow Motion", - "Home": "-Home", - "EmulatorJS License": "-EmulatorJS License", - "RetroArch License": "-RetroArch License", - "This project is powered by": "-This project is powered by", - "View the RetroArch license here": "-View the RetroArch license here", - "SLOW MOTION": "-SLOW MOTION", - "A": "-A", - "B": "-B", - "SELECT": "-SELECT", - "START": "-START", - "UP": "-UP", - "DOWN": "-DOWN", - "LEFT": "-LEFT", - "RIGHT": "-RIGHT", - "X": "-X", - "Y": "-Y", - "L": "-L", - "R": "-R", - "Z": "-Z", - "STICK UP": "-STICK UP", - "STICK DOWN": "-STICK DOWN", - "STICK LEFT": "-STICK LEFT", - "STICK RIGHT": "-STICK RIGHT", - "C-PAD UP": "-C-PAD UP", - "C-PAD DOWN": "-C-PAD DOWN", - "C-PAD LEFT": "-C-PAD LEFT", - "C-PAD RIGHT": "-C-PAD RIGHT", - "MICROPHONE": "-MICROPHONE", - "BUTTON 1 / START": "-BUTTON 1 / START", - "BUTTON 2": "-BUTTON 2", - "BUTTON": "-BUTTON", - "LEFT D-PAD UP": "-LEFT D-PAD UP", - "LEFT D-PAD DOWN": "-LEFT D-PAD DOWN", - "LEFT D-PAD LEFT": "-LEFT D-PAD LEFT", - "LEFT D-PAD RIGHT": "-LEFT D-PAD RIGHT", - "RIGHT D-PAD UP": "-RIGHT D-PAD UP", - "RIGHT D-PAD DOWN": "-RIGHT D-PAD DOWN", - "RIGHT D-PAD LEFT": "-RIGHT D-PAD LEFT", - "RIGHT D-PAD RIGHT": "-RIGHT D-PAD RIGHT", - "C": "-C", - "MODE": "-MODE", - "FIRE": "-FIRE", - "RESET": "-RESET", - "LEFT DIFFICULTY A": "-LEFT DIFFICULTY A", - "LEFT DIFFICULTY B": "-LEFT DIFFICULTY B", - "RIGHT DIFFICULTY A": "-RIGHT DIFFICULTY A", - "RIGHT DIFFICULTY B": "-RIGHT DIFFICULTY B", - "COLOR": "-COLOR", - "B/W": "-B/W", - "PAUSE": "-PAUSE", - "OPTION": "-OPTION", - "OPTION 1": "-OPTION 1", - "OPTION 2": "-OPTION 2", - "L2": "-L2", - "R2": "-R2", - "L3": "-L3", - "R3": "-R3", - "L STICK UP": "-L STICK UP", - "L STICK DOWN": "-L STICK DOWN", - "L STICK LEFT": "-L STICK LEFT", - "L STICK RIGHT": "-L STICK RIGHT", - "R STICK UP": "-R STICK UP", - "R STICK DOWN": "-R STICK DOWN", - "R STICK LEFT": "-R STICK LEFT", - "R STICK RIGHT": "-R STICK RIGHT", - "Start": "-Start", - "Select": "-Select", - "Fast": "-Fast", - "Slow": "-Slow", - "a": "-a", - "b": "-b", - "c": "-c", - "d": "-d", - "e": "-e", - "f": "-f", - "g": "-g", - "h": "-h", - "i": "-i", - "j": "-j", - "k": "-k", - "l": "-l", - "m": "-m", - "n": "-n", - "o": "-o", - "p": "-p", - "q": "-q", - "r": "-r", - "s": "-s", - "t": "-t", - "u": "-u", - "v": "-v", - "w": "-w", - "x": "-x", - "y": "-y", - "z": "-z", - "enter": "-enter", - "escape": "-escape", - "space": "-space", - "tab": "-tab", - "backspace": "-backspace", - "delete": "-delete", - "arrowup": "-arrowup", - "arrowdown": "-arrowdown", - "arrowleft": "-arrowleft", - "arrowright": "-arrowright", - "f1": "-f1", - "f2": "-f2", - "f3": "-f3", - "f4": "-f4", - "f5": "-f5", - "f6": "-f6", - "f7": "-f7", - "f8": "-f8", - "f9": "-f9", - "f10": "-f10", - "f11": "-f11", - "f12": "-f12", - "shift": "-shift", - "control": "-control", - "alt": "-alt", - "meta": "-meta", - "capslock": "-capslock", - "insert": "-insert", - "home": "-home", - "end": "-end", - "pageup": "-pageup", - "pagedown": "-pagedown", - "!": "-!", - "@": "-@", - "#": "-#", - "$": "-$", - "%": "-%", - "^": "-^", - "&": "-&", - "*": "-*", - "(": "-(", - ")": "-)", - "-": "--", - "_": "-_", - "+": "-+", - "=": "-=", - "[": "-[", - "]": "-]", - "{": "-{", - "}": "-}", - ";": "-;", - ":": "-:", - "'": "-'", - "\"": "-\"", - ",": "-,", - ".": "-.", - "<": "-<", - ">": "->", - "/": "-/", - "?": "-?", - "LEFT_STICK_X": "-LEFT_STICK_X", - "LEFT_STICK_Y": "-LEFT_STICK_Y", - "RIGHT_STICK_X": "-RIGHT_STICK_X", - "RIGHT_STICK_Y": "-RIGHT_STICK_Y", - "LEFT_TRIGGER": "-LEFT_TRIGGER", - "RIGHT_TRIGGER": "-RIGHT_TRIGGER", - "A_BUTTON": "-A_BUTTON", - "B_BUTTON": "-B_BUTTON", - "X_BUTTON": "-X_BUTTON", - "Y_BUTTON": "-Y_BUTTON", - "START_BUTTON": "-START_BUTTON", - "SELECT_BUTTON": "-SELECT_BUTTON", - "L1_BUTTON": "-L1_BUTTON", - "R1_BUTTON": "-R1_BUTTON", - "L2_BUTTON": "-L2_BUTTON", - "R2_BUTTON": "-R2_BUTTON", - "LEFT_THUMB_BUTTON": "-LEFT_THUMB_BUTTON", - "RIGHT_THUMB_BUTTON": "-RIGHT_THUMB_BUTTON", - "DPAD_UP": "-DPAD_UP", - "DPAD_DOWN": "-DPAD_DOWN", - "DPAD_LEFT": "-DPAD_LEFT", - "DPAD_RIGHT": "-DPAD_RIGHT" -} \ No newline at end of file diff --git a/data/localization/retroarch.json b/data/localization/retroarch.json index 4bd1f9b3..4ea0be5e 100644 --- a/data/localization/retroarch.json +++ b/data/localization/retroarch.json @@ -1,617 +1,617 @@ { - "fceumm region": "-fceumm region", - "fceumm sndquality": "-fceumm sndquality", - "fceumm aspect": "-fceumm aspect", - "fceumm overscan h left": "-fceumm overscan h left", - "fceumm overscan h right": "-fceumm overscan h right", - "fceumm overscan v top": "-fceumm overscan v top", - "fceumm overscan v bottom": "-fceumm overscan v bottom", - "fceumm turbo enable": "-fceumm turbo enable", - "fceumm turbo delay": "-fceumm turbo delay", - "fceumm zapper tolerance": "-fceumm zapper tolerance", - "fceumm mouse sensitivity": "-fceumm mouse sensitivity", - "50%": "-50%", - "60%": "-60%", - "70%": "-70%", - "80%": "-80%", - "90%": "-90%", - "100%": "-100%", - "150%": "-150%", - "200%": "-200%", - "250%": "-250%", - "300%": "-300%", - "350%": "-350%", - "400%": "-400%", - "450%": "-450%", - "500%": "-500%", - "snes9x overclock superfx": "-snes9x overclock superfx", - "snes9x superscope crosshair": "-snes9x superscope crosshair", - "White": "-White", - "White (blend)": "-White (blend)", - "Red": "-Red", - "Red (blend)": "-Red (blend)", - "Orange": "-Orange", - "Orange (blend)": "-Orange (blend)", - "Yellow": "-Yellow", - "Yellow (blend)": "-Yellow (blend)", - "Green": "-Green", - "Green (blend)": "-Green (blend)", - "Cyan": "-Cyan", - "Cyan (blend)": "-Cyan (blend)", - "Sky": "-Sky", - "Sky (blend)": "-Sky (blend)", - "Blue": "-Blue", - "Blue (blend)": "-Blue (blend)", - "Violet": "-Violet", - "Violet (blend)": "-Violet (blend)", - "Pink": "-Pink", - "Pink (blend)": "-Pink (blend)", - "Purple": "-Purple", - "Purple (blend)": "-Purple (blend)", - "Black": "-Black", - "Black (blend)": "-Black (blend)", - "25% Grey": "-25% Grey", - "25% Grey (blend)": "-25% Grey (blend)", - "50% Grey": "-50% Grey", - "50% Grey (blend)": "-50% Grey (blend)", - "75% Grey": "-75% Grey", - "75% Grey (blend)": "-75% Grey (blend)", - "snes9x superscope color": "-snes9x superscope color", - "snes9x justifier1 crosshair": "-snes9x justifier1 crosshair", - "snes9x justifier1 color": "-snes9x justifier1 color", - "snes9x justifier2 crosshair": "-snes9x justifier2 crosshair", - "snes9x justifier2 color": "-snes9x justifier2 color", - "snes9x rifle crosshair": "-snes9x rifle crosshair", - "snes9x rifle color": "-snes9x rifle color", - "1.0x (12.50Mhz)": "-1.0x (12.50Mhz)", - "1.1x (13.75Mhz)": "-1.1x (13.75Mhz)", - "1.2x (15.00Mhz)": "-1.2x (15.00Mhz)", - "1.5x (18.75Mhz)": "-1.5x (18.75Mhz)", - "1.6x (20.00Mhz)": "-1.6x (20.00Mhz)", - "1.8x (22.50Mhz)": "-1.8x (22.50Mhz)", - "2.0x (25.00Mhz)": "-2.0x (25.00Mhz)", - "opera cpu overclock": "-opera cpu overclock", - "0RGB1555": "-0RGB1555", - "RGB565": "-RGB565", - "XRGB8888": "-XRGB8888", - "opera vdlp pixel format": "-opera vdlp pixel format", - "opera nvram version": "-opera nvram version", - "opera active devices": "-opera active devices", - "stella2014 stelladaptor analog sensitivity": "-stella2014 stelladaptor analog sensitivity", - "stella2014 stelladaptor analog center": "-stella2014 stelladaptor analog center", - "handy frameskip threshold": "-handy frameskip threshold", - "320x240": "-320x240", - "640x480": "-640x480", - "960x720": "-960x720", - "1280x960": "-1280x960", - "1440x1080": "-1440x1080", - "1600x1200": "-1600x1200", - "1920x1440": "-1920x1440", - "2240x1680": "-2240x1680", - "2560x1920": "-2560x1920", - "2880x2160": "-2880x2160", - "3200x2400": "-3200x2400", - "3520x2640": "-3520x2640", - "3840x2880": "-3840x2880", - "43screensize": "-43screensize", - "3point": "-3point", - "standard": "-standard", - "BilinearMode": "-BilinearMode", - "MultiSampling": "-MultiSampling", - "FXAA": "-FXAA", - "Software": "-Software", - "FromMem": "-FromMem", - "EnableCopyDepthToRDRAM": "-EnableCopyDepthToRDRAM", - "Stripped": "-Stripped", - "OnePiece": "-OnePiece", - "BackgroundMode": "-BackgroundMode", - "OverscanTop": "-OverscanTop", - "OverscanLeft": "-OverscanLeft", - "OverscanRight": "-OverscanRight", - "OverscanBottom": "-OverscanBottom", - "MaxHiResTxVramLimit": "-MaxHiResTxVramLimit", - "MaxTxCacheSize": "-MaxTxCacheSize", - "Smooth filtering 1": "-Smooth filtering 1", - "Smooth filtering 2": "-Smooth filtering 2", - "Smooth filtering 3": "-Smooth filtering 3", - "Smooth filtering 4": "-Smooth filtering 4", - "Sharp filtering 1": "-Sharp filtering 1", - "Sharp filtering 2": "-Sharp filtering 2", - "txFilterMode": "-txFilterMode", - "As Is": "-As Is", - "X2": "-X2", - "X2SAI": "-X2SAI", - "HQ2X": "-HQ2X", - "HQ2XS": "-HQ2XS", - "LQ2X": "-LQ2X", - "LQ2XS": "-LQ2XS", - "HQ4X": "-HQ4X", - "2xBRZ": "-2xBRZ", - "3xBRZ": "-3xBRZ", - "4xBRZ": "-4xBRZ", - "5xBRZ": "-5xBRZ", - "6xBRZ": "-6xBRZ", - "txEnhancementMode": "-txEnhancementMode", - "Original": "-Original", - "Fullspeed": "-Fullspeed", - "Framerate": "-Framerate", - "virefresh": "-virefresh", - "CountPerOp": "-CountPerOp", - "CountPerOpDenomPot": "-CountPerOpDenomPot", - "deadzone": "-deadzone", - "sensitivity": "-sensitivity", - "C1": "-C1", - "C2": "-C2", - "C3": "-C3", - "C4": "-C4", - "cbutton": "-cbutton", - "none": "-none", - "memory": "-memory", - "rumble": "-rumble", - "transfer": "-transfer", - "pak1": "-pak1", - "pak2": "-pak2", - "pak3": "-pak3", - "pak4": "-pak4", - "Autodetect": "-Autodetect", - "Game Boy": "-Game Boy", - "Super Game Boy": "-Super Game Boy", - "Game Boy Color": "-Game Boy Color", - "Game Boy Advance": "-Game Boy Advance", - "mgba gb model": "-mgba gb model", - "ON": "-ON", - "OFF": "-OFF", - "mgba use bios": "-mgba use bios", - "mgba skip bios": "-mgba skip bios", - "Grayscale": "-Grayscale", - "DMG Green": "-DMG Green", - "GB Pocket": "-GB Pocket", - "GB Light": "-GB Light", - "GBC Brown ↑": "-GBC Brown ↑", - "GBC Red ↑A": "-GBC Red ↑A", - "GBC Dark Brown ↑B": "-GBC Dark Brown ↑B", - "GBC Pale Yellow ↓": "-GBC Pale Yellow ↓", - "GBC Orange ↓A": "-GBC Orange ↓A", - "GBC Yellow ↓B": "-GBC Yellow ↓B", - "GBC Blue ←": "-GBC Blue ←", - "GBC Dark Blue ←A": "-GBC Dark Blue ←A", - "GBC Gray ←B": "-GBC Gray ←B", - "GBC Green →": "-GBC Green →", - "GBC Dark Green →A": "-GBC Dark Green →A", - "GBC Reverse →B": "-GBC Reverse →B", - "SGB 1-A": "-SGB 1-A", - "SGB 1-B": "-SGB 1-B", - "SGB 1-C": "-SGB 1-C", - "SGB 1-D": "-SGB 1-D", - "SGB 1-E": "-SGB 1-E", - "SGB 1-F": "-SGB 1-F", - "SGB 1-G": "-SGB 1-G", - "SGB 1-H": "-SGB 1-H", - "SGB 2-A": "-SGB 2-A", - "SGB 2-B": "-SGB 2-B", - "SGB 2-C": "-SGB 2-C", - "SGB 2-D": "-SGB 2-D", - "SGB 2-E": "-SGB 2-E", - "SGB 2-F": "-SGB 2-F", - "SGB 2-G": "-SGB 2-G", - "SGB 2-H": "-SGB 2-H", - "SGB 3-A": "-SGB 3-A", - "SGB 3-B": "-SGB 3-B", - "SGB 3-C": "-SGB 3-C", - "SGB 3-D": "-SGB 3-D", - "SGB 3-E": "-SGB 3-E", - "SGB 3-F": "-SGB 3-F", - "SGB 3-G": "-SGB 3-G", - "SGB 3-H": "-SGB 3-H", - "SGB 4-A": "-SGB 4-A", - "SGB 4-B": "-SGB 4-B", - "SGB 4-C": "-SGB 4-C", - "SGB 4-D": "-SGB 4-D", - "SGB 4-E": "-SGB 4-E", - "SGB 4-F": "-SGB 4-F", - "SGB 4-G": "-SGB 4-G", - "SGB 4-H": "-SGB 4-H", - "mgba gb colors": "-mgba gb colors", - "mgba sgb borders": "-mgba sgb borders", - "mgba color correction": "-mgba color correction", - "mgba solar sensor level": "-mgba solar sensor level", - "mgba force gbp": "-mgba force gbp", - "Remove Known": "-Remove Known", - "Detect and Remove": "-Detect and Remove", - "Don't Remove": "-Don't Remove", - "mgba idle optimization": "-mgba idle optimization", - "mgba frameskip threshold": "-mgba frameskip threshold", - "mgba frameskip interval": "-mgba frameskip interval", - "GB - DMG": "-GB - DMG", - "GB - Pocket": "-GB - Pocket", - "GB - Light": "-GB - Light", - "GBC - Blue": "-GBC - Blue", - "GBC - Brown": "-GBC - Brown", - "GBC - Dark Blue": "-GBC - Dark Blue", - "GBC - Dark Brown": "-GBC - Dark Brown", - "GBC - Dark Green": "-GBC - Dark Green", - "GBC - Grayscale": "-GBC - Grayscale", - "GBC - Green": "-GBC - Green", - "GBC - Inverted": "-GBC - Inverted", - "GBC - Orange": "-GBC - Orange", - "GBC - Pastel Mix": "-GBC - Pastel Mix", - "GBC - Red": "-GBC - Red", - "GBC - Yellow": "-GBC - Yellow", - "SGB - 1A": "-SGB - 1A", - "SGB - 1B": "-SGB - 1B", - "SGB - 1C": "-SGB - 1C", - "SGB - 1D": "-SGB - 1D", - "SGB - 1E": "-SGB - 1E", - "SGB - 1F": "-SGB - 1F", - "SGB - 1G": "-SGB - 1G", - "SGB - 1H": "-SGB - 1H", - "SGB - 2A": "-SGB - 2A", - "SGB - 2B": "-SGB - 2B", - "SGB - 2C": "-SGB - 2C", - "SGB - 2D": "-SGB - 2D", - "SGB - 2E": "-SGB - 2E", - "SGB - 2F": "-SGB - 2F", - "SGB - 2G": "-SGB - 2G", - "SGB - 2H": "-SGB - 2H", - "SGB - 3A": "-SGB - 3A", - "SGB - 3B": "-SGB - 3B", - "SGB - 3C": "-SGB - 3C", - "SGB - 3D": "-SGB - 3D", - "SGB - 3E": "-SGB - 3E", - "SGB - 3F": "-SGB - 3F", - "SGB - 3G": "-SGB - 3G", - "SGB - 3H": "-SGB - 3H", - "SGB - 4A": "-SGB - 4A", - "SGB - 4B": "-SGB - 4B", - "SGB - 4C": "-SGB - 4C", - "SGB - 4D": "-SGB - 4D", - "SGB - 4E": "-SGB - 4E", - "SGB - 4F": "-SGB - 4F", - "SGB - 4G": "-SGB - 4G", - "SGB - 4H": "-SGB - 4H", - "Special 1": "-Special 1", - "Special 2": "-Special 2", - "Special 3": "-Special 3", - "Special 4 (TI-83 Legacy)": "-Special 4 (TI-83 Legacy)", - "TWB64 - Pack 1": "-TWB64 - Pack 1", - "TWB64 - Pack 2": "-TWB64 - Pack 2", - "PixelShift - Pack 1": "-PixelShift - Pack 1", - "gambatte gb internal palette": "-gambatte gb internal palette", - "TWB64 001 - Aqours Blue": "-TWB64 001 - Aqours Blue", - "TWB64 002 - Anime Expo Ver.": "-TWB64 002 - Anime Expo Ver.", - "TWB64 003 - SpongeBob Yellow": "-TWB64 003 - SpongeBob Yellow", - "TWB64 004 - Patrick Star Pink": "-TWB64 004 - Patrick Star Pink", - "TWB64 005 - Neon Red": "-TWB64 005 - Neon Red", - "TWB64 006 - Neon Blue": "-TWB64 006 - Neon Blue", - "TWB64 007 - Neon Yellow": "-TWB64 007 - Neon Yellow", - "TWB64 008 - Neon Green": "-TWB64 008 - Neon Green", - "TWB64 009 - Neon Pink": "-TWB64 009 - Neon Pink", - "TWB64 010 - Mario Red": "-TWB64 010 - Mario Red", - "TWB64 011 - Nick Orange": "-TWB64 011 - Nick Orange", - "TWB64 012 - Virtual Boy Ver.": "-TWB64 012 - Virtual Boy Ver.", - "TWB64 013 - Golden Wild": "-TWB64 013 - Golden Wild", - "TWB64 014 - Builder Yellow": "-TWB64 014 - Builder Yellow", - "TWB64 015 - Classic Blurple": "-TWB64 015 - Classic Blurple", - "TWB64 016 - 765 Production Ver.": "-TWB64 016 - 765 Production Ver.", - "TWB64 017 - Superball Ivory": "-TWB64 017 - Superball Ivory", - "TWB64 018 - Crunchyroll Orange": "-TWB64 018 - Crunchyroll Orange", - "TWB64 019 - Muse Pink": "-TWB64 019 - Muse Pink", - "TWB64 020 - Nijigasaki Yellow": "-TWB64 020 - Nijigasaki Yellow", - "TWB64 021 - Gamate Ver.": "-TWB64 021 - Gamate Ver.", - "TWB64 022 - Greenscale Ver.": "-TWB64 022 - Greenscale Ver.", - "TWB64 023 - Odyssey Gold": "-TWB64 023 - Odyssey Gold", - "TWB64 024 - Super Saiyan God": "-TWB64 024 - Super Saiyan God", - "TWB64 025 - Super Saiyan Blue": "-TWB64 025 - Super Saiyan Blue", - "TWB64 026 - Bizarre Pink": "-TWB64 026 - Bizarre Pink", - "TWB64 027 - Nintendo Switch Lite Ver.": "-TWB64 027 - Nintendo Switch Lite Ver.", - "TWB64 028 - Game.com Ver.": "-TWB64 028 - Game.com Ver.", - "TWB64 029 - Sanrio Pink": "-TWB64 029 - Sanrio Pink", - "TWB64 030 - BANDAI NAMCO Ver.": "-TWB64 030 - BANDAI NAMCO Ver.", - "TWB64 031 - Cosmo Green": "-TWB64 031 - Cosmo Green", - "TWB64 032 - Wanda Pink": "-TWB64 032 - Wanda Pink", - "TWB64 033 - Link's Awakening DX Ver.": "-TWB64 033 - Link's Awakening DX Ver.", - "TWB64 034 - Travel Wood": "-TWB64 034 - Travel Wood", - "TWB64 035 - Pokemon Ver.": "-TWB64 035 - Pokemon Ver.", - "TWB64 036 - Game Grump Orange": "-TWB64 036 - Game Grump Orange", - "TWB64 037 - Scooby-Doo Mystery Ver.": "-TWB64 037 - Scooby-Doo Mystery Ver.", - "TWB64 038 - Pokemon mini Ver.": "-TWB64 038 - Pokemon mini Ver.", - "TWB64 039 - Supervision Ver.": "-TWB64 039 - Supervision Ver.", - "TWB64 040 - DMG Ver.": "-TWB64 040 - DMG Ver.", - "TWB64 041 - Pocket Ver.": "-TWB64 041 - Pocket Ver.", - "TWB64 042 - Light Ver.": "-TWB64 042 - Light Ver.", - "TWB64 043 - Miraitowa Blue": "-TWB64 043 - Miraitowa Blue", - "TWB64 044 - Someity Pink": "-TWB64 044 - Someity Pink", - "TWB64 045 - Pikachu Yellow": "-TWB64 045 - Pikachu Yellow", - "TWB64 046 - Eevee Brown": "-TWB64 046 - Eevee Brown", - "TWB64 047 - Microvision Ver.": "-TWB64 047 - Microvision Ver.", - "TWB64 048 - TI-83 Ver.": "-TWB64 048 - TI-83 Ver.", - "TWB64 049 - Aegis Cherry": "-TWB64 049 - Aegis Cherry", - "TWB64 050 - Labo Fawn": "-TWB64 050 - Labo Fawn", - "TWB64 051 - MILLION LIVE GOLD!": "-TWB64 051 - MILLION LIVE GOLD!", - "TWB64 052 - Tokyo Midtown Ver.": "-TWB64 052 - Tokyo Midtown Ver.", - "TWB64 053 - VMU Ver.": "-TWB64 053 - VMU Ver.", - "TWB64 054 - Game Master Ver.": "-TWB64 054 - Game Master Ver.", - "TWB64 055 - Android Green": "-TWB64 055 - Android Green", - "TWB64 056 - Ticketmaster Azure": "-TWB64 056 - Ticketmaster Azure", - "TWB64 057 - Google Red": "-TWB64 057 - Google Red", - "TWB64 058 - Google Blue": "-TWB64 058 - Google Blue", - "TWB64 059 - Google Yellow": "-TWB64 059 - Google Yellow", - "TWB64 060 - Google Green": "-TWB64 060 - Google Green", - "TWB64 061 - WonderSwan Ver.": "-TWB64 061 - WonderSwan Ver.", - "TWB64 062 - Neo Geo Pocket Ver.": "-TWB64 062 - Neo Geo Pocket Ver.", - "TWB64 063 - Dew Green": "-TWB64 063 - Dew Green", - "TWB64 064 - Coca-Cola Red": "-TWB64 064 - Coca-Cola Red", - "TWB64 065 - GameKing Ver.": "-TWB64 065 - GameKing Ver.", - "TWB64 066 - Do The Dew Ver.": "-TWB64 066 - Do The Dew Ver.", - "TWB64 067 - Digivice Ver.": "-TWB64 067 - Digivice Ver.", - "TWB64 068 - Bikini Bottom Ver.": "-TWB64 068 - Bikini Bottom Ver.", - "TWB64 069 - Blossom Pink": "-TWB64 069 - Blossom Pink", - "TWB64 070 - Bubbles Blue": "-TWB64 070 - Bubbles Blue", - "TWB64 071 - Buttercup Green": "-TWB64 071 - Buttercup Green", - "TWB64 072 - NASCAR Ver.": "-TWB64 072 - NASCAR Ver.", - "TWB64 073 - Lemon-Lime Green": "-TWB64 073 - Lemon-Lime Green", - "TWB64 074 - Mega Man V Ver.": "-TWB64 074 - Mega Man V Ver.", - "TWB64 075 - Tamagotchi Ver.": "-TWB64 075 - Tamagotchi Ver.", - "TWB64 076 - Phantom Red": "-TWB64 076 - Phantom Red", - "TWB64 077 - Halloween Ver.": "-TWB64 077 - Halloween Ver.", - "TWB64 078 - Christmas Ver.": "-TWB64 078 - Christmas Ver.", - "TWB64 079 - Cardcaptor Pink": "-TWB64 079 - Cardcaptor Pink", - "TWB64 080 - Pretty Guardian Gold": "-TWB64 080 - Pretty Guardian Gold", - "TWB64 081 - Camouflage Ver.": "-TWB64 081 - Camouflage Ver.", - "TWB64 082 - Legendary Super Saiyan": "-TWB64 082 - Legendary Super Saiyan", - "TWB64 083 - Super Saiyan Rose": "-TWB64 083 - Super Saiyan Rose", - "TWB64 084 - Super Saiyan": "-TWB64 084 - Super Saiyan", - "TWB64 085 - Perfected Ultra Instinct": "-TWB64 085 - Perfected Ultra Instinct", - "TWB64 086 - Saint Snow Red": "-TWB64 086 - Saint Snow Red", - "TWB64 087 - Yellow Banana": "-TWB64 087 - Yellow Banana", - "TWB64 088 - Green Banana": "-TWB64 088 - Green Banana", - "TWB64 089 - Super Saiyan 3": "-TWB64 089 - Super Saiyan 3", - "TWB64 090 - Super Saiyan Blue Evolved": "-TWB64 090 - Super Saiyan Blue Evolved", - "TWB64 091 - Pocket Tales Ver.": "-TWB64 091 - Pocket Tales Ver.", - "TWB64 092 - Investigation Yellow": "-TWB64 092 - Investigation Yellow", - "TWB64 093 - S.E.E.S. Blue": "-TWB64 093 - S.E.E.S. Blue", - "TWB64 094 - Game Awards Cyan": "-TWB64 094 - Game Awards Cyan", - "TWB64 095 - Hokage Orange": "-TWB64 095 - Hokage Orange", - "TWB64 096 - Straw Hat Red": "-TWB64 096 - Straw Hat Red", - "TWB64 097 - Sword Art Cyan": "-TWB64 097 - Sword Art Cyan", - "TWB64 098 - Deku Alpha Emerald": "-TWB64 098 - Deku Alpha Emerald", - "TWB64 099 - Blue Stripes Ver.": "-TWB64 099 - Blue Stripes Ver.", - "TWB64 100 - Stone Orange": "-TWB64 100 - Stone Orange", - "gambatte gb palette twb64 1": "-gambatte gb palette twb64 1", - "TWB64 101 - 765PRO Pink": "-TWB64 101 - 765PRO Pink", - "TWB64 102 - CINDERELLA Blue": "-TWB64 102 - CINDERELLA Blue", - "TWB64 103 - MILLION Yellow!": "-TWB64 103 - MILLION Yellow!", - "TWB64 104 - SideM Green": "-TWB64 104 - SideM Green", - "TWB64 105 - SHINY Sky Blue": "-TWB64 105 - SHINY Sky Blue", - "TWB64 106 - Angry Volcano Ver.": "-TWB64 106 - Angry Volcano Ver.", - "TWB64 107 - Yo-kai Pink": "-TWB64 107 - Yo-kai Pink", - "TWB64 108 - Yo-kai Green": "-TWB64 108 - Yo-kai Green", - "TWB64 109 - Yo-kai Blue": "-TWB64 109 - Yo-kai Blue", - "TWB64 110 - Yo-kai Purple": "-TWB64 110 - Yo-kai Purple", - "TWB64 111 - Aquatic Iro": "-TWB64 111 - Aquatic Iro", - "TWB64 112 - Tea Midori": "-TWB64 112 - Tea Midori", - "TWB64 113 - Sakura Pink": "-TWB64 113 - Sakura Pink", - "TWB64 114 - Wisteria Murasaki": "-TWB64 114 - Wisteria Murasaki", - "TWB64 115 - Oni Aka": "-TWB64 115 - Oni Aka", - "TWB64 116 - Golden Kiiro": "-TWB64 116 - Golden Kiiro", - "TWB64 117 - Silver Shiro": "-TWB64 117 - Silver Shiro", - "TWB64 118 - Fruity Orange": "-TWB64 118 - Fruity Orange", - "TWB64 119 - AKB48 Pink": "-TWB64 119 - AKB48 Pink", - "TWB64 120 - Miku Blue": "-TWB64 120 - Miku Blue", - "TWB64 121 - Fairy Tail Red": "-TWB64 121 - Fairy Tail Red", - "TWB64 122 - Survey Corps Brown": "-TWB64 122 - Survey Corps Brown", - "TWB64 123 - Island Green": "-TWB64 123 - Island Green", - "TWB64 124 - Mania Plus Green": "-TWB64 124 - Mania Plus Green", - "TWB64 125 - Ninja Turtle Green": "-TWB64 125 - Ninja Turtle Green", - "TWB64 126 - Slime Blue": "-TWB64 126 - Slime Blue", - "TWB64 127 - Lime Midori": "-TWB64 127 - Lime Midori", - "TWB64 128 - Ghostly Aoi": "-TWB64 128 - Ghostly Aoi", - "TWB64 129 - Retro Bogeda": "-TWB64 129 - Retro Bogeda", - "TWB64 130 - Royal Blue": "-TWB64 130 - Royal Blue", - "TWB64 131 - Neon Purple": "-TWB64 131 - Neon Purple", - "TWB64 132 - Neon Orange": "-TWB64 132 - Neon Orange", - "TWB64 133 - Moonlight Vision": "-TWB64 133 - Moonlight Vision", - "TWB64 134 - Tokyo Red": "-TWB64 134 - Tokyo Red", - "TWB64 135 - Paris Gold": "-TWB64 135 - Paris Gold", - "TWB64 136 - Beijing Blue": "-TWB64 136 - Beijing Blue", - "TWB64 137 - Pac-Man Yellow": "-TWB64 137 - Pac-Man Yellow", - "TWB64 138 - Irish Green": "-TWB64 138 - Irish Green", - "TWB64 139 - Kakarot Orange": "-TWB64 139 - Kakarot Orange", - "TWB64 140 - Dragon Ball Orange": "-TWB64 140 - Dragon Ball Orange", - "TWB64 141 - Christmas Gold": "-TWB64 141 - Christmas Gold", - "TWB64 142 - Pepsi-Cola Blue": "-TWB64 142 - Pepsi-Cola Blue", - "TWB64 143 - Bubblun Green": "-TWB64 143 - Bubblun Green", - "TWB64 144 - Bobblun Blue": "-TWB64 144 - Bobblun Blue", - "TWB64 145 - Baja Blast Storm": "-TWB64 145 - Baja Blast Storm", - "TWB64 146 - Olympic Gold": "-TWB64 146 - Olympic Gold", - "TWB64 147 - Value Orange": "-TWB64 147 - Value Orange", - "TWB64 148 - Liella Purple!": "-TWB64 148 - Liella Purple!", - "TWB64 149 - Olympic Silver": "-TWB64 149 - Olympic Silver", - "TWB64 150 - Olympic Bronze": "-TWB64 150 - Olympic Bronze", - "TWB64 151 - ANA Sky Blue": "-TWB64 151 - ANA Sky Blue", - "TWB64 152 - Nijigasaki Orange": "-TWB64 152 - Nijigasaki Orange", - "TWB64 153 - HoloBlue": "-TWB64 153 - HoloBlue", - "TWB64 154 - Wrestling Red": "-TWB64 154 - Wrestling Red", - "TWB64 155 - Yoshi Egg Green": "-TWB64 155 - Yoshi Egg Green", - "TWB64 156 - Pokedex Red": "-TWB64 156 - Pokedex Red", - "TWB64 157 - Disney Dream Blue": "-TWB64 157 - Disney Dream Blue", - "TWB64 158 - Xbox Green": "-TWB64 158 - Xbox Green", - "TWB64 159 - Sonic Mega Blue": "-TWB64 159 - Sonic Mega Blue", - "TWB64 160 - G4 Orange": "-TWB64 160 - G4 Orange", - "TWB64 161 - Scarlett Green": "-TWB64 161 - Scarlett Green", - "TWB64 162 - Glitchy Blue": "-TWB64 162 - Glitchy Blue", - "TWB64 163 - Classic LCD": "-TWB64 163 - Classic LCD", - "TWB64 164 - 3DS Virtual Console Ver.": "-TWB64 164 - 3DS Virtual Console Ver.", - "TWB64 165 - PocketStation Ver.": "-TWB64 165 - PocketStation Ver.", - "TWB64 166 - Game and Gold": "-TWB64 166 - Game and Gold", - "TWB64 167 - Smurfy Blue": "-TWB64 167 - Smurfy Blue", - "TWB64 168 - Swampy Ogre Green": "-TWB64 168 - Swampy Ogre Green", - "TWB64 169 - Sailor Spinach Green": "-TWB64 169 - Sailor Spinach Green", - "TWB64 170 - Shenron Green": "-TWB64 170 - Shenron Green", - "TWB64 171 - Berserk Blood": "-TWB64 171 - Berserk Blood", - "TWB64 172 - Super Star Pink": "-TWB64 172 - Super Star Pink", - "TWB64 173 - Gamebuino Classic Ver.": "-TWB64 173 - Gamebuino Classic Ver.", - "TWB64 174 - Barbie Pink": "-TWB64 174 - Barbie Pink", - "TWB64 175 - Star Command Green": "-TWB64 175 - Star Command Green", - "TWB64 176 - Nokia 3310 Ver.": "-TWB64 176 - Nokia 3310 Ver.", - "TWB64 177 - Clover Green": "-TWB64 177 - Clover Green", - "TWB64 178 - Crash Orange": "-TWB64 178 - Crash Orange", - "TWB64 179 - Famicom Disk Yellow": "-TWB64 179 - Famicom Disk Yellow", - "TWB64 180 - Team Rocket Red": "-TWB64 180 - Team Rocket Red", - "TWB64 181 - SEIKO Timer Yellow": "-TWB64 181 - SEIKO Timer Yellow", - "TWB64 182 - PINK109": "-TWB64 182 - PINK109", - "TWB64 183 - Doraemon Blue": "-TWB64 183 - Doraemon Blue", - "TWB64 184 - Fury Blue": "-TWB64 184 - Fury Blue", - "TWB64 185 - Rockstar Orange": "-TWB64 185 - Rockstar Orange", - "TWB64 186 - Puyo Puyo Green": "-TWB64 186 - Puyo Puyo Green", - "TWB64 187 - Susan G. Pink": "-TWB64 187 - Susan G. Pink", - "TWB64 188 - Pizza Hut Red": "-TWB64 188 - Pizza Hut Red", - "TWB64 189 - Plumbob Green": "-TWB64 189 - Plumbob Green", - "TWB64 190 - Grand Ivory": "-TWB64 190 - Grand Ivory", - "TWB64 191 - Demon's Gold": "-TWB64 191 - Demon's Gold", - "TWB64 192 - SEGA Tokyo Blue": "-TWB64 192 - SEGA Tokyo Blue", - "TWB64 193 - Champion Blue": "-TWB64 193 - Champion Blue", - "TWB64 194 - DK Barrel Brown": "-TWB64 194 - DK Barrel Brown", - "TWB64 195 - Evangelion Green": "-TWB64 195 - Evangelion Green", - "TWB64 196 - Equestrian Purple": "-TWB64 196 - Equestrian Purple", - "TWB64 197 - Autobot Red": "-TWB64 197 - Autobot Red", - "TWB64 198 - Niconico Sea Green": "-TWB64 198 - Niconico Sea Green", - "TWB64 199 - Duracell Copper": "-TWB64 199 - Duracell Copper", - "TWB64 200 - TOKYO SKYTREE CLOUDY BLUE": "-TWB64 200 - TOKYO SKYTREE CLOUDY BLUE", - "gambatte gb palette twb64 2": "-gambatte gb palette twb64 2", - "PixelShift 01 - Arctic Green": "-PixelShift 01 - Arctic Green", - "PixelShift 02 - Arduboy": "-PixelShift 02 - Arduboy", - "PixelShift 03 - BGB 0.3 Emulator": "-PixelShift 03 - BGB 0.3 Emulator", - "PixelShift 04 - Camouflage": "-PixelShift 04 - Camouflage", - "PixelShift 05 - Chocolate Bar": "-PixelShift 05 - Chocolate Bar", - "PixelShift 06 - CMYK": "-PixelShift 06 - CMYK", - "PixelShift 07 - Cotton Candy": "-PixelShift 07 - Cotton Candy", - "PixelShift 08 - Easy Greens": "-PixelShift 08 - Easy Greens", - "PixelShift 09 - Gamate": "-PixelShift 09 - Gamate", - "PixelShift 10 - Game Boy Light": "-PixelShift 10 - Game Boy Light", - "PixelShift 11 - Game Boy Pocket": "-PixelShift 11 - Game Boy Pocket", - "PixelShift 12 - Game Boy Pocket Alt": "-PixelShift 12 - Game Boy Pocket Alt", - "PixelShift 13 - Game Pocket Computer": "-PixelShift 13 - Game Pocket Computer", - "PixelShift 14 - Game & Watch Ball": "-PixelShift 14 - Game & Watch Ball", - "PixelShift 15 - GB Backlight Blue": "-PixelShift 15 - GB Backlight Blue", - "PixelShift 16 - GB Backlight Faded": "-PixelShift 16 - GB Backlight Faded", - "PixelShift 17 - GB Backlight Orange": "-PixelShift 17 - GB Backlight Orange", - "PixelShift 18 - GB Backlight White ": "-PixelShift 18 - GB Backlight White ", - "PixelShift 19 - GB Backlight Yellow Dark": "-PixelShift 19 - GB Backlight Yellow Dark", - "PixelShift 20 - GB Bootleg": "-PixelShift 20 - GB Bootleg", - "PixelShift 21 - GB Hunter": "-PixelShift 21 - GB Hunter", - "PixelShift 22 - GB Kiosk": "-PixelShift 22 - GB Kiosk", - "PixelShift 23 - GB Kiosk 2": "-PixelShift 23 - GB Kiosk 2", - "PixelShift 24 - GB New": "-PixelShift 24 - GB New", - "PixelShift 25 - GB Nuked": "-PixelShift 25 - GB Nuked", - "PixelShift 26 - GB Old": "-PixelShift 26 - GB Old", - "PixelShift 27 - GBP Bivert": "-PixelShift 27 - GBP Bivert", - "PixelShift 28 - GB Washed Yellow Backlight": "-PixelShift 28 - GB Washed Yellow Backlight", - "PixelShift 29 - Ghost": "-PixelShift 29 - Ghost", - "PixelShift 30 - Glow In The Dark": "-PixelShift 30 - Glow In The Dark", - "PixelShift 31 - Gold Bar": "-PixelShift 31 - Gold Bar", - "PixelShift 32 - Grapefruit": "-PixelShift 32 - Grapefruit", - "PixelShift 33 - Gray Green Mix": "-PixelShift 33 - Gray Green Mix", - "PixelShift 34 - Missingno": "-PixelShift 34 - Missingno", - "PixelShift 35 - MS-Dos": "-PixelShift 35 - MS-Dos", - "PixelShift 36 - Newspaper": "-PixelShift 36 - Newspaper", - "PixelShift 37 - Pip-Boy": "-PixelShift 37 - Pip-Boy", - "PixelShift 38 - Pocket Girl": "-PixelShift 38 - Pocket Girl", - "PixelShift 39 - Silhouette": "-PixelShift 39 - Silhouette", - "PixelShift 40 - Sunburst": "-PixelShift 40 - Sunburst", - "PixelShift 41 - Technicolor": "-PixelShift 41 - Technicolor", - "PixelShift 42 - Tron": "-PixelShift 42 - Tron", - "PixelShift 43 - Vaporwave": "-PixelShift 43 - Vaporwave", - "PixelShift 44 - Virtual Boy": "-PixelShift 44 - Virtual Boy", - "PixelShift 45 - Wish": "-PixelShift 45 - Wish", - "gambatte gb palette pixelshift 1": "-gambatte gb palette pixelshift 1", - "gambatte dark filter level": "-gambatte dark filter level", - "GB": "-GB", - "GBC": "-GBC", - "GBA": "-GBA", - "gambatte gb hwmode": "-gambatte gb hwmode", - "gambatte turbo period": "-gambatte turbo period", - "gambatte rumble level": "-gambatte rumble level", - "pcsx rearmed psxclock": "-pcsx rearmed psxclock", - "pcsx rearmed frameskip threshold": "-pcsx rearmed frameskip threshold", - "pcsx rearmed frameskip interval": "-pcsx rearmed frameskip interval", - "pcsx rearmed input sensitivity": "-pcsx rearmed input sensitivity", - "pcsx rearmed gunconadjustx": "-pcsx rearmed gunconadjustx", - "pcsx rearmed gunconadjusty": "-pcsx rearmed gunconadjusty", - "pcsx rearmed gunconadjustratiox": "-pcsx rearmed gunconadjustratiox", - "pcsx rearmed gunconadjustratioy": "-pcsx rearmed gunconadjustratioy", - "Japan NTSC": "-Japan NTSC", - "Japan PAL": "-Japan PAL", - "US": "-US", - "Europe": "-Europe", - "picodrive region": "-picodrive region", - "Game Gear": "-Game Gear", - "Master System": "-Master System", - "SG-1000": "-SG-1000", - "SC-3000": "-SC-3000", - "picodrive smstype": "-picodrive smstype", - "Sega": "-Sega", - "Codemasters": "-Codemasters", - "Korea": "-Korea", - "Korea MSX": "-Korea MSX", - "Korea X-in-1": "-Korea X-in-1", - "Korea 4-Pak": "-Korea 4-Pak", - "Korea Janggun": "-Korea Janggun", - "Korea Nemesis": "-Korea Nemesis", - "Taiwan 8K RAM": "-Taiwan 8K RAM", - "picodrive smsmapper": "-picodrive smsmapper", - "PAR": "-PAR", - "CRT": "-CRT", - "picodrive aspect": "-picodrive aspect", - "native": "-native", - "picodrive sound rate": "-picodrive sound rate", - "picodrive lowpass range": "-picodrive lowpass range", - "picodrive frameskip threshold": "-picodrive frameskip threshold", - "genesis plus gx frameskip threshold": "-genesis plus gx frameskip threshold", - "genesis plus gx lowpass range": "-genesis plus gx lowpass range", - "genesis plus gx psg preamp": "-genesis plus gx psg preamp", - "genesis plus gx fm preamp": "-genesis plus gx fm preamp", - "genesis plus gx cdda volume": "-genesis plus gx cdda volume", - "genesis plus gx pcm volume": "-genesis plus gx pcm volume", - "genesis plus gx audio eq low": "-genesis plus gx audio eq low", - "genesis plus gx audio eq mid": "-genesis plus gx audio eq mid", - "genesis plus gx audio eq high": "-genesis plus gx audio eq high", - "genesis plus gx enhanced vscroll limit": "-genesis plus gx enhanced vscroll limit", - "genesis plus gx psg channel 0 volume": "-genesis plus gx psg channel 0 volume", - "genesis plus gx psg channel 1 volume": "-genesis plus gx psg channel 1 volume", - "genesis plus gx psg channel 2 volume": "-genesis plus gx psg channel 2 volume", - "genesis plus gx psg channel 3 volume": "-genesis plus gx psg channel 3 volume", - "genesis plus gx md channel 0 volume": "-genesis plus gx md channel 0 volume", - "genesis plus gx md channel 1 volume": "-genesis plus gx md channel 1 volume", - "genesis plus gx md channel 2 volume": "-genesis plus gx md channel 2 volume", - "genesis plus gx md channel 3 volume": "-genesis plus gx md channel 3 volume", - "genesis plus gx md channel 4 volume": "-genesis plus gx md channel 4 volume", - "genesis plus gx md channel 5 volume": "-genesis plus gx md channel 5 volume", - "genesis plus gx sms fm channel 0 volume": "-genesis plus gx sms fm channel 0 volume", - "genesis plus gx sms fm channel 1 volume": "-genesis plus gx sms fm channel 1 volume", - "genesis plus gx sms fm channel 2 volume": "-genesis plus gx sms fm channel 2 volume", - "genesis plus gx sms fm channel 3 volume": "-genesis plus gx sms fm channel 3 volume", - "genesis plus gx sms fm channel 4 volume": "-genesis plus gx sms fm channel 4 volume", - "genesis plus gx sms fm channel 5 volume": "-genesis plus gx sms fm channel 5 volume", - "genesis plus gx sms fm channel 6 volume": "-genesis plus gx sms fm channel 6 volume", - "genesis plus gx sms fm channel 7 volume": "-genesis plus gx sms fm channel 7 volume", - "genesis plus gx sms fm channel 8 volume": "-genesis plus gx sms fm channel 8 volume", - "anaglyph": "-anaglyph", - "cyberscope": "-cyberscope", - "side-by-side": "-side-by-side", - "vli": "-vli", - "hli": "-hli", - "vb 3dmode": "-vb 3dmode", - "black & red": "-black & red", - "black & white": "-black & white", - "black & blue": "-black & blue", - "black & cyan": "-black & cyan", - "black & electric cyan": "-black & electric cyan", - "black & green": "-black & green", - "black & magenta": "-black & magenta", - "black & yellow": "-black & yellow", - "vb color mode": "-vb color mode", - "accurate": "-accurate", - "fast": "-fast", - "vb cpu emulation": "-vb cpu emulation" -} \ No newline at end of file + "fceumm region": "fceumm region", + "fceumm sndquality": "fceumm sndquality", + "fceumm aspect": "fceumm aspect", + "fceumm overscan h left": "fceumm overscan h left", + "fceumm overscan h right": "fceumm overscan h right", + "fceumm overscan v top": "fceumm overscan v top", + "fceumm overscan v bottom": "fceumm overscan v bottom", + "fceumm turbo enable": "fceumm turbo enable", + "fceumm turbo delay": "fceumm turbo delay", + "fceumm zapper tolerance": "fceumm zapper tolerance", + "fceumm mouse sensitivity": "fceumm mouse sensitivity", + "50%": "50%", + "60%": "60%", + "70%": "70%", + "80%": "80%", + "90%": "90%", + "100%": "100%", + "150%": "150%", + "200%": "200%", + "250%": "250%", + "300%": "300%", + "350%": "350%", + "400%": "400%", + "450%": "450%", + "500%": "500%", + "snes9x overclock superfx": "snes9x overclock superfx", + "snes9x superscope crosshair": "snes9x superscope crosshair", + "White": "White", + "White (blend)": "White (blend)", + "Red": "Red", + "Red (blend)": "Red (blend)", + "Orange": "Orange", + "Orange (blend)": "Orange (blend)", + "Yellow": "Yellow", + "Yellow (blend)": "Yellow (blend)", + "Green": "Green", + "Green (blend)": "Green (blend)", + "Cyan": "Cyan", + "Cyan (blend)": "Cyan (blend)", + "Sky": "Sky", + "Sky (blend)": "Sky (blend)", + "Blue": "Blue", + "Blue (blend)": "Blue (blend)", + "Violet": "Violet", + "Violet (blend)": "Violet (blend)", + "Pink": "Pink", + "Pink (blend)": "Pink (blend)", + "Purple": "Purple", + "Purple (blend)": "Purple (blend)", + "Black": "Black", + "Black (blend)": "Black (blend)", + "25% Grey": "25% Grey", + "25% Grey (blend)": "25% Grey (blend)", + "50% Grey": "50% Grey", + "50% Grey (blend)": "50% Grey (blend)", + "75% Grey": "75% Grey", + "75% Grey (blend)": "75% Grey (blend)", + "snes9x superscope color": "snes9x superscope color", + "snes9x justifier1 crosshair": "snes9x justifier1 crosshair", + "snes9x justifier1 color": "snes9x justifier1 color", + "snes9x justifier2 crosshair": "snes9x justifier2 crosshair", + "snes9x justifier2 color": "snes9x justifier2 color", + "snes9x rifle crosshair": "snes9x rifle crosshair", + "snes9x rifle color": "snes9x rifle color", + "1.0x (12.50Mhz)": "1.0x (12.50Mhz)", + "1.1x (13.75Mhz)": "1.1x (13.75Mhz)", + "1.2x (15.00Mhz)": "1.2x (15.00Mhz)", + "1.5x (18.75Mhz)": "1.5x (18.75Mhz)", + "1.6x (20.00Mhz)": "1.6x (20.00Mhz)", + "1.8x (22.50Mhz)": "1.8x (22.50Mhz)", + "2.0x (25.00Mhz)": "2.0x (25.00Mhz)", + "opera cpu overclock": "opera cpu overclock", + "0RGB1555": "0RGB1555", + "RGB565": "RGB565", + "XRGB8888": "XRGB8888", + "opera vdlp pixel format": "opera vdlp pixel format", + "opera nvram version": "opera nvram version", + "opera active devices": "opera active devices", + "stella2014 stelladaptor analog sensitivity": "stella2014 stelladaptor analog sensitivity", + "stella2014 stelladaptor analog center": "stella2014 stelladaptor analog center", + "handy frameskip threshold": "handy frameskip threshold", + "320x240": "320x240", + "640x480": "640x480", + "960x720": "960x720", + "1280x960": "1280x960", + "1440x1080": "1440x1080", + "1600x1200": "1600x1200", + "1920x1440": "1920x1440", + "2240x1680": "2240x1680", + "2560x1920": "2560x1920", + "2880x2160": "2880x2160", + "3200x2400": "3200x2400", + "3520x2640": "3520x2640", + "3840x2880": "3840x2880", + "43screensize": "43screensize", + "3point": "3point", + "standard": "standard", + "BilinearMode": "BilinearMode", + "MultiSampling": "MultiSampling", + "FXAA": "FXAA", + "Software": "Software", + "FromMem": "FromMem", + "EnableCopyDepthToRDRAM": "EnableCopyDepthToRDRAM", + "Stripped": "Stripped", + "OnePiece": "OnePiece", + "BackgroundMode": "BackgroundMode", + "OverscanTop": "OverscanTop", + "OverscanLeft": "OverscanLeft", + "OverscanRight": "OverscanRight", + "OverscanBottom": "OverscanBottom", + "MaxHiResTxVramLimit": "MaxHiResTxVramLimit", + "MaxTxCacheSize": "MaxTxCacheSize", + "Smooth filtering 1": "Smooth filtering 1", + "Smooth filtering 2": "Smooth filtering 2", + "Smooth filtering 3": "Smooth filtering 3", + "Smooth filtering 4": "Smooth filtering 4", + "Sharp filtering 1": "Sharp filtering 1", + "Sharp filtering 2": "Sharp filtering 2", + "txFilterMode": "txFilterMode", + "As Is": "As Is", + "X2": "X2", + "X2SAI": "X2SAI", + "HQ2X": "HQ2X", + "HQ2XS": "HQ2XS", + "LQ2X": "LQ2X", + "LQ2XS": "LQ2XS", + "HQ4X": "HQ4X", + "2xBRZ": "2xBRZ", + "3xBRZ": "3xBRZ", + "4xBRZ": "4xBRZ", + "5xBRZ": "5xBRZ", + "6xBRZ": "6xBRZ", + "txEnhancementMode": "txEnhancementMode", + "Original": "Original", + "Fullspeed": "Fullspeed", + "Framerate": "Framerate", + "virefresh": "virefresh", + "CountPerOp": "CountPerOp", + "CountPerOpDenomPot": "CountPerOpDenomPot", + "deadzone": "deadzone", + "sensitivity": "sensitivity", + "C1": "C1", + "C2": "C2", + "C3": "C3", + "C4": "C4", + "cbutton": "cbutton", + "none": "none", + "memory": "memory", + "rumble": "rumble", + "transfer": "transfer", + "pak1": "pak1", + "pak2": "pak2", + "pak3": "pak3", + "pak4": "pak4", + "Autodetect": "Autodetect", + "Game Boy": "Game Boy", + "Super Game Boy": "Super Game Boy", + "Game Boy Color": "Game Boy Color", + "Game Boy Advance": "Game Boy Advance", + "mgba gb model": "mgba gb model", + "ON": "ON", + "OFF": "OFF", + "mgba use bios": "mgba use bios", + "mgba skip bios": "mgba skip bios", + "Grayscale": "Grayscale", + "DMG Green": "DMG Green", + "GB Pocket": "GB Pocket", + "GB Light": "GB Light", + "GBC Brown ↑": "GBC Brown ↑", + "GBC Red ↑A": "GBC Red ↑A", + "GBC Dark Brown ↑B": "GBC Dark Brown ↑B", + "GBC Pale Yellow ↓": "GBC Pale Yellow ↓", + "GBC Orange ↓A": "GBC Orange ↓A", + "GBC Yellow ↓B": "GBC Yellow ↓B", + "GBC Blue ←": "GBC Blue ←", + "GBC Dark Blue ←A": "GBC Dark Blue ←A", + "GBC Gray ←B": "GBC Gray ←B", + "GBC Green →": "GBC Green →", + "GBC Dark Green →A": "GBC Dark Green →A", + "GBC Reverse →B": "GBC Reverse →B", + "SGB 1-A": "SGB 1-A", + "SGB 1-B": "SGB 1-B", + "SGB 1-C": "SGB 1-C", + "SGB 1-D": "SGB 1-D", + "SGB 1-E": "SGB 1-E", + "SGB 1-F": "SGB 1-F", + "SGB 1-G": "SGB 1-G", + "SGB 1-H": "SGB 1-H", + "SGB 2-A": "SGB 2-A", + "SGB 2-B": "SGB 2-B", + "SGB 2-C": "SGB 2-C", + "SGB 2-D": "SGB 2-D", + "SGB 2-E": "SGB 2-E", + "SGB 2-F": "SGB 2-F", + "SGB 2-G": "SGB 2-G", + "SGB 2-H": "SGB 2-H", + "SGB 3-A": "SGB 3-A", + "SGB 3-B": "SGB 3-B", + "SGB 3-C": "SGB 3-C", + "SGB 3-D": "SGB 3-D", + "SGB 3-E": "SGB 3-E", + "SGB 3-F": "SGB 3-F", + "SGB 3-G": "SGB 3-G", + "SGB 3-H": "SGB 3-H", + "SGB 4-A": "SGB 4-A", + "SGB 4-B": "SGB 4-B", + "SGB 4-C": "SGB 4-C", + "SGB 4-D": "SGB 4-D", + "SGB 4-E": "SGB 4-E", + "SGB 4-F": "SGB 4-F", + "SGB 4-G": "SGB 4-G", + "SGB 4-H": "SGB 4-H", + "mgba gb colors": "mgba gb colors", + "mgba sgb borders": "mgba sgb borders", + "mgba color correction": "mgba color correction", + "mgba solar sensor level": "mgba solar sensor level", + "mgba force gbp": "mgba force gbp", + "Remove Known": "Remove Known", + "Detect and Remove": "Detect and Remove", + "Don't Remove": "Don't Remove", + "mgba idle optimization": "mgba idle optimization", + "mgba frameskip threshold": "mgba frameskip threshold", + "mgba frameskip interval": "mgba frameskip interval", + "GB - DMG": "GB - DMG", + "GB - Pocket": "GB - Pocket", + "GB - Light": "GB - Light", + "GBC - Blue": "GBC - Blue", + "GBC - Brown": "GBC - Brown", + "GBC - Dark Blue": "GBC - Dark Blue", + "GBC - Dark Brown": "GBC - Dark Brown", + "GBC - Dark Green": "GBC - Dark Green", + "GBC - Grayscale": "GBC - Grayscale", + "GBC - Green": "GBC - Green", + "GBC - Inverted": "GBC - Inverted", + "GBC - Orange": "GBC - Orange", + "GBC - Pastel Mix": "GBC - Pastel Mix", + "GBC - Red": "GBC - Red", + "GBC - Yellow": "GBC - Yellow", + "SGB - 1A": "SGB - 1A", + "SGB - 1B": "SGB - 1B", + "SGB - 1C": "SGB - 1C", + "SGB - 1D": "SGB - 1D", + "SGB - 1E": "SGB - 1E", + "SGB - 1F": "SGB - 1F", + "SGB - 1G": "SGB - 1G", + "SGB - 1H": "SGB - 1H", + "SGB - 2A": "SGB - 2A", + "SGB - 2B": "SGB - 2B", + "SGB - 2C": "SGB - 2C", + "SGB - 2D": "SGB - 2D", + "SGB - 2E": "SGB - 2E", + "SGB - 2F": "SGB - 2F", + "SGB - 2G": "SGB - 2G", + "SGB - 2H": "SGB - 2H", + "SGB - 3A": "SGB - 3A", + "SGB - 3B": "SGB - 3B", + "SGB - 3C": "SGB - 3C", + "SGB - 3D": "SGB - 3D", + "SGB - 3E": "SGB - 3E", + "SGB - 3F": "SGB - 3F", + "SGB - 3G": "SGB - 3G", + "SGB - 3H": "SGB - 3H", + "SGB - 4A": "SGB - 4A", + "SGB - 4B": "SGB - 4B", + "SGB - 4C": "SGB - 4C", + "SGB - 4D": "SGB - 4D", + "SGB - 4E": "SGB - 4E", + "SGB - 4F": "SGB - 4F", + "SGB - 4G": "SGB - 4G", + "SGB - 4H": "SGB - 4H", + "Special 1": "Special 1", + "Special 2": "Special 2", + "Special 3": "Special 3", + "Special 4 (TI-83 Legacy)": "Special 4 (TI-83 Legacy)", + "TWB64 - Pack 1": "TWB64 - Pack 1", + "TWB64 - Pack 2": "TWB64 - Pack 2", + "PixelShift - Pack 1": "PixelShift - Pack 1", + "gambatte gb internal palette": "gambatte gb internal palette", + "TWB64 001 - Aqours Blue": "TWB64 001 - Aqours Blue", + "TWB64 002 - Anime Expo Ver.": "TWB64 002 - Anime Expo Ver.", + "TWB64 003 - SpongeBob Yellow": "TWB64 003 - SpongeBob Yellow", + "TWB64 004 - Patrick Star Pink": "TWB64 004 - Patrick Star Pink", + "TWB64 005 - Neon Red": "TWB64 005 - Neon Red", + "TWB64 006 - Neon Blue": "TWB64 006 - Neon Blue", + "TWB64 007 - Neon Yellow": "TWB64 007 - Neon Yellow", + "TWB64 008 - Neon Green": "TWB64 008 - Neon Green", + "TWB64 009 - Neon Pink": "TWB64 009 - Neon Pink", + "TWB64 010 - Mario Red": "TWB64 010 - Mario Red", + "TWB64 011 - Nick Orange": "TWB64 011 - Nick Orange", + "TWB64 012 - Virtual Boy Ver.": "TWB64 012 - Virtual Boy Ver.", + "TWB64 013 - Golden Wild": "TWB64 013 - Golden Wild", + "TWB64 014 - Builder Yellow": "TWB64 014 - Builder Yellow", + "TWB64 015 - Classic Blurple": "TWB64 015 - Classic Blurple", + "TWB64 016 - 765 Production Ver.": "TWB64 016 - 765 Production Ver.", + "TWB64 017 - Superball Ivory": "TWB64 017 - Superball Ivory", + "TWB64 018 - Crunchyroll Orange": "TWB64 018 - Crunchyroll Orange", + "TWB64 019 - Muse Pink": "TWB64 019 - Muse Pink", + "TWB64 020 - Nijigasaki Yellow": "TWB64 020 - Nijigasaki Yellow", + "TWB64 021 - Gamate Ver.": "TWB64 021 - Gamate Ver.", + "TWB64 022 - Greenscale Ver.": "TWB64 022 - Greenscale Ver.", + "TWB64 023 - Odyssey Gold": "TWB64 023 - Odyssey Gold", + "TWB64 024 - Super Saiyan God": "TWB64 024 - Super Saiyan God", + "TWB64 025 - Super Saiyan Blue": "TWB64 025 - Super Saiyan Blue", + "TWB64 026 - Bizarre Pink": "TWB64 026 - Bizarre Pink", + "TWB64 027 - Nintendo Switch Lite Ver.": "TWB64 027 - Nintendo Switch Lite Ver.", + "TWB64 028 - Game.com Ver.": "TWB64 028 - Game.com Ver.", + "TWB64 029 - Sanrio Pink": "TWB64 029 - Sanrio Pink", + "TWB64 030 - BANDAI NAMCO Ver.": "TWB64 030 - BANDAI NAMCO Ver.", + "TWB64 031 - Cosmo Green": "TWB64 031 - Cosmo Green", + "TWB64 032 - Wanda Pink": "TWB64 032 - Wanda Pink", + "TWB64 033 - Link's Awakening DX Ver.": "TWB64 033 - Link's Awakening DX Ver.", + "TWB64 034 - Travel Wood": "TWB64 034 - Travel Wood", + "TWB64 035 - Pokemon Ver.": "TWB64 035 - Pokemon Ver.", + "TWB64 036 - Game Grump Orange": "TWB64 036 - Game Grump Orange", + "TWB64 037 - Scooby-Doo Mystery Ver.": "TWB64 037 - Scooby-Doo Mystery Ver.", + "TWB64 038 - Pokemon mini Ver.": "TWB64 038 - Pokemon mini Ver.", + "TWB64 039 - Supervision Ver.": "TWB64 039 - Supervision Ver.", + "TWB64 040 - DMG Ver.": "TWB64 040 - DMG Ver.", + "TWB64 041 - Pocket Ver.": "TWB64 041 - Pocket Ver.", + "TWB64 042 - Light Ver.": "TWB64 042 - Light Ver.", + "TWB64 043 - Miraitowa Blue": "TWB64 043 - Miraitowa Blue", + "TWB64 044 - Someity Pink": "TWB64 044 - Someity Pink", + "TWB64 045 - Pikachu Yellow": "TWB64 045 - Pikachu Yellow", + "TWB64 046 - Eevee Brown": "TWB64 046 - Eevee Brown", + "TWB64 047 - Microvision Ver.": "TWB64 047 - Microvision Ver.", + "TWB64 048 - TI-83 Ver.": "TWB64 048 - TI-83 Ver.", + "TWB64 049 - Aegis Cherry": "TWB64 049 - Aegis Cherry", + "TWB64 050 - Labo Fawn": "TWB64 050 - Labo Fawn", + "TWB64 051 - MILLION LIVE GOLD!": "TWB64 051 - MILLION LIVE GOLD!", + "TWB64 052 - Tokyo Midtown Ver.": "TWB64 052 - Tokyo Midtown Ver.", + "TWB64 053 - VMU Ver.": "TWB64 053 - VMU Ver.", + "TWB64 054 - Game Master Ver.": "TWB64 054 - Game Master Ver.", + "TWB64 055 - Android Green": "TWB64 055 - Android Green", + "TWB64 056 - Ticketmaster Azure": "TWB64 056 - Ticketmaster Azure", + "TWB64 057 - Google Red": "TWB64 057 - Google Red", + "TWB64 058 - Google Blue": "TWB64 058 - Google Blue", + "TWB64 059 - Google Yellow": "TWB64 059 - Google Yellow", + "TWB64 060 - Google Green": "TWB64 060 - Google Green", + "TWB64 061 - WonderSwan Ver.": "TWB64 061 - WonderSwan Ver.", + "TWB64 062 - Neo Geo Pocket Ver.": "TWB64 062 - Neo Geo Pocket Ver.", + "TWB64 063 - Dew Green": "TWB64 063 - Dew Green", + "TWB64 064 - Coca-Cola Red": "TWB64 064 - Coca-Cola Red", + "TWB64 065 - GameKing Ver.": "TWB64 065 - GameKing Ver.", + "TWB64 066 - Do The Dew Ver.": "TWB64 066 - Do The Dew Ver.", + "TWB64 067 - Digivice Ver.": "TWB64 067 - Digivice Ver.", + "TWB64 068 - Bikini Bottom Ver.": "TWB64 068 - Bikini Bottom Ver.", + "TWB64 069 - Blossom Pink": "TWB64 069 - Blossom Pink", + "TWB64 070 - Bubbles Blue": "TWB64 070 - Bubbles Blue", + "TWB64 071 - Buttercup Green": "TWB64 071 - Buttercup Green", + "TWB64 072 - NASCAR Ver.": "TWB64 072 - NASCAR Ver.", + "TWB64 073 - Lemon-Lime Green": "TWB64 073 - Lemon-Lime Green", + "TWB64 074 - Mega Man V Ver.": "TWB64 074 - Mega Man V Ver.", + "TWB64 075 - Tamagotchi Ver.": "TWB64 075 - Tamagotchi Ver.", + "TWB64 076 - Phantom Red": "TWB64 076 - Phantom Red", + "TWB64 077 - Halloween Ver.": "TWB64 077 - Halloween Ver.", + "TWB64 078 - Christmas Ver.": "TWB64 078 - Christmas Ver.", + "TWB64 079 - Cardcaptor Pink": "TWB64 079 - Cardcaptor Pink", + "TWB64 080 - Pretty Guardian Gold": "TWB64 080 - Pretty Guardian Gold", + "TWB64 081 - Camouflage Ver.": "TWB64 081 - Camouflage Ver.", + "TWB64 082 - Legendary Super Saiyan": "TWB64 082 - Legendary Super Saiyan", + "TWB64 083 - Super Saiyan Rose": "TWB64 083 - Super Saiyan Rose", + "TWB64 084 - Super Saiyan": "TWB64 084 - Super Saiyan", + "TWB64 085 - Perfected Ultra Instinct": "TWB64 085 - Perfected Ultra Instinct", + "TWB64 086 - Saint Snow Red": "TWB64 086 - Saint Snow Red", + "TWB64 087 - Yellow Banana": "TWB64 087 - Yellow Banana", + "TWB64 088 - Green Banana": "TWB64 088 - Green Banana", + "TWB64 089 - Super Saiyan 3": "TWB64 089 - Super Saiyan 3", + "TWB64 090 - Super Saiyan Blue Evolved": "TWB64 090 - Super Saiyan Blue Evolved", + "TWB64 091 - Pocket Tales Ver.": "TWB64 091 - Pocket Tales Ver.", + "TWB64 092 - Investigation Yellow": "TWB64 092 - Investigation Yellow", + "TWB64 093 - S.E.E.S. Blue": "TWB64 093 - S.E.E.S. Blue", + "TWB64 094 - Game Awards Cyan": "TWB64 094 - Game Awards Cyan", + "TWB64 095 - Hokage Orange": "TWB64 095 - Hokage Orange", + "TWB64 096 - Straw Hat Red": "TWB64 096 - Straw Hat Red", + "TWB64 097 - Sword Art Cyan": "TWB64 097 - Sword Art Cyan", + "TWB64 098 - Deku Alpha Emerald": "TWB64 098 - Deku Alpha Emerald", + "TWB64 099 - Blue Stripes Ver.": "TWB64 099 - Blue Stripes Ver.", + "TWB64 100 - Stone Orange": "TWB64 100 - Stone Orange", + "gambatte gb palette twb64 1": "gambatte gb palette twb64 1", + "TWB64 101 - 765PRO Pink": "TWB64 101 - 765PRO Pink", + "TWB64 102 - CINDERELLA Blue": "TWB64 102 - CINDERELLA Blue", + "TWB64 103 - MILLION Yellow!": "TWB64 103 - MILLION Yellow!", + "TWB64 104 - SideM Green": "TWB64 104 - SideM Green", + "TWB64 105 - SHINY Sky Blue": "TWB64 105 - SHINY Sky Blue", + "TWB64 106 - Angry Volcano Ver.": "TWB64 106 - Angry Volcano Ver.", + "TWB64 107 - Yo-kai Pink": "TWB64 107 - Yo-kai Pink", + "TWB64 108 - Yo-kai Green": "TWB64 108 - Yo-kai Green", + "TWB64 109 - Yo-kai Blue": "TWB64 109 - Yo-kai Blue", + "TWB64 110 - Yo-kai Purple": "TWB64 110 - Yo-kai Purple", + "TWB64 111 - Aquatic Iro": "TWB64 111 - Aquatic Iro", + "TWB64 112 - Tea Midori": "TWB64 112 - Tea Midori", + "TWB64 113 - Sakura Pink": "TWB64 113 - Sakura Pink", + "TWB64 114 - Wisteria Murasaki": "TWB64 114 - Wisteria Murasaki", + "TWB64 115 - Oni Aka": "TWB64 115 - Oni Aka", + "TWB64 116 - Golden Kiiro": "TWB64 116 - Golden Kiiro", + "TWB64 117 - Silver Shiro": "TWB64 117 - Silver Shiro", + "TWB64 118 - Fruity Orange": "TWB64 118 - Fruity Orange", + "TWB64 119 - AKB48 Pink": "TWB64 119 - AKB48 Pink", + "TWB64 120 - Miku Blue": "TWB64 120 - Miku Blue", + "TWB64 121 - Fairy Tail Red": "TWB64 121 - Fairy Tail Red", + "TWB64 122 - Survey Corps Brown": "TWB64 122 - Survey Corps Brown", + "TWB64 123 - Island Green": "TWB64 123 - Island Green", + "TWB64 124 - Mania Plus Green": "TWB64 124 - Mania Plus Green", + "TWB64 125 - Ninja Turtle Green": "TWB64 125 - Ninja Turtle Green", + "TWB64 126 - Slime Blue": "TWB64 126 - Slime Blue", + "TWB64 127 - Lime Midori": "TWB64 127 - Lime Midori", + "TWB64 128 - Ghostly Aoi": "TWB64 128 - Ghostly Aoi", + "TWB64 129 - Retro Bogeda": "TWB64 129 - Retro Bogeda", + "TWB64 130 - Royal Blue": "TWB64 130 - Royal Blue", + "TWB64 131 - Neon Purple": "TWB64 131 - Neon Purple", + "TWB64 132 - Neon Orange": "TWB64 132 - Neon Orange", + "TWB64 133 - Moonlight Vision": "TWB64 133 - Moonlight Vision", + "TWB64 134 - Tokyo Red": "TWB64 134 - Tokyo Red", + "TWB64 135 - Paris Gold": "TWB64 135 - Paris Gold", + "TWB64 136 - Beijing Blue": "TWB64 136 - Beijing Blue", + "TWB64 137 - Pac-Man Yellow": "TWB64 137 - Pac-Man Yellow", + "TWB64 138 - Irish Green": "TWB64 138 - Irish Green", + "TWB64 139 - Kakarot Orange": "TWB64 139 - Kakarot Orange", + "TWB64 140 - Dragon Ball Orange": "TWB64 140 - Dragon Ball Orange", + "TWB64 141 - Christmas Gold": "TWB64 141 - Christmas Gold", + "TWB64 142 - Pepsi-Cola Blue": "TWB64 142 - Pepsi-Cola Blue", + "TWB64 143 - Bubblun Green": "TWB64 143 - Bubblun Green", + "TWB64 144 - Bobblun Blue": "TWB64 144 - Bobblun Blue", + "TWB64 145 - Baja Blast Storm": "TWB64 145 - Baja Blast Storm", + "TWB64 146 - Olympic Gold": "TWB64 146 - Olympic Gold", + "TWB64 147 - Value Orange": "TWB64 147 - Value Orange", + "TWB64 148 - Liella Purple!": "TWB64 148 - Liella Purple!", + "TWB64 149 - Olympic Silver": "TWB64 149 - Olympic Silver", + "TWB64 150 - Olympic Bronze": "TWB64 150 - Olympic Bronze", + "TWB64 151 - ANA Sky Blue": "TWB64 151 - ANA Sky Blue", + "TWB64 152 - Nijigasaki Orange": "TWB64 152 - Nijigasaki Orange", + "TWB64 153 - HoloBlue": "TWB64 153 - HoloBlue", + "TWB64 154 - Wrestling Red": "TWB64 154 - Wrestling Red", + "TWB64 155 - Yoshi Egg Green": "TWB64 155 - Yoshi Egg Green", + "TWB64 156 - Pokedex Red": "TWB64 156 - Pokedex Red", + "TWB64 157 - Disney Dream Blue": "TWB64 157 - Disney Dream Blue", + "TWB64 158 - Xbox Green": "TWB64 158 - Xbox Green", + "TWB64 159 - Sonic Mega Blue": "TWB64 159 - Sonic Mega Blue", + "TWB64 160 - G4 Orange": "TWB64 160 - G4 Orange", + "TWB64 161 - Scarlett Green": "TWB64 161 - Scarlett Green", + "TWB64 162 - Glitchy Blue": "TWB64 162 - Glitchy Blue", + "TWB64 163 - Classic LCD": "TWB64 163 - Classic LCD", + "TWB64 164 - 3DS Virtual Console Ver.": "TWB64 164 - 3DS Virtual Console Ver.", + "TWB64 165 - PocketStation Ver.": "TWB64 165 - PocketStation Ver.", + "TWB64 166 - Game and Gold": "TWB64 166 - Game and Gold", + "TWB64 167 - Smurfy Blue": "TWB64 167 - Smurfy Blue", + "TWB64 168 - Swampy Ogre Green": "TWB64 168 - Swampy Ogre Green", + "TWB64 169 - Sailor Spinach Green": "TWB64 169 - Sailor Spinach Green", + "TWB64 170 - Shenron Green": "TWB64 170 - Shenron Green", + "TWB64 171 - Berserk Blood": "TWB64 171 - Berserk Blood", + "TWB64 172 - Super Star Pink": "TWB64 172 - Super Star Pink", + "TWB64 173 - Gamebuino Classic Ver.": "TWB64 173 - Gamebuino Classic Ver.", + "TWB64 174 - Barbie Pink": "TWB64 174 - Barbie Pink", + "TWB64 175 - Star Command Green": "TWB64 175 - Star Command Green", + "TWB64 176 - Nokia 3310 Ver.": "TWB64 176 - Nokia 3310 Ver.", + "TWB64 177 - Clover Green": "TWB64 177 - Clover Green", + "TWB64 178 - Crash Orange": "TWB64 178 - Crash Orange", + "TWB64 179 - Famicom Disk Yellow": "TWB64 179 - Famicom Disk Yellow", + "TWB64 180 - Team Rocket Red": "TWB64 180 - Team Rocket Red", + "TWB64 181 - SEIKO Timer Yellow": "TWB64 181 - SEIKO Timer Yellow", + "TWB64 182 - PINK109": "TWB64 182 - PINK109", + "TWB64 183 - Doraemon Blue": "TWB64 183 - Doraemon Blue", + "TWB64 184 - Fury Blue": "TWB64 184 - Fury Blue", + "TWB64 185 - Rockstar Orange": "TWB64 185 - Rockstar Orange", + "TWB64 186 - Puyo Puyo Green": "TWB64 186 - Puyo Puyo Green", + "TWB64 187 - Susan G. Pink": "TWB64 187 - Susan G. Pink", + "TWB64 188 - Pizza Hut Red": "TWB64 188 - Pizza Hut Red", + "TWB64 189 - Plumbob Green": "TWB64 189 - Plumbob Green", + "TWB64 190 - Grand Ivory": "TWB64 190 - Grand Ivory", + "TWB64 191 - Demon's Gold": "TWB64 191 - Demon's Gold", + "TWB64 192 - SEGA Tokyo Blue": "TWB64 192 - SEGA Tokyo Blue", + "TWB64 193 - Champion Blue": "TWB64 193 - Champion Blue", + "TWB64 194 - DK Barrel Brown": "TWB64 194 - DK Barrel Brown", + "TWB64 195 - Evangelion Green": "TWB64 195 - Evangelion Green", + "TWB64 196 - Equestrian Purple": "TWB64 196 - Equestrian Purple", + "TWB64 197 - Autobot Red": "TWB64 197 - Autobot Red", + "TWB64 198 - Niconico Sea Green": "TWB64 198 - Niconico Sea Green", + "TWB64 199 - Duracell Copper": "TWB64 199 - Duracell Copper", + "TWB64 200 - TOKYO SKYTREE CLOUDY BLUE": "TWB64 200 - TOKYO SKYTREE CLOUDY BLUE", + "gambatte gb palette twb64 2": "gambatte gb palette twb64 2", + "PixelShift 01 - Arctic Green": "PixelShift 01 - Arctic Green", + "PixelShift 02 - Arduboy": "PixelShift 02 - Arduboy", + "PixelShift 03 - BGB 0.3 Emulator": "PixelShift 03 - BGB 0.3 Emulator", + "PixelShift 04 - Camouflage": "PixelShift 04 - Camouflage", + "PixelShift 05 - Chocolate Bar": "PixelShift 05 - Chocolate Bar", + "PixelShift 06 - CMYK": "PixelShift 06 - CMYK", + "PixelShift 07 - Cotton Candy": "PixelShift 07 - Cotton Candy", + "PixelShift 08 - Easy Greens": "PixelShift 08 - Easy Greens", + "PixelShift 09 - Gamate": "PixelShift 09 - Gamate", + "PixelShift 10 - Game Boy Light": "PixelShift 10 - Game Boy Light", + "PixelShift 11 - Game Boy Pocket": "PixelShift 11 - Game Boy Pocket", + "PixelShift 12 - Game Boy Pocket Alt": "PixelShift 12 - Game Boy Pocket Alt", + "PixelShift 13 - Game Pocket Computer": "PixelShift 13 - Game Pocket Computer", + "PixelShift 14 - Game & Watch Ball": "PixelShift 14 - Game & Watch Ball", + "PixelShift 15 - GB Backlight Blue": "PixelShift 15 - GB Backlight Blue", + "PixelShift 16 - GB Backlight Faded": "PixelShift 16 - GB Backlight Faded", + "PixelShift 17 - GB Backlight Orange": "PixelShift 17 - GB Backlight Orange", + "PixelShift 18 - GB Backlight White ": "PixelShift 18 - GB Backlight White ", + "PixelShift 19 - GB Backlight Yellow Dark": "PixelShift 19 - GB Backlight Yellow Dark", + "PixelShift 20 - GB Bootleg": "PixelShift 20 - GB Bootleg", + "PixelShift 21 - GB Hunter": "PixelShift 21 - GB Hunter", + "PixelShift 22 - GB Kiosk": "PixelShift 22 - GB Kiosk", + "PixelShift 23 - GB Kiosk 2": "PixelShift 23 - GB Kiosk 2", + "PixelShift 24 - GB New": "PixelShift 24 - GB New", + "PixelShift 25 - GB Nuked": "PixelShift 25 - GB Nuked", + "PixelShift 26 - GB Old": "PixelShift 26 - GB Old", + "PixelShift 27 - GBP Bivert": "PixelShift 27 - GBP Bivert", + "PixelShift 28 - GB Washed Yellow Backlight": "PixelShift 28 - GB Washed Yellow Backlight", + "PixelShift 29 - Ghost": "PixelShift 29 - Ghost", + "PixelShift 30 - Glow In The Dark": "PixelShift 30 - Glow In The Dark", + "PixelShift 31 - Gold Bar": "PixelShift 31 - Gold Bar", + "PixelShift 32 - Grapefruit": "PixelShift 32 - Grapefruit", + "PixelShift 33 - Gray Green Mix": "PixelShift 33 - Gray Green Mix", + "PixelShift 34 - Missingno": "PixelShift 34 - Missingno", + "PixelShift 35 - MS-Dos": "PixelShift 35 - MS-Dos", + "PixelShift 36 - Newspaper": "PixelShift 36 - Newspaper", + "PixelShift 37 - Pip-Boy": "PixelShift 37 - Pip-Boy", + "PixelShift 38 - Pocket Girl": "PixelShift 38 - Pocket Girl", + "PixelShift 39 - Silhouette": "PixelShift 39 - Silhouette", + "PixelShift 40 - Sunburst": "PixelShift 40 - Sunburst", + "PixelShift 41 - Technicolor": "PixelShift 41 - Technicolor", + "PixelShift 42 - Tron": "PixelShift 42 - Tron", + "PixelShift 43 - Vaporwave": "PixelShift 43 - Vaporwave", + "PixelShift 44 - Virtual Boy": "PixelShift 44 - Virtual Boy", + "PixelShift 45 - Wish": "PixelShift 45 - Wish", + "gambatte gb palette pixelshift 1": "gambatte gb palette pixelshift 1", + "gambatte dark filter level": "gambatte dark filter level", + "GB": "GB", + "GBC": "GBC", + "GBA": "GBA", + "gambatte gb hwmode": "gambatte gb hwmode", + "gambatte turbo period": "gambatte turbo period", + "gambatte rumble level": "gambatte rumble level", + "pcsx rearmed psxclock": "pcsx rearmed psxclock", + "pcsx rearmed frameskip threshold": "pcsx rearmed frameskip threshold", + "pcsx rearmed frameskip interval": "pcsx rearmed frameskip interval", + "pcsx rearmed input sensitivity": "pcsx rearmed input sensitivity", + "pcsx rearmed gunconadjustx": "pcsx rearmed gunconadjustx", + "pcsx rearmed gunconadjusty": "pcsx rearmed gunconadjusty", + "pcsx rearmed gunconadjustratiox": "pcsx rearmed gunconadjustratiox", + "pcsx rearmed gunconadjustratioy": "pcsx rearmed gunconadjustratioy", + "Japan NTSC": "Japan NTSC", + "Japan PAL": "Japan PAL", + "US": "US", + "Europe": "Europe", + "picodrive region": "picodrive region", + "Game Gear": "Game Gear", + "Master System": "Master System", + "SG-1000": "SG-1000", + "SC-3000": "SC-3000", + "picodrive smstype": "picodrive smstype", + "Sega": "Sega", + "Codemasters": "Codemasters", + "Korea": "Korea", + "Korea MSX": "Korea MSX", + "Korea X-in-1": "Korea X-in-1", + "Korea 4-Pak": "Korea 4-Pak", + "Korea Janggun": "Korea Janggun", + "Korea Nemesis": "Korea Nemesis", + "Taiwan 8K RAM": "Taiwan 8K RAM", + "picodrive smsmapper": "picodrive smsmapper", + "PAR": "PAR", + "CRT": "CRT", + "picodrive aspect": "picodrive aspect", + "native": "native", + "picodrive sound rate": "picodrive sound rate", + "picodrive lowpass range": "picodrive lowpass range", + "picodrive frameskip threshold": "picodrive frameskip threshold", + "genesis plus gx frameskip threshold": "genesis plus gx frameskip threshold", + "genesis plus gx lowpass range": "genesis plus gx lowpass range", + "genesis plus gx psg preamp": "genesis plus gx psg preamp", + "genesis plus gx fm preamp": "genesis plus gx fm preamp", + "genesis plus gx cdda volume": "genesis plus gx cdda volume", + "genesis plus gx pcm volume": "genesis plus gx pcm volume", + "genesis plus gx audio eq low": "genesis plus gx audio eq low", + "genesis plus gx audio eq mid": "genesis plus gx audio eq mid", + "genesis plus gx audio eq high": "genesis plus gx audio eq high", + "genesis plus gx enhanced vscroll limit": "genesis plus gx enhanced vscroll limit", + "genesis plus gx psg channel 0 volume": "genesis plus gx psg channel 0 volume", + "genesis plus gx psg channel 1 volume": "genesis plus gx psg channel 1 volume", + "genesis plus gx psg channel 2 volume": "genesis plus gx psg channel 2 volume", + "genesis plus gx psg channel 3 volume": "genesis plus gx psg channel 3 volume", + "genesis plus gx md channel 0 volume": "genesis plus gx md channel 0 volume", + "genesis plus gx md channel 1 volume": "genesis plus gx md channel 1 volume", + "genesis plus gx md channel 2 volume": "genesis plus gx md channel 2 volume", + "genesis plus gx md channel 3 volume": "genesis plus gx md channel 3 volume", + "genesis plus gx md channel 4 volume": "genesis plus gx md channel 4 volume", + "genesis plus gx md channel 5 volume": "genesis plus gx md channel 5 volume", + "genesis plus gx sms fm channel 0 volume": "genesis plus gx sms fm channel 0 volume", + "genesis plus gx sms fm channel 1 volume": "genesis plus gx sms fm channel 1 volume", + "genesis plus gx sms fm channel 2 volume": "genesis plus gx sms fm channel 2 volume", + "genesis plus gx sms fm channel 3 volume": "genesis plus gx sms fm channel 3 volume", + "genesis plus gx sms fm channel 4 volume": "genesis plus gx sms fm channel 4 volume", + "genesis plus gx sms fm channel 5 volume": "genesis plus gx sms fm channel 5 volume", + "genesis plus gx sms fm channel 6 volume": "genesis plus gx sms fm channel 6 volume", + "genesis plus gx sms fm channel 7 volume": "genesis plus gx sms fm channel 7 volume", + "genesis plus gx sms fm channel 8 volume": "genesis plus gx sms fm channel 8 volume", + "anaglyph": "anaglyph", + "cyberscope": "cyberscope", + "side-by-side": "side-by-side", + "vli": "vli", + "hli": "hli", + "vb 3dmode": "vb 3dmode", + "black & red": "black & red", + "black & white": "black & white", + "black & blue": "black & blue", + "black & cyan": "black & cyan", + "black & electric cyan": "black & electric cyan", + "black & green": "black & green", + "black & magenta": "black & magenta", + "black & yellow": "black & yellow", + "vb color mode": "vb color mode", + "accurate": "accurate", + "fast": "fast", + "vb cpu emulation": "vb cpu emulation" +} From 2dd1a3df5c6ef691f7b3db1af60ddd24052384f0 Mon Sep 17 00:00:00 2001 From: Allan Niles Date: Mon, 28 Apr 2025 12:50:23 -0600 Subject: [PATCH 072/137] Add context menu and fix virtualGamepad button --- data/emulator.css | 5 +++-- data/src/emulator.js | 44 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/data/emulator.css b/data/emulator.css index 33f087d4..7349e291 100644 --- a/data/emulator.css +++ b/data/emulator.css @@ -1337,14 +1337,15 @@ .ejs_virtualGamepad_open { display: inline-block; - width: 30px; - height: 30px; + width: 20px; + height: 20px; color: #fff; position: absolute; top: 5px; right: 5px; opacity: .5; z-index: 999; + cursor: pointer; } .ejs_virtualGamepad_open svg { fill: currentColor; diff --git a/data/src/emulator.js b/data/src/emulator.js index 9e879522..80dede8f 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -1171,22 +1171,45 @@ class EmulatorJS { createContextMenu() { this.elements.contextmenu = this.createElement('div'); this.elements.contextmenu.classList.add("ejs_context_menu"); - this.addEventListener(this.game, 'contextmenu', (e) => { - e.preventDefault(); - if ((this.config.buttonOpts && this.config.buttonOpts.rightClick === false) || !this.started) return; + const showMenu = (e) => { const parentRect = this.elements.parent.getBoundingClientRect(); this.elements.contextmenu.style.display = "block"; const rect = this.elements.contextmenu.getBoundingClientRect(); - const up = e.offsetY + rect.height > parentRect.bottom - 25; - const left = e.offsetX + rect.width > parentRect.right - 5; - this.elements.contextmenu.style.left = (e.offsetX - (left ? rect.width : 0)) + "px"; - this.elements.contextmenu.style.top = (e.offsetY - (up ? rect.height : 0)) + "px"; + const up = (e.offsetY || e.touches[0].clientY) + rect.height > parentRect.bottom - 25; + const left = (e.offsetX || e.touches[0].clientX) + rect.width > parentRect.right - 5; + this.elements.contextmenu.style.left = ((e.offsetX || e.touches[0].clientX) - (left ? rect.width : 0)) + "px"; + this.elements.contextmenu.style.top = ((e.offsetY || e.touches[0].clientY) - (up ? rect.height : 0)) + "px"; + } + this.addEventListener(this.game, 'contextmenu', (e) => { + e.preventDefault(); + if ((this.config.buttonOpts && this.config.buttonOpts.rightClick === false) || !this.started) return; + showMenu(e); }) const hideMenu = () => { this.elements.contextmenu.style.display = "none"; } this.addEventListener(this.elements.contextmenu, 'contextmenu', (e) => e.preventDefault()); this.addEventListener(this.elements.parent, 'contextmenu', (e) => e.preventDefault()); + let holdTimer; + let touchHold = false; + this.addEventListener(this.game, 'touchstart', (e) => { + holdTimer = setTimeout(() => { + if (this.elements.contextmenu.style.display === "none") { + showMenu(e); + touchHold = true; + setTimeout(this.menu.close.bind(this), 20); + } + }, 750) + }); + this.addEventListener(this.game, 'touchend', () => { + clearTimeout(holdTimer); + if (touchHold) { + touchHold = false; + return; + } + hideMenu(); + }); + this.addEventListener(this.game, 'touchmove touchcancel', clearTimeout(holdTimer)); this.addEventListener(this.game, 'mousedown', hideMenu); const parent = this.createElement("ul"); const addButton = (title, hidden, functi0n) => { @@ -3765,7 +3788,11 @@ class EmulatorJS { menuButton.innerHTML = ''; menuButton.classList.add("ejs_virtualGamepad_open"); menuButton.style.display = "none"; - this.on("start", () => menuButton.style.display = ""); + this.on("start", () => { + if (this.settings["virtual-gamepad"] !== "disabled" || this.isMobile) { + menuButton.style.display = "" + } + }); this.elements.parent.appendChild(menuButton); let timeout; let ready = true; @@ -3917,6 +3944,7 @@ class EmulatorJS { this.gameManager.setCurrentDisk(value); } else if (option === "virtual-gamepad") { this.toggleVirtualGamepad(value !== "disabled"); + this.elements.menuToggle.style.display = (value !== "disabled" || this.isMobile) ? "" : "none"; } else if (option === "virtual-gamepad-left-handed-mode") { this.toggleVirtualGamepadLeftHanded(value !== "disabled"); } else if (option === "ff-ratio") { From 1b655b9e4da07094827fdd8013f179eb7bab0a1f Mon Sep 17 00:00:00 2001 From: Michael Green <84688932+michael-j-green@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:39:16 +1000 Subject: [PATCH 073/137] Enhance toolbar customisation options (#992) * Proof of concept * POC now implements custom buttons (up to three) * Reversed formatting in loader.js * Reversed formatting changes for emulator.js * Revert formatting changes to index.html * Revert whitespace formatting * Migrated button defaults and setup code to emulator.js * Some more refactoring * Unlimited custom buttons * Remove demo code from index.html --- data/src/emulator.js | 286 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 241 insertions(+), 45 deletions(-) diff --git a/data/src/emulator.js b/data/src/emulator.js index 80dede8f..85b2c74f 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -207,6 +207,7 @@ class EmulatorJS { if (this.debug || (window.location && ['localhost', '127.0.0.1'].includes(location.hostname))) this.checkForUpdates(); this.netplayEnabled = (window.EJS_DEBUG_XX === true) && (window.EJS_EXPERIMENTAL_NETPLAY === true); this.config = config; + this.config.buttonOpts = this.buildButtonOptions(this.config.buttonOpts); this.config.settingsLanguage = window.EJS_settingsLanguage || false; this.currentPopup = null; this.isFastForward = false; @@ -1168,6 +1169,186 @@ class EmulatorJS { elem.appendChild(elm); } } + defaultButtonOptions = { + playPause: { + visible: true, + icon: "play", + displayName: "Play/Pause" + }, + play: { + visible: true, + icon: '', + displayName: "Play" + }, + pause: { + visible: true, + icon: '', + displayName: "Pause" + }, + restart: { + visible: true, + icon: '', + displayName: "Restart" + }, + mute: { + visible: true, + icon: '', + displayName: "Mute" + }, + unmute: { + visible: true, + icon: '', + displayName: "Unmute" + }, + settings: { + visible: true, + icon: '', + displayName: "Settings" + }, + fullscreen: { + visible: true, + icon: "fullscreen", + displayName: "Fullscreen" + }, + enterFullscreen: { + visible: true, + icon: '', + displayName: "Enter Fullscreen" + }, + exitFullscreen: { + visible: true, + icon: '', + displayName: "Exit Fullscreen" + }, + saveState: { + visible: true, + icon: '', + displayName: "Save State" + }, + loadState: { + visible: true, + icon: '', + displayName: "Load State" + }, + screenRecord: { + visible: true + }, + gamepad: { + visible: true, + icon: '', + displayName: "Control Settings" + }, + cheat: { + visible: true, + icon: '', + displayName: "Cheats" + }, + volumeSlider: { + visible: true + }, + saveSavFiles: { + visible: true, + icon: '', + displayName: "Export Save File" + }, + loadSavFiles: { + visible: true, + icon: '', + displayName: "Import Save File" + }, + quickSave: { + visible: true + }, + quickLoad: { + visible: true + }, + screenshot: { + visible: true + }, + cacheManager: { + visible: true, + icon: '', + displayName: "Cache Manager" + }, + exitEmulation: { + visible: true, + icon: '', + displayName: "Exit Emulation" + }, + netplay: { + visible: false, + displayName: "Netplay", + icon: '' + }, + diskButton: { + visible: true, + icon: '', + displayName: "Disks" + }, + contextMenu: { + visible: true, + displayName: "Context Menu", + icon: '' + } + }; + buildButtonOptions(buttonUserOpts) { + let mergedButtonOptions = this.defaultButtonOptions; + + // merge buttonUserOpts with mergedButtonOptions + if (buttonUserOpts) { + for (const key in buttonUserOpts) { + // Check if the button exists in the default buttons, and update its properties + // if the value is a boolean, set the visible property to the value + if (typeof buttonUserOpts[key] === "boolean") { + mergedButtonOptions[key].visible = buttonUserOpts[key]; + } else if (typeof buttonUserOpts[key] === "object") { + // If the value is an object, merge it with the default button properties + + if (this.defaultButtonOptions[key]) { + // copy properties from the button definition if they aren't null + for (const prop in buttonUserOpts[key]) { + if (buttonUserOpts[key][prop] !== null) { + mergedButtonOptions[key][prop] = buttonUserOpts[key][prop]; + } + } + } else { + // button was not in the default buttons list and is therefore a custom button + // verify that the value has a displayName, icon, and callback property + if (buttonUserOpts[key].displayName && buttonUserOpts[key].icon && buttonUserOpts[key].callback) { + mergedButtonOptions[key] = { + visible: true, + displayName: buttonUserOpts[key].displayName, + icon: buttonUserOpts[key].icon, + callback: buttonUserOpts[key].callback, + custom: true + }; + } else { + console.warn(`Custom button "${key}" is missing required properties`); + } + } + } + + // behaviour exceptions + switch (key) { + case "playPause": + mergedButtonOptions.play.visible = mergedButtonOptions.playPause.visible; + mergedButtonOptions.pause.visible = mergedButtonOptions.playPause.visible; + break; + + case "mute": + mergedButtonOptions.unmute.visible = mergedButtonOptions.mute.visible; + break; + + case "fullscreen": + mergedButtonOptions.enterFullscreen.visible = mergedButtonOptions.fullscreen.visible; + mergedButtonOptions.exitFullscreen.visible = mergedButtonOptions.fullscreen.visible; + break; + } + } + } + + return mergedButtonOptions; + } createContextMenu() { this.elements.contextmenu = this.createElement('div'); this.elements.contextmenu.classList.add("ejs_context_menu"); @@ -1402,10 +1583,10 @@ class EmulatorJS { }); if (this.config.buttonOpts) { - if (this.config.buttonOpts.screenshot === false) screenshot.setAttribute("hidden", ""); - if (this.config.buttonOpts.screenRecord === false) startScreenRecording.setAttribute("hidden", ""); - if (this.config.buttonOpts.quickSave === false) qSave.setAttribute("hidden", ""); - if (this.config.buttonOpts.quickLoad === false) qLoad.setAttribute("hidden", ""); + if (this.config.buttonOpts.screenshot.visible === false) screenshot.setAttribute("hidden", ""); + if (this.config.buttonOpts.screenRecord.visible === false) startScreenRecording.setAttribute("hidden", ""); + if (this.config.buttonOpts.quickSave.visible === false) qSave.setAttribute("hidden", ""); + if (this.config.buttonOpts.quickLoad.visible === false) qLoad.setAttribute("hidden", ""); } this.elements.contextmenu.appendChild(parent); @@ -1548,15 +1729,15 @@ class EmulatorJS { let paddingSet = false; //Now add buttons - const addButton = (title, image, callback, element, both) => { + const addButton = (buttonConfig, callback, element, both) => { const button = this.createElement("button"); button.type = "button"; const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute("role", "presentation"); svg.setAttribute("focusable", "false"); - svg.innerHTML = image; + svg.innerHTML = buttonConfig.icon; const text = this.createElement("span"); - text.innerText = this.localization(title); + text.innerText = this.localization(buttonConfig.displayName); if (paddingSet) text.classList.add("ejs_menu_text_right"); text.classList.add("ejs_menu_text"); @@ -1571,10 +1752,14 @@ class EmulatorJS { if (callback instanceof Function) { this.addEventListener(button, 'click', callback); } + + if (buttonConfig.callback instanceof Function) { + this.addEventListener(button, 'click', buttonConfig.callback); + } return both ? [button, svg, text] : button; } - const restartButton = addButton("Restart", '', () => { + const restartButton = addButton(this.config.buttonOpts.restart, () => { if (this.isNetplay && this.netplay.owner) { this.gameManager.restart(); this.netplay.reset(); @@ -1584,7 +1769,7 @@ class EmulatorJS { this.gameManager.restart(); } }); - const pauseButton = addButton("Pause", '', () => { + const pauseButton = addButton(this.config.buttonOpts.pause, () => { if (this.isNetplay && this.netplay.owner) { this.pause(); this.gameManager.saveSaveFiles(); @@ -1593,7 +1778,7 @@ class EmulatorJS { this.pause(); } }); - const playButton = addButton("Play", '', () => { + const playButton = addButton(this.config.buttonOpts.play, () => { if (this.isNetplay && this.netplay.owner) { this.play(); this.netplay.sendMessage({play:true}); @@ -1632,7 +1817,7 @@ class EmulatorJS { } let stateUrl; - const saveState = addButton("Save State", '', async () => { + const saveState = addButton(this.config.buttonOpts.saveState, async () => { const state = this.gameManager.getState(); const called = this.callEvent("saveState", { screenshot: await this.gameManager.screenshot(), @@ -1652,7 +1837,7 @@ class EmulatorJS { a.click(); } }); - const loadState = addButton("Load State", '', async () => { + const loadState = addButton(this.config.buttonOpts.loadState, async () => { const called = this.callEvent("loadState"); if (called > 0) return; if (this.settings['save-state-location'] === "browser" && this.saveInBrowserSupported()) { @@ -1666,21 +1851,21 @@ class EmulatorJS { this.gameManager.loadState(state); } }); - const controlMenu = addButton("Control Settings", '', () => { + const controlMenu = addButton(this.config.buttonOpts.gamepad, () => { this.controlMenu.style.display = ""; }); - const cheatMenu = addButton("Cheats", '', () => { + const cheatMenu = addButton(this.config.buttonOpts.cheat, () => { this.cheatMenu.style.display = ""; }); - - const cache = addButton("Cache Manager", '', () => { + + const cache = addButton(this.config.buttonOpts.cacheManager, () => { this.openCacheMenu(); }); if (this.config.disableDatabases) cache.style.display = "none"; let savUrl; - - const saveSavFiles = addButton("Export Save File", '', async () => { + + const saveSavFiles = addButton(this.config.buttonOpts.saveSavFiles, async () => { const file = await this.gameManager.getSaveFile(); const called = this.callEvent("saveSave", { screenshot: await this.gameManager.screenshot(), @@ -1694,7 +1879,7 @@ class EmulatorJS { a.download = this.gameManager.getSaveFilePath().split("/").pop(); a.click(); }); - const loadSavFiles = addButton("Import Save File", '', async () => { + const loadSavFiles = addButton(this.config.buttonOpts.loadSavFiles, async () => { const called = this.callEvent("loadSave"); if (called > 0) return; const file = await this.selectFile(); @@ -1711,10 +1896,20 @@ class EmulatorJS { this.gameManager.FS.writeFile(path, sav); this.gameManager.loadSaveFiles(); }); - const netplay = addButton("Netplay", '', async () => { + const netplay = addButton(this.config.buttonOpts.netplay, async () => { this.openNetplayMenu(); }); + // add custom buttons + // get all elements from this.config.buttonOpts with custom: true + if (this.config.buttonOpts) { + for (const [key, value] of Object.entries(this.config.buttonOpts)) { + if (value.custom === true) { + const customBtn = addButton(value); + } + } + } + const spacer = this.createElement("span"); spacer.classList.add("ejs_menu_bar_spacer"); this.elements.menu.appendChild(spacer); @@ -1722,13 +1917,13 @@ class EmulatorJS { const volumeSettings = this.createElement("div"); volumeSettings.classList.add("ejs_volume_parent"); - const muteButton = addButton("Mute", '', () => { + const muteButton = addButton(this.config.buttonOpts.mute, () => { muteButton.style.display = "none"; unmuteButton.style.display = ""; this.muted = true; this.setVolume(0); }, volumeSettings); - const unmuteButton = addButton("Unmute", '', () => { + const unmuteButton = addButton(this.config.buttonOpts.unmute, () => { if (this.volume === 0) this.volume = 0.5; muteButton.style.display = ""; unmuteButton.style.display = "none"; @@ -1782,7 +1977,7 @@ class EmulatorJS { this.elements.menu.appendChild(volumeSettings); - const contextMenuButton = addButton("Context Menu", '', () => { + const contextMenuButton = addButton(this.config.buttonOpts.contextMenu, () => { if (this.elements.contextmenu.style.display === "none") { this.elements.contextmenu.style.display = "block"; this.elements.contextmenu.style.left = (getComputedStyle(this.elements.parent).width.split("px")[0]/2 - getComputedStyle(this.elements.contextmenu).width.split("px")[0]/2)+"px"; @@ -1796,7 +1991,7 @@ class EmulatorJS { this.diskParent = this.createElement("div"); this.diskParent.id = "ejs_disksMenu"; this.disksMenuOpen = false; - const diskButton = addButton("Disks", '', () => { + const diskButton = addButton(this.config.buttonOpts.diskButton, () => { this.disksMenuOpen = !this.disksMenuOpen; diskButton[1].classList.toggle("ejs_svg_rotate", this.disksMenuOpen); this.disksMenu.style.display = this.disksMenuOpen ? "" : "none"; @@ -1819,7 +2014,7 @@ class EmulatorJS { this.settingParent = this.createElement("div"); this.settingsMenuOpen = false; - const settingButton = addButton("Settings", '', () => { + const settingButton = addButton(this.config.buttonOpts.settings, () => { this.settingsMenuOpen = !this.settingsMenuOpen; settingButton[1].classList.toggle("ejs_svg_rotate", this.settingsMenuOpen); this.settingsMenu.style.display = this.settingsMenuOpen ? "" : "none"; @@ -1851,11 +2046,11 @@ class EmulatorJS { this.menu.close(); } }) - - const enter = addButton("Enter Fullscreen", '', () => { + + const enter = addButton(this.config.buttonOpts.enterFullscreen, () => { this.toggleFullscreen(true); }); - const exit = addButton("Exit Fullscreen", '', () => { + const exit = addButton(this.config.buttonOpts.exitFullscreen, () => { this.toggleFullscreen(false); }); exit.style.display = "none"; @@ -1899,7 +2094,7 @@ class EmulatorJS { } let exitMenuIsOpen = false; - const exitEmulation = addButton("Exit EmulatorJS", '', async () => { + const exitEmulation = addButton(this.config.buttonOpts.exitEmulation, async () => { if (exitMenuIsOpen) return; exitMenuIsOpen = true; const popups = this.createSubPopup(); @@ -1993,32 +2188,33 @@ class EmulatorJS { if (this.config.buttonOpts) { if (this.debug) console.log(this.config.buttonOpts); - if (this.config.buttonOpts.playPause === false) { + if (this.config.buttonOpts.playPause.visible === false) { pauseButton.style.display = "none"; playButton.style.display = "none"; } if (this.config.buttonOpts.contextMenuButton === false && this.config.buttonOpts.rightClick !== false && this.isMobile === false) contextMenuButton.style.display = "none" - if (this.config.buttonOpts.restart === false) restartButton.style.display = "none" - if (this.config.buttonOpts.settings === false) settingButton[0].style.display = "none" - if (this.config.buttonOpts.fullscreen === false) { + if (this.config.buttonOpts.restart.visible === false) restartButton.style.display = "none" + if (this.config.buttonOpts.settings.visible === false) settingButton[0].style.display = "none" + if (this.config.buttonOpts.fullscreen.visible === false) { enter.style.display = "none"; exit.style.display = "none"; } - if (this.config.buttonOpts.mute === false) { + if (this.config.buttonOpts.mute.visible === false) { muteButton.style.display = "none"; unmuteButton.style.display = "none"; } - if (this.config.buttonOpts.saveState === false) saveState.style.display = "none"; - if (this.config.buttonOpts.loadState === false) loadState.style.display = "none"; - if (this.config.buttonOpts.saveSavFiles === false) saveSavFiles.style.display = "none"; - if (this.config.buttonOpts.loadSavFiles === false) loadSavFiles.style.display = "none"; - if (this.config.buttonOpts.gamepad === false) controlMenu.style.display = "none"; - if (this.config.buttonOpts.cheat === false) cheatMenu.style.display = "none"; - if (this.config.buttonOpts.cacheManager === false) cache.style.display = "none"; - if (this.config.buttonOpts.netplay === false) netplay.style.display = "none"; - if (this.config.buttonOpts.diskButton === false) diskButton[0].style.display = "none"; - if (this.config.buttonOpts.volumeSlider === false) volumeSlider.style.display = "none"; - if (this.config.buttonOpts.exitEmulation === false) exitEmulation.style.display = "none"; + if (this.config.buttonOpts.saveState.visible === false) saveState.style.display = "none"; + if (this.config.buttonOpts.loadState.visible === false) loadState.style.display = "none"; + if (this.config.buttonOpts.saveSavFiles.visible === false) saveSavFiles.style.display = "none"; + if (this.config.buttonOpts.loadSavFiles.visible === false) loadSavFiles.style.display = "none"; + if (this.config.buttonOpts.gamepad.visible === false) controlMenu.style.display = "none"; + if (this.config.buttonOpts.cheat.visible === false) cheatMenu.style.display = "none"; + if (this.config.buttonOpts.cacheManager.visible === false) cache.style.display = "none"; + if (this.config.buttonOpts.netplay.visible === false) netplay.style.display = "none"; + if (this.config.buttonOpts.diskButton.visible === false) diskButton[0].style.display = "none"; + if (this.config.buttonOpts.volumeSlider.visible === false) volumeSlider.style.display = "none"; + if (this.config.buttonOpts.contextMenu.visible === false) contextMenuButton.style.display = "none"; + if (this.config.buttonOpts.exitEmulation.visible === false) exitEmulation.style.display = "none"; } this.menu.failedToStart = () => { From 45cc488ebc630293821ef854fc7808fb3511c435 Mon Sep 17 00:00:00 2001 From: Allan Niles Date: Mon, 28 Apr 2025 22:24:19 -0600 Subject: [PATCH 074/137] Add update script (#990) * add update script * fix formatting * spacing * typo * spacing * new line --- .github/workflows/latest.yml | 2 +- README.md | 2 +- data/src/nipplejs.js | 2 +- data/src/socket.io.min.js | 6 +- data/version.json | 6 +- docs/contributors.json | 46 ++++++++ docs/{Contributors.md => contributors.md} | 101 +++++------------ package.json | 9 +- update.js | 126 ++++++++++++++++++++++ 9 files changed, 218 insertions(+), 82 deletions(-) create mode 100644 docs/contributors.json rename docs/{Contributors.md => contributors.md} (67%) create mode 100644 update.js diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index bcc8ba62..1de76fb6 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -16,7 +16,7 @@ jobs: run: | cd /mnt/HDD/public chmod -R 755 .EmulatorJS/ - - name: Update Stable + - name: Update Latest run: | cd /mnt/HDD/public/.EmulatorJS/ git fetch --all diff --git a/README.md b/README.md index 0b00d14d..f51cf604 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ If you want to help with localization, please check out the [localization](data/ [Configurator]: https://emulatorjs.org/editor -[Contributors]: docs/Contributors.md +[Contributors]: docs/contributors.md [Website]: https://emulatorjs.org/ [Usage]: https://emulatorjs.org/docs/ [Demo]: https://demo.emulatorjs.org/ diff --git a/data/src/nipplejs.js b/data/src/nipplejs.js index 48fa61c1..2c31fb62 100644 --- a/data/src/nipplejs.js +++ b/data/src/nipplejs.js @@ -1 +1 @@ -!function(t,i){"object"==typeof exports&&"object"==typeof module?module.exports=i():"function"==typeof define&&define.amd?define("nipplejs",[],i):"object"==typeof exports?exports.nipplejs=i():t.nipplejs=i()}(window,(function(){return function(t){var i={};function e(o){if(i[o])return i[o].exports;var n=i[o]={i:o,l:!1,exports:{}};return t[o].call(n.exports,n,n.exports,e),n.l=!0,n.exports}return e.m=t,e.c=i,e.d=function(t,i,o){e.o(t,i)||Object.defineProperty(t,i,{enumerable:!0,get:o})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,i){if(1&i&&(t=e(t)),8&i)return t;if(4&i&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(e.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&i&&"string"!=typeof t)for(var n in t)e.d(o,n,function(i){return t[i]}.bind(null,n));return o},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,i){return Object.prototype.hasOwnProperty.call(t,i)},e.p="",e(e.s=0)}([function(t,i,e){"use strict";e.r(i);var o,n=function(t,i){var e=i.x-t.x,o=i.y-t.y;return Math.sqrt(e*e+o*o)},s=function(t){return t*(Math.PI/180)},r=function(t){return t*(180/Math.PI)},d=new Map,a=function(t){d.has(t)&&clearTimeout(d.get(t)),d.set(t,setTimeout(t,100))},p=function(t,i,e){for(var o,n=i.split(/[ ,]+/g),s=0;s=0&&this._handlers_[t].splice(this._handlers_[t].indexOf(i),1),this},_.prototype.trigger=function(t,i){var e,o=this,n=t.split(/[ ,]+/g);o._handlers_=o._handlers_||{};for(var s=0;ss&&n<3*s&&!t.lockX?i="up":n>-s&&n<=s&&!t.lockY?i="left":n>3*-s&&n<=-s&&!t.lockX?i="down":t.lockY||(i="right"),t.lockY||(e=n>-r&&n0?"up":"down"),t.force>this.options.threshold){var d,a={};for(d in this.direction)this.direction.hasOwnProperty(d)&&(a[d]=this.direction[d]);var p={};for(d in this.direction={x:e,y:o,angle:i},t.direction=this.direction,a)a[d]===this.direction[d]&&(p[d]=!0);if(p.x&&p.y&&p.angle)return t;p.x&&p.y||this.trigger("plain",t),p.x||this.trigger("plain:"+e,t),p.y||this.trigger("plain:"+o,t),p.angle||this.trigger("dir dir:"+i,t)}else this.resetDirection();return t};var P=k;function E(t,i){this.nipples=[],this.idles=[],this.actives=[],this.ids=[],this.pressureIntervals={},this.manager=t,this.id=E.id,E.id+=1,this.defaults={zone:document.body,multitouch:!1,maxNumberOfNipples:10,mode:"dynamic",position:{top:0,left:0},catchDistance:200,size:100,threshold:.1,color:"white",fadeTime:250,dataOnly:!1,restJoystick:!0,restOpacity:.5,lockX:!1,lockY:!1,shape:"circle",dynamicPage:!1,follow:!1},this.config(i),"static"!==this.options.mode&&"semi"!==this.options.mode||(this.options.multitouch=!1),this.options.multitouch||(this.options.maxNumberOfNipples=1);var e=getComputedStyle(this.options.zone.parentElement);return e&&"flex"===e.display&&(this.parentIsFlex=!0),this.updateBox(),this.prepareNipples(),this.bindings(),this.begin(),this.nipples}E.prototype=new T,E.constructor=E,E.id=0,E.prototype.prepareNipples=function(){var t=this.nipples;t.on=this.on.bind(this),t.off=this.off.bind(this),t.options=this.options,t.destroy=this.destroy.bind(this),t.ids=this.ids,t.id=this.id,t.processOnMove=this.processOnMove.bind(this),t.processOnEnd=this.processOnEnd.bind(this),t.get=function(i){if(void 0===i)return t[0];for(var e=0,o=t.length;e