Load core options from config.yml

This commit is contained in:
Georges-Antoine Assi
2025-09-01 21:16:37 -04:00
parent aca8ffc4f5
commit 147a94c406
11 changed files with 61 additions and 3 deletions

View File

@@ -41,6 +41,7 @@ class Config:
PLATFORMS_VERSIONS: dict[str, str]
ROMS_FOLDER_NAME: str
FIRMWARE_FOLDER_NAME: str
EJS_CORE_OPTIONS: dict[str, dict[str, str]]
HIGH_PRIO_STRUCTURE_PATH: str
def __init__(self, **entries):
@@ -149,6 +150,7 @@ class ConfigManager:
FIRMWARE_FOLDER_NAME=pydash.get(
self._raw_config, "filesystem.firmware_folder", "bios"
),
EJS_CORE_OPTIONS=pydash.get(self._raw_config, "emulatorjs", {}),
)
def _validate_config(self):
@@ -231,6 +233,17 @@ class ConfigManager:
)
sys.exit(3)
if not isinstance(self.config.EJS_CORE_OPTIONS, dict):
log.critical("Invalid config.yml: emulatorjs must be a dictionary")
sys.exit(3)
else:
for core, options in self.config.EJS_CORE_OPTIONS.items():
if not isinstance(options, dict):
log.critical(
f"Invalid config.yml: emulatorjs.{core} must be a dictionary"
)
sys.exit(3)
def get_config(self) -> Config:
with open(self.config_file) as config_file:
self._raw_config = yaml.load(config_file, Loader=SafeLoader) or {}

View File

@@ -35,6 +35,7 @@ def get_config() -> ConfigResponse:
EXCLUDED_MULTI_PARTS_FILES=cfg.EXCLUDED_MULTI_PARTS_FILES,
PLATFORMS_BINDING=cfg.PLATFORMS_BINDING,
PLATFORMS_VERSIONS=cfg.PLATFORMS_VERSIONS,
EJS_CORE_OPTIONS=cfg.EJS_CORE_OPTIONS,
)
except ConfigNotReadableException as exc:
log.critical(exc.message)

View File

@@ -10,3 +10,4 @@ class ConfigResponse(TypedDict):
EXCLUDED_MULTI_PARTS_FILES: list[str]
PLATFORMS_BINDING: dict[str, str]
PLATFORMS_VERSIONS: dict[str, str]
EJS_CORE_OPTIONS: dict[str, dict[str, str]]

View File

@@ -29,3 +29,9 @@ system:
filesystem:
roms_folder: "ROMS"
firmware_folder: "BIOS"
emulatorjs:
parallel_n64:
vsync: disable
snes9x:
snes9x_region: ntsc

View File

@@ -19,6 +19,10 @@ def test_config_loader():
assert loader.config.PLATFORMS_VERSIONS == {"naomi": "arcade"}
assert loader.config.ROMS_FOLDER_NAME == "ROMS"
assert loader.config.FIRMWARE_FOLDER_NAME == "BIOS"
assert loader.config.EJS_CORE_OPTIONS == {
"parallel_n64": {"vsync": "disable"},
"snes9x": {"snes9x_region": "ntsc"},
}
def test_empty_config_loader():
@@ -38,3 +42,4 @@ def test_empty_config_loader():
assert loader.config.PLATFORMS_VERSIONS == {}
assert loader.config.ROMS_FOLDER_NAME == "roms"
assert loader.config.FIRMWARE_FOLDER_NAME == "bios"
assert loader.config.EJS_CORE_OPTIONS == {}

View File

@@ -46,3 +46,12 @@ system:
# The folder name where your roms are located
filesystem: {} # { roms_folder: 'roms' } For example if your folder structure is /home/user/library/roms_folder
# EmulatorJS per-core options
emulatorjs:
parallel_n64: # Use the exact core name
vsync: disable
snes9x:
snes9x_region: ntsc
default: # These settings apply to all cores
fps: show

View File

@@ -11,5 +11,6 @@ export type ConfigResponse = {
EXCLUDED_MULTI_PARTS_FILES: Array<string>;
PLATFORMS_BINDING: Record<string, string>;
PLATFORMS_VERSIONS: Record<string, string>;
EJS_CORE_OPTIONS: Record<string, Record<string, string>>;
};

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import languageStore from "@/stores/language";
import { storeToRefs } from "pinia";

View File

