add debug and controls

This commit is contained in:
Georges-Antoine Assi
2025-09-02 08:38:11 -04:00
parent 147a94c406
commit e42acbfafc
11 changed files with 128 additions and 26 deletions

View File

@@ -1,6 +1,6 @@
import json
import sys
from typing import Final
from typing import Final, NotRequired, TypedDict
import pydash
import yaml
@@ -30,6 +30,11 @@ ROMM_USER_CONFIG_FILE: Final = f"{ROMM_USER_CONFIG_PATH}/config.yml"
SQLITE_DB_BASE_PATH: Final = f"{ROMM_BASE_PATH}/database"
class EjsControlsButton(TypedDict):
value: NotRequired[str]
value2: NotRequired[str]
class Config:
EXCLUDED_PLATFORMS: list[str]
EXCLUDED_SINGLE_EXT: list[str]
@@ -41,7 +46,13 @@ class Config:
PLATFORMS_VERSIONS: dict[str, str]
ROMS_FOLDER_NAME: str
FIRMWARE_FOLDER_NAME: str
EJS_CORE_OPTIONS: dict[str, dict[str, str]]
EJS_DEBUG: bool
EJS_OPTIONS: dict[
str, dict[str, str]
] # dict[core_name, dict[option_name, option_value]]
EJS_CONTROLS: dict[
str, dict[int, dict[int, EjsControlsButton]]
] # dict[core_name, dict[player_number, dict[button_number, EjsControlsButton]]]
HIGH_PRIO_STRUCTURE_PATH: str
def __init__(self, **entries):
@@ -150,7 +161,9 @@ class ConfigManager:
FIRMWARE_FOLDER_NAME=pydash.get(
self._raw_config, "filesystem.firmware_folder", "bios"
),
EJS_CORE_OPTIONS=pydash.get(self._raw_config, "emulatorjs", {}),
EJS_DEBUG=pydash.get(self._raw_config, "emulatorjs.debug", False),
EJS_OPTIONS=pydash.get(self._raw_config, "emulatorjs.options", {}),
EJS_CONTROLS=pydash.get(self._raw_config, "emulatorjs.controls", {}),
)
def _validate_config(self):
@@ -233,17 +246,46 @@ class ConfigManager:
)
sys.exit(3)
if not isinstance(self.config.EJS_CORE_OPTIONS, dict):
if not isinstance(self.config.EJS_DEBUG, bool):
log.critical("Invalid config.yml: emulatorjs.debug must be a boolean")
sys.exit(3)
if not isinstance(self.config.EJS_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():
for core, options in self.config.EJS_OPTIONS.items():
if not isinstance(options, dict):
log.critical(
f"Invalid config.yml: emulatorjs.{core} must be a dictionary"
)
sys.exit(3)
if not isinstance(self.config.EJS_CONTROLS, dict):
log.critical("Invalid config.yml: emulatorjs.controls must be a dictionary")
sys.exit(3)
else:
for core, controls in self.config.EJS_CONTROLS.items():
if not isinstance(controls, dict):
log.critical(
f"Invalid config.yml: emulatorjs.controls.{core} must be a dictionary"
)
sys.exit(3)
for player, buttons in controls.items():
if not isinstance(buttons, dict):
log.critical(
f"Invalid config.yml: emulatorjs.controls.{core}.{player} must be a dictionary"
)
sys.exit(3)
for button, value in buttons.items():
if not isinstance(value, dict):
log.critical(
f"Invalid config.yml: emulatorjs.controls.{core}.{player}.{button} 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,7 +35,9 @@ 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,
EJS_DEBUG=cfg.EJS_DEBUG,
EJS_CONTROLS=cfg.EJS_CONTROLS,
EJS_OPTIONS=cfg.EJS_OPTIONS,
)
except ConfigNotReadableException as exc:
log.critical(exc.message)

View File

@@ -1,5 +1,7 @@
from typing import TypedDict
from config.config_manager import EjsControlsButton
class ConfigResponse(TypedDict):
EXCLUDED_PLATFORMS: list[str]
@@ -10,4 +12,6 @@ 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]]
EJS_DEBUG: bool
EJS_OPTIONS: dict[str, dict[str, str]]
EJS_CONTROLS: dict[str, dict[int, dict[int, EjsControlsButton]]]

View File

@@ -31,7 +31,15 @@ filesystem:
firmware_folder: "BIOS"
emulatorjs:
parallel_n64:
vsync: disable
snes9x:
snes9x_region: ntsc
debug: true
options:
parallel_n64:
vsync: disable
snes9x:
snes9x_region: ntsc
controls:
snes9x:
0:
0:
value: x
value2: BUTTON_2

View File

