fix: place files in a folder a launcher sets

KNULLI sets AetherSX2's [Folders] Bios to bios/ps2
(aethersx2Generator.py:219), yet its 112 PS2 BIOS files went to the
BIOS root. Three causes:

- a record's passes_to was split into words, so "[Folders] Bios"
  could not be told from "[Filenames] BIOS". ini_keys() adds the
  section-qualified key a profile names in standalone_directories.
- scans_dir records hand a folder too (ROCKNIX's bios/armsx2,
  EmuDeck's PCSX2 and ARMSX2) and were not read as handed.
- the agnostic scan looked its seeds up by the id of the profile's
  entries, while it walks the copies platform_layouts makes: any
  bound layout scanned nothing. Copies map back to the profile's
  entries by position.

An agnostic profile's entry the layout reads outside the BIOS tree
is now named by the gap report (outside_bios_folder) instead of
dropped: ARMSX2's arcade/proverb.zip on ROCKNIX and EmuDeck.
This commit is contained in:
Abdessamad Derraz committed 2026-11-10 11:46:57 +01:00
1 parent 62278e1d4e
commit ddc947c85d
4 files changed
+192 -10

No files matched your search

+18 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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.