@@ -20,6 +20,7 @@ const defaultConfig = {
EXCLUDED_MULTI_PARTS_FILES: [],
PLATFORMS_BINDING: {},
PLATFORMS_VERSIONS: {},
EJS_CORE_OPTIONS: {},
} as ConfigResponse;
export default defineStore("config", {
@@ -66,6 +67,14 @@ export default defineStore("config", {
isExclusionType(type: string): type is ExclusionTypes {
return Object.keys(this.config).includes(type);
},
getEJSCoreOptions(core: string | null): Record<string, string | boolean> {
const defaultOptions = this.config.EJS_CORE_OPTIONS["default"];
if (!core) return defaultOptions;
return {
...defaultOptions,
...this.config.EJS_CORE_OPTIONS[core],
};
},
reset() {},
},
});

View File

@@ -2,6 +2,7 @@
import type { FirmwareSchema, SaveSchema, StateSchema } from "@/__generated__";
import { saveApi as api } from "@/services/api/save";
import storeRoms, { type DetailedRom } from "@/stores/roms";
import storeConfig from "@/stores/config";
import {
areThreadsRequiredForEJSCore,
getSupportedEJSCores,
@@ -22,11 +23,14 @@ import {
import type { Emitter } from "mitt";
import type { Events } from "@/types/emitter";
import storePlaying from "@/stores/playing";
import languageStore from "@/stores/language";
import { storeToRefs } from "pinia";
const INVALID_CHARS_REGEX = /[#<$+%>!`&*'|{}/\\?"=@:^\r\n]/gi;
const romsStore = storeRoms();
const configStore = storeConfig();
const props = defineProps<{
rom: DetailedRom;
save: SaveSchema | null;
@@ -41,6 +45,8 @@ const theme = useTheme();
const emitter = inject<Emitter<Events>>("emitter");
const playingStore = storePlaying();
const { playing, fullScreen } = storeToRefs(playingStore);
const storeLanguage = languageStore();
const { selectedLanguage } = storeToRefs(storeLanguage);
// Declare global variables for EmulatorJS
declare global {
@@ -67,6 +73,9 @@ declare global {
EJS_threads: boolean;
EJS_controlScheme: string | null;
EJS_emulator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
EJS_language: string;
EJS_disableAutoLang: boolean;
EJS_DEBUG_XX: boolean;
EJS_Buttons: Record<string, boolean>;
EJS_VirtualGamepadSettings: {};
EJS_onGameStart: () => void;
@@ -108,15 +117,20 @@ window.EJS_Buttons = {
// Disable the standard exit button to implement our own
exitEmulation: false,
};
// Force saving saves and states to the browser
const coreOptions = configStore.getEJSCoreOptions(props.core);
window.EJS_defaultOptions = {
// Force saving saves and states to the browser
"save-state-location": "browser",
rewindEnabled: "enabled",
...coreOptions,
};
// Set a valid game name
window.EJS_gameName = romRef.value.fs_name_no_tags
.replace(INVALID_CHARS_REGEX, "")
.trim();
window.EJS_language = selectedLanguage.value.value.replace("_", "-");
window.EJS_disableAutoLang = true;
window.EJS_DEBUG_XX = true;
onMounted(() => {
window.scrollTo(0, 0);

View File

@@ -178,7 +178,7 @@ export function createExitEmulationButton(): HTMLButtonElement {
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 460 460"><path style="fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(255,255,255);stroke-opacity:1;stroke-miterlimit:4;" d="M 14.000061 7.636414 L 14.000061 4.5 C 14.000061 4.223877 13.776123 3.999939 13.5 3.999939 L 4.5 3.999939 C 4.223877 3.999939 3.999939 4.223877 3.999939 4.5 L 3.999939 19.5 C 3.999939 19.776123 4.223877 20.000061 4.5 20.000061 L 13.5 20.000061 C 13.776123 20.000061 14.000061 19.776123 14.000061 19.5 L 14.000061 16.363586 " transform="matrix(21.333333,0,0,21.333333,0,0)"></path><path style="fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(255,255,255);stroke-opacity:1;stroke-miterlimit:4;" d="M 9.999939 12 L 21 12 M 21 12 L 18.000366 8.499939 M 21 12 L 18 15.500061 " transform="matrix(21.333333,0,0,21.333333,0,0)"></path></svg>';
const text = document.createElement("span");
text.classList.add("ejs_menu_text", "ejs_menu_text_right");
text.innerText = "Exit Emulation";
text.innerText = "Quit";
button.classList.add("ejs_menu_button");
button.appendChild(svg);
button.appendChild(text);