@@ -19,10 +19,16 @@ 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 == {
assert loader.config.EJS_DEBUG
assert loader.config.EJS_OPTIONS == {
"parallel_n64": {"vsync": "disable"},
"snes9x": {"snes9x_region": "ntsc"},
}
assert loader.config.EJS_CONTROLS == {
"snes9x": {
0: {0: {"value": "x", "value2": "BUTTON_2"}},
},
}
def test_empty_config_loader():
@@ -42,4 +48,6 @@ 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 == {}
assert not loader.config.EJS_DEBUG
assert loader.config.EJS_OPTIONS == {}
assert loader.config.EJS_CONTROLS == {}

View File

@@ -49,9 +49,20 @@ filesystem: {} # { roms_folder: 'roms' } For example if your folder structure is
# 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
debug: true # Available options will be logged to the browser consolewhen the emulator is started
options:
parallel_n64: # Use the exact core name
vsync: disable
snes9x:
snes9x_region: ntsc
default: # These settings apply to all cores
fps: show
controls: # https://emulatorjs.org/docs4devs/control-mapping/
snes9x:
0: # Player 1
0:
value: x # Default mapping for keyboard
value2: BUTTON_2 # Default mapping for connected controller
1: # Player 2
2: # Player 3
3: # Player 4

View File

@@ -24,6 +24,7 @@ export type { ConfigResponse } from './models/ConfigResponse';
export type { CustomLimitOffsetPage_SimpleRomSchema_ } from './models/CustomLimitOffsetPage_SimpleRomSchema_';
export type { DetailedRomSchema } from './models/DetailedRomSchema';
export type { EarnedAchievement } from './models/EarnedAchievement';
export type { EjsControlsButton } from './models/EjsControlsButton';
export type { EmulationDict } from './models/EmulationDict';
export type { FilesystemDict } from './models/FilesystemDict';
export type { FirmwareSchema } from './models/FirmwareSchema';

View File

@@ -2,6 +2,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { EjsControlsButton } from './EjsControlsButton';
export type ConfigResponse = {
EXCLUDED_PLATFORMS: Array<string>;
EXCLUDED_SINGLE_EXT: Array<string>;
@@ -11,6 +12,8 @@ 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>>;
EJS_DEBUG: boolean;
EJS_OPTIONS: Record<string, Record<string, string>>;
EJS_CONTROLS: Record<string, Record<string, Record<string, EjsControlsButton>>>;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type EjsControlsButton = {
value?: string;
value2?: string;
};

View File

@@ -1,6 +1,6 @@
import { defineStore } from "pinia";
import type { ConfigResponse } from "@/__generated__";
import type { ConfigResponse, EjsControlsButton } from "@/__generated__";
import api from "@/services/api";
type ExclusionTypes =
@@ -20,7 +20,9 @@ const defaultConfig = {
EXCLUDED_MULTI_PARTS_FILES: [],
PLATFORMS_BINDING: {},
PLATFORMS_VERSIONS: {},
EJS_CORE_OPTIONS: {},
EJS_DEBUG: false,
EJS_OPTIONS: {},
EJS_CONTROLS: {},
} as ConfigResponse;
export default defineStore("config", {
@@ -68,11 +70,21 @@ export default defineStore("config", {
return Object.keys(this.config).includes(type);
},
getEJSCoreOptions(core: string | null): Record<string, string | boolean> {
const defaultOptions = this.config.EJS_CORE_OPTIONS["default"];
const defaultOptions = this.config.EJS_OPTIONS["default"] || {};
if (!core) return defaultOptions;
return {
...defaultOptions,
...this.config.EJS_CORE_OPTIONS[core],
...this.config.EJS_OPTIONS[core],
};
},
getEJSControls(
core: string | null,
): Record<string, Record<string, EjsControlsButton>> {
const defaultControls = this.config.EJS_CONTROLS["default"] || {};
if (!core) return defaultControls;
return {
...defaultControls,
...this.config.EJS_CONTROLS[core],
};
},
reset() {},

View File

@@ -56,7 +56,6 @@ declare global {
EJS_player: string;
EJS_pathtodata: string;
EJS_color: string;
EJS_defaultOptions: object;
EJS_gameID: number;
EJS_gameName: string;
EJS_backgroundImage: string;
@@ -72,6 +71,8 @@ declare global {
EJS_fullscreenOnLoaded: boolean;
EJS_threads: boolean;
EJS_controlScheme: string | null;
EJS_defaultOptions: object;
EJS_defaultControls: object;
EJS_emulator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
EJS_language: string;
EJS_disableAutoLang: boolean;
@@ -124,13 +125,14 @@ window.EJS_defaultOptions = {
rewindEnabled: "enabled",
...coreOptions,
};
window.EJS_defaultControls = configStore.getEJSControls(props.core);
// 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;
window.EJS_DEBUG_XX = configStore.config.EJS_DEBUG;
onMounted(() => {
window.scrollTo(0, 0);