mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-12 13:03:54 -06:00
feat: record pcsx2's relative bios folder
configureINI writes [Folders] Bios = ../../../bios/ps2 into inis/PCSX2.ini, which PCSX2 and AetherSX2 resolve against the config folder configgen hands it. relative_folders reads the call, the ini path and the value, and records the resolved /userdata/bios/ps2 as a passes_path for pcsx2 and aethersx2.
This commit is contained in:
1 parent
cedeee2254
commit
f0289e3624
3 files changed
+146
-1
No files matched your search
@@ -5732,6 +5732,24 @@ launchers:
|
||||
depends_on_option: neocd_bios
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/libretro/libretroOptions.py:2558-2559,\
|
||||
\ 2566-2569"
|
||||
- emulator: pcsx2
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/ps2
|
||||
passes_to: "inis/PCSX2.ini [Folders] Bios = ../../../bios/ps2, relative to /userdata/system/configs/PCSX2"
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/pcsx2/pcsx2Generator.py:92,\
|
||||
\ 176, 213"
|
||||
- emulator: aethersx2
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: /userdata/bios/ps2
|
||||
passes_to: "inis/PCSX2.ini [Folders] Bios = ../../../bios/ps2, relative to /userdata/system/.config/aethersx2"
|
||||
fails_when_missing: false
|
||||
source_ref: "package/system/knulli-configgen/configgen/configgen/generators/aethersx2/aethersx2Generator.py:71,\
|
||||
\ 182, 219"
|
||||
- emulator: lexaloffle
|
||||
build: standalone
|
||||
kind: moves
|
||||
|
||||
@@ -20,6 +20,7 @@ LauncherShapeError, as every other extractor does.
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import posixpath
|
||||
import re
|
||||
from collections.abc import Callable, Iterator
|
||||
from dataclasses import dataclass, field
|
||||
@@ -834,8 +835,82 @@ def bios_sites(cg: Configgen, spec: GenericSites) -> list[Launcher]:
|
||||
return launchers
|
||||
|
||||
|
||||
# Generators that write a [Folders] path relative to the config folder they
|
||||
# are given. PCSX2 and AetherSX2 resolve those paths against their data root,
|
||||
# the folder holding inis/, which configureINI receives as config_directory.
|
||||
RELATIVE_FOLDERS = (
|
||||
("pcsx2/pcsx2Generator.py", "configureINI"),
|
||||
("aethersx2/aethersx2Generator.py", "configureINI"),
|
||||
)
|
||||
|
||||
|
||||
def relative_folders(
|
||||
cg: Configgen, specs: tuple[tuple[str, str], ...] = RELATIVE_FOLDERS
|
||||
) -> list[Launcher]:
|
||||
"""parser.set(section, key, "../<path>") resolved against config_directory."""
|
||||
launchers = []
|
||||
for path, function_name in specs:
|
||||
src = cg.source(f"{cg.generators}/{path}")
|
||||
reader = _Reader(cg, src)
|
||||
function = src.function(function_name)
|
||||
if not function.args.args:
|
||||
src.fail(f"{function_name} takes no config folder")
|
||||
folder = function.args.args[0].arg
|
||||
ini = [
|
||||
node for node in ast.walk(function)
|
||||
if isinstance(node, ast.Assign) and isinstance(node.value, ast.BinOp)
|
||||
and ast.unparse(node.value).startswith(f"{folder} /")
|
||||
and ast.unparse(node.value).endswith(".ini'")
|
||||
]
|
||||
if len(ini) != 1:
|
||||
src.fail(f"{function_name} builds no one ini path under {folder}")
|
||||
parts = sorted(
|
||||
(n for n in ast.walk(ini[0].value)
|
||||
if isinstance(n, ast.Constant) and isinstance(n.value, str)),
|
||||
key=lambda n: (n.lineno, n.col_offset),
|
||||
)
|
||||
ini_name = posixpath.join(*(str(n.value) for n in parts))
|
||||
calls = [
|
||||
node for node in ast.walk(src.tree)
|
||||
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
|
||||
and node.func.id == function_name and node.args
|
||||
]
|
||||
if len(calls) != 1:
|
||||
src.fail(f"expected one call of {function_name}, found {len(calls)}")
|
||||
base = reader.resolve(calls[0].args[0])
|
||||
if base is None or not base.startswith("/"):
|
||||
src.fail(f"{function_name} is given a folder that is not a path")
|
||||
found = 0
|
||||
for call in ast.walk(function):
|
||||
if not (
|
||||
isinstance(call, ast.Call) and isinstance(call.func, ast.Attribute)
|
||||
and call.func.attr == "set" and len(call.args) == 3
|
||||
and all(isinstance(a, ast.Constant) for a in call.args)
|
||||
):
|
||||
continue
|
||||
section, key, value = (str(a.value) for a in call.args) # type: ignore[attr-defined]
|
||||
if not value.startswith("../"):
|
||||
continue
|
||||
resolved = posixpath.normpath(posixpath.join(base, value))
|
||||
if not reader.under_bios(resolved):
|
||||
continue
|
||||
found += 1
|
||||
launchers.append(Launcher(
|
||||
emulator=cg.emulator(path.removesuffix(".py").replace("/", ".")),
|
||||
build="standalone", kind="passes_path", names=[], search_dir=resolved,
|
||||
passes_to=f"{ini_name} [{section}] {key} = {value}, relative to {base}",
|
||||
fails_when_missing=False,
|
||||
source_ref=src.ref([
|
||||
src.span(reader.statement_of(calls[0])), src.span(ini[0]), _lines(call),
|
||||
]),
|
||||
))
|
||||
if not found:
|
||||
src.fail(f"{function_name} writes no relative folder under {cg.bios}")
|
||||
return launchers
|
||||
|
||||
|
||||
EXTRACTORS: tuple[Callable[[Configgen], list[Launcher]], ...] = (
|
||||
*SHARED, lexaloffle, core_file_options,
|
||||
*SHARED, lexaloffle, core_file_options, relative_folders,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ IMPORTER = text(
|
||||
'openmsx': ('openmsx.openmsxGenerator', 'OpenmsxGenerator'),
|
||||
'hatari': ('hatari.hatariGenerator', 'HatariGenerator'),
|
||||
'melonds': ('melonds.melondsGenerator', 'MelonDSGenerator'),
|
||||
'pcsx2': ('pcsx2.pcsx2Generator', 'Pcsx2Generator'),
|
||||
}
|
||||
"""
|
||||
)
|
||||
@@ -305,6 +306,29 @@ MELONDS = text(
|
||||
)
|
||||
|
||||
|
||||
# pcsx2Generator.py: the default patches copied into BIOS when absent, and
|
||||
# the BIOS folder written relative to the config folder.
|
||||
PCSX2 = text(
|
||||
"""
|
||||
from ...batoceraPaths import BIOS, CONFIGS, DATAINIT_DIR
|
||||
|
||||
_PCSX2_CONFIG: Final = CONFIGS / "PCSX2"
|
||||
_PCSX2_BIOS: Final = BIOS / "ps2"
|
||||
|
||||
class Pcsx2Generator(Generator):
|
||||
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):
|
||||
pcsx2Patches = _PCSX2_BIOS / "patches.zip"
|
||||
configureINI(_PCSX2_CONFIG, _PCSX2_BIOS, system)
|
||||
if not pcsx2Patches.exists():
|
||||
shutil.copy(DATAINIT_DIR / "bios" / "ps2" / "patches.zip", pcsx2Patches)
|
||||
|
||||
def configureINI(config_directory, bios_directory, system):
|
||||
configFileName = config_directory / 'inis' / "PCSX2.ini"
|
||||
pcsx2INIConfig.set("Folders", "Bios", "../../../bios/ps2")
|
||||
pcsx2INIConfig.set("Folders", "Logs", "../../logs")
|
||||
"""
|
||||
)
|
||||
|
||||
def tree(**overrides: str) -> dict[str, str]:
|
||||
files = {
|
||||
f"{kl.CONFIGGEN}/batoceraPaths.py": PATHS,
|
||||
@@ -322,6 +346,7 @@ def tree(**overrides: str) -> dict[str, str]:
|
||||
f"{G}/libretro/libretroMAMEConfig.py": MAME_CONFIG,
|
||||
f"{G}/hatari/hatariGenerator.py": HATARI,
|
||||
f"{G}/melonds/melondsGenerator.py": MELONDS,
|
||||
f"{G}/pcsx2/pcsx2Generator.py": PCSX2,
|
||||
}
|
||||
files.update(overrides)
|
||||
return files
|
||||
@@ -498,6 +523,33 @@ class BiosSites(unittest.TestCase):
|
||||
kl.bios_sites(kl.configgen(files.__getitem__), spec)
|
||||
|
||||
|
||||
class CopiesAndFolders(unittest.TestCase):
|
||||
def test_a_copy_into_bios_reads_from_the_folder_it_comes_from(self):
|
||||
(record,) = sites("pcsx2/pcsx2Generator.py", "fixed_name")
|
||||
self.assertEqual(record["kind"], "copies")
|
||||
self.assertEqual(record["names"], ["patches.zip"])
|
||||
self.assertEqual(record["search_dir"], "/usr/share/knulli/datainit/bios/ps2")
|
||||
self.assertEqual(record["passes_to"], "/userdata/bios/ps2/patches.zip")
|
||||
self.assertEqual(record["depends_on_option"], "not pcsx2Patches.exists()")
|
||||
|
||||
def test_a_relative_folder_resolved_against_the_config_folder(self):
|
||||
(record,) = kl.relative_folders(configgen(), kl.RELATIVE_FOLDERS[:1])
|
||||
self.assertEqual(record.kind, "passes_path")
|
||||
self.assertEqual(record.search_dir, "/userdata/bios/ps2")
|
||||
self.assertEqual(
|
||||
record.passes_to,
|
||||
"inis/PCSX2.ini [Folders] Bios = ../../../bios/ps2, relative to /userdata/system/configs/PCSX2",
|
||||
)
|
||||
self.assertEqual(record.source_ref, f"{G}/pcsx2/pcsx2Generator.py:9, 14-15")
|
||||
|
||||
def test_a_folder_written_outside_bios_raises(self):
|
||||
broken = PCSX2.replace("../../../bios/ps2", "../../../saves/ps2")
|
||||
with self.assertRaises(LauncherShapeError):
|
||||
kl.relative_folders(
|
||||
configgen(**{f"{G}/pcsx2/pcsx2Generator.py": broken}), kl.RELATIVE_FOLDERS[:1]
|
||||
)
|
||||
|
||||
|
||||
class PlatformFile(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
|
||||
Reference in new issue
Block a user