mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 12:33:53 -06:00
chore: merge the launcher folder placement
This commit is contained in:
commit
51e5fd0dbd
7 files changed
+216
-11
No files matched your search
@@ -22,6 +22,8 @@ systems:
|
||||
|
||||
bios_directory: "bios/"
|
||||
resources_directory: "resources/"
|
||||
standalone_directories:
|
||||
- "[Folders] Bios = bios"
|
||||
|
||||
data_root:
|
||||
android:
|
||||
@@ -53,7 +55,8 @@ notes: |
|
||||
A BIOS image is required and boot stops without one. It belongs in the bios
|
||||
folder of the data directory given in data_root, and the Folders section of
|
||||
PCSX2.ini names another path when set.
|
||||
ref: AetherSX2-release MacOS/AetherSX2, aethersx2 archive/desktop/mac/README.md
|
||||
ref: AetherSX2-release MacOS/AetherSX2, aethersx2 archive/desktop/mac/README.md,
|
||||
aethersx2/pcsx2/Pcsx2Config.cpp:935-946
|
||||
|
||||
Detection is by content. The scan lists the BIOS folder with a *.* mask, keeps
|
||||
files between 4 and 8 MB, and accepts an image whose romdir carries a RESET
|
||||
|
||||
@@ -24,6 +24,8 @@ systems:
|
||||
|
||||
bios_directory: "bios/"
|
||||
resources_directory: "resources/"
|
||||
standalone_directories:
|
||||
- "[Folders] Bios = bios"
|
||||
|
||||
data_root:
|
||||
android:
|
||||
|
||||
@@ -16,6 +16,8 @@ built_by: [retropie]
|
||||
systems:
|
||||
- nec-pc-88
|
||||
- nec-pc-80
|
||||
standalone_directories:
|
||||
- "-romdir = rom"
|
||||
|
||||
notes: |
|
||||
Continuation of Showzoh Fukunaga's QUASI88, version 0.7.1 at the pinned
|
||||
|
||||
+18
-3
@@ -312,8 +312,10 @@ def launcher_places(config: dict) -> list[LauncherPlace]:
|
||||
# component scripts $bios_path.
|
||||
BIOS_ROOT_VARIABLES = ("$biosdir", "$home/RetroPie/BIOS", "${biosPath}", "$bios_path")
|
||||
|
||||
# The kinds that hand an emulator a whole directory.
|
||||
HANDED_KINDS = ("links", "passes_path")
|
||||
# The kinds that hand an emulator a whole directory. A scans_dir record sets
|
||||
# the directory the emulator picks its file from: ROCKNIX writes ARMSX2's
|
||||
# [Folders] Bios, EmuDeck PCSX2's.
|
||||
HANDED_KINDS = ("links", "passes_path", "scans_dir")
|
||||
|
||||
# How a launcher record says the platform starts the emulator in a directory
|
||||
# (retropie_launchers._working_directory), and the handle a profile gives
|
||||
@@ -357,6 +359,19 @@ def normalize_handle(token: str) -> str:
|
||||
return _FLATPAK_DATA.sub("$XDG_DATA_HOME", token)
|
||||
|
||||
|
||||
_INI_KEY = re.compile(r"(\[[^\]\s]+\])\s+([^\s,;=()]+)")
|
||||
|
||||
|
||||
def ini_keys(passes_to: str) -> frozenset[str]:
|
||||
"""The INI keys a record's ``passes_to`` writes, as "[Section] Key".
|
||||
|
||||
A key alone is ambiguous: PCSX2 reads its BIOS folder from [Folders]
|
||||
Bios and the image name from [Filenames] BIOS. A profile declares the
|
||||
key it resolves a folder from in that spelling ("[Folders] Bios = bios").
|
||||
"""
|
||||
return frozenset(f"{section} {key}" for section, key in _INI_KEY.findall(passes_to))
|
||||
|
||||
|
||||
def _folder_in_bios(directory: str, base: str) -> str | None:
|
||||
"""A directory relative to the BIOS root, None when it lies outside."""
|
||||
relative = _bios_relative(directory, base)
|
||||
@@ -485,7 +500,7 @@ def handed_directories(config: dict) -> list[HandedDirectory]:
|
||||
handle
|
||||
for token in passes_to.split()
|
||||
if (handle := normalize_handle(token)) and handle != directory
|
||||
)
|
||||
) | ini_keys(passes_to)
|
||||
folder = _folder_in_bios(part, base)
|
||||
for emulator in (e.strip() for e in str(record.get("emulator", "")).split(",")):
|
||||
found.append(HandedDirectory(
|
||||
|
||||
+15
-4
@@ -567,7 +567,15 @@ def _agnostic_scan_extras(
|
||||
agnostic_votes.get(directory + "/", 0) + 1
|
||||
)
|
||||
|
||||
for profile, standalone in platform_layouts(emu_name, profile, standalone_set or set()):
|
||||
own = profile
|
||||
for profile, standalone in platform_layouts(emu_name, own, standalone_set or set()):
|
||||
# A layout copies each entry in order (a launcher's folder, a
|
||||
# system directory): its files are looked up as the profile's
|
||||
# own, or a bound layout scans nothing (KNULLI's bios/ps2).
|
||||
origin = {
|
||||
id(view): entry
|
||||
for view, entry in zip(profile.get("files", []), own.get("files", []))
|
||||
}
|
||||
for f in profile.get("files", []):
|
||||
if not is_profile_agnostic and not f.get("agnostic"):
|
||||
continue
|
||||
@@ -617,8 +625,8 @@ def _agnostic_scan_extras(
|
||||
companion_ext, image = _companion_of(profile, fname)
|
||||
|
||||
path_prefix = (
|
||||
resolved_dirs.get(id(image), "") if image
|
||||
else resolved_dirs.get(id(f), "")
|
||||
resolved_dirs.get(id(origin.get(id(image), image)), "") if image
|
||||
else resolved_dirs.get(id(origin.get(id(f), f)), "")
|
||||
)
|
||||
# A placeholder (<bios>.bin) resolves to nothing and anchors no
|
||||
# directory. The folders where the system's other profiles
|
||||
@@ -639,7 +647,10 @@ def _agnostic_scan_extras(
|
||||
# A name matching one file identifies it. A name matching several
|
||||
# identifies nothing on its own, so the profile's other files have
|
||||
# to agree on the directory before the scan walks it.
|
||||
ambiguous = id(f) in named_only and len(by_name.get(fname, [])) > 1
|
||||
ambiguous = (
|
||||
id(origin.get(id(f), f)) in named_only
|
||||
and len(by_name.get(fname, [])) > 1
|
||||
)
|
||||
if ambiguous and agnostic_votes.get(path_prefix, 0) < 2:
|
||||
continue
|
||||
|
||||
|
||||
+7
-3
@@ -564,15 +564,19 @@ def find_undeclared_files(
|
||||
if emu_name not in relevant:
|
||||
continue
|
||||
|
||||
# Skip agnostic profiles entirely (filename-agnostic BIOS detection)
|
||||
if profile.get("bios_mode") == "agnostic":
|
||||
continue
|
||||
# A filename-agnostic profile is served by the scan (packextras), all
|
||||
# but the entries its layout reads outside the BIOS tree: those are
|
||||
# named here, or a launcher folder drops them without a word
|
||||
# (ARMSX2's arcade/proverb.zip once ROCKNIX binds bios/armsx2).
|
||||
agnostic = profile.get("bios_mode") == "agnostic"
|
||||
|
||||
for profile, is_standalone in platform_layouts(emu_name, profile, standalone_set):
|
||||
|
||||
for f in expand_directory_entries(
|
||||
profile.get("files", []), db, is_standalone
|
||||
):
|
||||
if agnostic and not f.get("unplaced"):
|
||||
continue
|
||||
fname = f.get("name", "")
|
||||
# One destination rule for the dedup key and for the entry: a
|
||||
# standalone build without standalone_path falls back on path.
|
||||
|
||||
@@ -553,5 +553,173 @@ class EachProgramItsOwnProfile(unittest.TestCase):
|
||||
self.assertEqual(resolve_platform_cores(config, profiles), {"emu"})
|
||||
|
||||
|
||||
BIOS_SETTING = {
|
||||
"emulator": "ps2emu", "build": "standalone", "kind": "passes_path", "names": [],
|
||||
"search_dir": "/userdata/bios/ps2",
|
||||
"passes_to": "inis/PCSX2.ini [Folders] Bios = ../../../bios/ps2, relative to the data root",
|
||||
"source_ref": "ps2emuGenerator.py:219",
|
||||
}
|
||||
# The same folder through a scans_dir record, and a record setting the image
|
||||
# name alone, which hands no folder.
|
||||
BIOS_SCAN = {**BIOS_SETTING, "kind": "scans_dir", "passes_to": "PCSX2.ini [Folders] Bios"}
|
||||
BIOS_NAME = {**BIOS_SETTING, "passes_to": "PCSX2.ini [Filenames] BIOS"}
|
||||
|
||||
|
||||
class ASettingSetsTheBiosFolder(unittest.TestCase):
|
||||
"""KNULLI sets AetherSX2's [Folders] Bios to bios/ps2.
|
||||
|
||||
The profile reads its BIOS images by content from bios/ under its data
|
||||
root, and names that setting in standalone_directories. Every image the
|
||||
scan finds goes to ps2/ in the pack, the manifest and verify, and a file
|
||||
the layout keeps outside its bios/ folder is named, never packed.
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
_emulator_profiles_cache.clear()
|
||||
(REPO_ROOT / "tmp").mkdir(exist_ok=True)
|
||||
self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
|
||||
self.addCleanup(shutil.rmtree, self.root, True)
|
||||
files, by_md5, by_name = {}, {}, {}
|
||||
for folder, name, data in (
|
||||
("Sony/PlayStation 2", "img-a.bin", b"PS2 IMAGE A....."),
|
||||
("Sony/PlayStation 2", "img-b.bin", b"PS2 IMAGE B....."),
|
||||
("Other/ps2emu/arcade", "boot.zip", b"BOOT PROGRAMS"),
|
||||
):
|
||||
where = self.root / "bios" / folder
|
||||
where.mkdir(parents=True, exist_ok=True)
|
||||
(where / name).write_bytes(data)
|
||||
sha1 = hashlib.sha1(data).hexdigest()
|
||||
md5 = hashlib.md5(data).hexdigest()
|
||||
files[sha1] = {"path": str(where / name), "name": name, "sha1": sha1,
|
||||
"md5": md5, "crc32": "", "size": len(data)}
|
||||
by_md5[md5] = sha1
|
||||
by_name[name] = [sha1]
|
||||
self.db = {
|
||||
"files": files,
|
||||
"indexes": {"by_md5": by_md5, "by_name": by_name, "by_crc32": {},
|
||||
"by_path_suffix": {}},
|
||||
}
|
||||
self.platforms = self.root / "platforms"
|
||||
self.emulators = self.root / "emulators"
|
||||
self.platforms.mkdir()
|
||||
self.emulators.mkdir()
|
||||
platforms = {"setplat": [BIOS_SETTING], "scanplat": [BIOS_SCAN],
|
||||
"nameplat": [BIOS_NAME], "plainplat": []}
|
||||
for name, launchers in platforms.items():
|
||||
(self.platforms / f"{name}.yml").write_text(yaml.safe_dump({
|
||||
"platform": name, "version": "1", "verification_mode": "existence",
|
||||
"base_destination": "bios", "cores": ["ps2emu"],
|
||||
"standalone_cores": ["ps2emu"], "launchers": launchers,
|
||||
"systems": {"sony-playstation-2": {"files": []}},
|
||||
}))
|
||||
(self.platforms / "_registry.yml").write_text(yaml.safe_dump({"platforms": {
|
||||
name: {"config": f"{name}.yml"} for name in platforms
|
||||
}}))
|
||||
(self.emulators / "ps2emu.yml").write_text(yaml.safe_dump({
|
||||
"emulator": "PS2Emu", "type": "standalone", "bios_mode": "agnostic",
|
||||
"cores": ["ps2emu"], "systems": ["sony-playstation-2"],
|
||||
"bios_directory": "bios/",
|
||||
"standalone_directories": ["[Folders] Bios = bios"],
|
||||
"files": [
|
||||
{"name": "img-a.bin", "path": "bios/img-a.bin", "required": True,
|
||||
"min_size": 16, "max_size": 16, "validation": ["size"]},
|
||||
# Read below the data root, never below its bios/ folder.
|
||||
{"name": "boot.zip", "standalone_path": "arcade/boot.zip",
|
||||
"mode": "standalone", "required": False},
|
||||
],
|
||||
}))
|
||||
|
||||
def pack_names(self, platform: str) -> list[str]:
|
||||
with redirect_stdout(io.StringIO()):
|
||||
zip_path = generate_pack(
|
||||
platform, str(self.platforms), self.db, str(self.root / "bios"),
|
||||
str(self.root / "out" / platform), emulators_dir=str(self.emulators),
|
||||
offline=True,
|
||||
)
|
||||
with zipfile.ZipFile(zip_path) as zf:
|
||||
return sorted(n for n in zf.namelist() if n.endswith((".bin", ".zip")))
|
||||
|
||||
def manifest(self, platform: str) -> dict:
|
||||
with redirect_stdout(io.StringIO()):
|
||||
return generate_manifest(
|
||||
platform, str(self.platforms), self.db, str(self.root / "bios"),
|
||||
str(self.platforms / "_registry.yml"),
|
||||
emulators_dir=str(self.emulators), offline=True,
|
||||
)
|
||||
|
||||
def verify_dests(self, platform: str) -> list[str]:
|
||||
config = load_platform_config(platform, str(self.platforms))
|
||||
result = verify_platform(config, self.db, str(self.emulators))
|
||||
return sorted(d for d in result["pack_destinations"] if d.endswith((".bin", ".zip")))
|
||||
|
||||
def assert_placed(self, platform: str, expected: list[str]) -> None:
|
||||
self.assertEqual(self.pack_names(platform), expected)
|
||||
self.assertEqual(
|
||||
sorted(f["dest"] for f in self.manifest(platform)["files"]), expected
|
||||
)
|
||||
self.assertEqual(self.verify_dests(platform), expected)
|
||||
|
||||
def test_every_scanned_image_lands_in_the_folder_set(self) -> None:
|
||||
self.assert_placed("setplat", ["ps2/img-a.bin", "ps2/img-b.bin"])
|
||||
|
||||
def test_a_scans_dir_record_sets_it_too(self) -> None:
|
||||
self.assert_placed("scanplat", ["ps2/img-a.bin", "ps2/img-b.bin"])
|
||||
|
||||
def test_the_image_name_setting_hands_no_folder(self) -> None:
|
||||
self.assert_placed("nameplat", ["img-a.bin", "img-b.bin"])
|
||||
|
||||
def test_a_platform_without_the_record_keeps_the_layout(self) -> None:
|
||||
self.assert_placed("plainplat", ["img-a.bin", "img-b.bin"])
|
||||
|
||||
def test_a_file_outside_the_bios_folder_is_named(self) -> None:
|
||||
omitted = [o for o in self.manifest("setplat")["omitted_files"]
|
||||
if o["name"] == "boot.zip"]
|
||||
self.assertEqual(
|
||||
[(o["dest"], o["root"], o["reason"], o["cores"]) for o in omitted],
|
||||
[("arcade/boot.zip", "emulator", "outside_bios_folder", ["ps2emu"])],
|
||||
)
|
||||
|
||||
def test_the_ini_key_carries_its_section(self) -> None:
|
||||
from launchers import ini_keys
|
||||
|
||||
self.assertEqual(
|
||||
ini_keys("PCSX2.ini [Folders] Bios = /storage/roms/bios/armsx2, [Filenames] BIOS"),
|
||||
{"[Folders] Bios", "[Filenames] BIOS"},
|
||||
)
|
||||
self.assertEqual(ini_keys("command line -rompath"), frozenset())
|
||||
|
||||
|
||||
class AetherSX2OnKnulli(unittest.TestCase):
|
||||
"""The real profile against KNULLI's aethersx2Generator.py:219 record."""
|
||||
|
||||
def test_the_bios_images_land_in_ps2(self) -> None:
|
||||
profile = load_emulator_profiles(str(REPO_ROOT / "emulators"))["aethersx2"]
|
||||
config = load_platform_config("knulli", str(REPO_ROOT / "platforms"))
|
||||
(view, standalone), = platform_layouts("aethersx2", profile, standalone_set(config))
|
||||
self.assertTrue(standalone)
|
||||
for entry in view["files"]:
|
||||
path = entry.get("path") or ""
|
||||
if path.startswith("bios/"):
|
||||
self.assertEqual(entry["standalone_path"], "ps2/" + path.removeprefix("bios/"))
|
||||
elif path:
|
||||
self.assertEqual(entry.get("unplaced"), "outside_bios_folder", path)
|
||||
|
||||
|
||||
class Quasi88OnRetroPie(unittest.TestCase):
|
||||
"""RetroPie writes -romdir $biosdir/pc88 into quasi88.ini (quasi88.sh:67)."""
|
||||
|
||||
def test_the_roms_land_in_pc88(self) -> None:
|
||||
profile = load_emulator_profiles(str(REPO_ROOT / "emulators"))["quasi88kai"]
|
||||
config = load_platform_config("retropie", str(REPO_ROOT / "platforms"))
|
||||
(view, standalone), = platform_layouts("quasi88kai", profile, standalone_set(config))
|
||||
self.assertTrue(standalone)
|
||||
paths = [e["path"] for e in view["files"]]
|
||||
self.assertTrue(paths and all(p.startswith("rom/") for p in paths))
|
||||
self.assertEqual(
|
||||
[e["standalone_path"] for e in view["files"]],
|
||||
["pc88/" + p.removeprefix("rom/") for p in paths],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user