fix: keep both cases on lakka and retropie

Both run on Linux but inherited RetroArch's case folding, so a core
file differing from a platform name only by case left their packs:
MesenCE's bios.col and np2kai's lowercase 2608_*.wav, which those
cores open with that exact case. They are now case-sensitive, the
shared groups fold only where the platform does, and Lakka gets its
own pack instead of sharing RetroArch's.
This commit is contained in:
Abdessamad Derraz committed 2026-10-17 08:38:16 +02:00
1 parent 08b239f22a
commit 6e8f2df24f
8 files changed
+91 -42

No files matched your search

+5
View File
@@ -283,6 +283,9 @@ platforms:
scraper: libretro
inherits_from: retroarch
cores: all_libretro
# Lakka is a Linux distribution: two names that differ by case are two
# files there, not the one RetroArch's Windows and macOS builds see.
case_insensitive_fs: false
schedule: weekly
target_scraper: lakka_targets
target_source: https://buildbot.libretro.com/nightly/
@@ -386,6 +389,8 @@ platforms:
source: https://github.com/RetroPie/RetroPie-Setup
# No cores here: the registry overrides the config, and RetroPie's own
# scriptmodules are what say which packages it ships.
# RetroPie runs on Linux, where case tells two files apart.
case_insensitive_fs: false
schedule: null
target_scraper: retropie_targets
target_source: https://retropie.org.uk/stats/pkgflags/
+39 -29
View File
@@ -187,35 +187,6 @@ def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -
# Resolve shared group includes
shared_path = os.path.join(platforms_dir, "_shared.yml")
if os.path.exists(shared_path):
shared_real = os.path.realpath(shared_path)
if shared_real not in _shared_yml_cache:
with open(shared_path) as f:
_shared_yml_cache[shared_real] = yaml_load(f) or {}
shared = _shared_yml_cache[shared_real]
shared_groups = shared.get("shared_groups", {})
for system in config.get("systems", {}).values():
for group_name in system.get("includes", []):
if group_name in shared_groups:
existing = {
(f.get("name"), f.get("destination", f.get("name")))
for f in system.get("files", [])
}
existing_lower = {
f.get("destination", f.get("name", "")).lower()
for f in system.get("files", [])
}
for gf in shared_groups[group_name]:
key = (gf.get("name"), gf.get("destination", gf.get("name")))
dest_lower = gf.get("destination", gf.get("name", "")).lower()
if key not in existing and dest_lower not in existing_lower:
# Marked as ours: the platform's own file does not
# carry it, and an export must not count it kept.
system.setdefault("files", []).append(
{**gf, "shared_group": group_name}
)
existing.add(key)
# Merge metadata from _registry.yml. The registry is our curated source;
# the scraped YAML may be incomplete (missing cores, metadata fields).
# Registry fields supplement (not replace) the scraped config.
@@ -252,6 +223,42 @@ def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -
if key != "cores" and key not in config:
config[key] = val
# A shared group's file joins a system unless the platform already has
# it there. Case only makes two names one where the filesystem folds it:
# Lakka and RetroPie run on Linux, and np2kai opens 2608_bd.wav beside
# System.dat's 2608_BD.WAV.
def fold(destination: str) -> str:
return destination.lower() if config.get("case_insensitive_fs") else destination
if os.path.exists(shared_path):
shared_real = os.path.realpath(shared_path)
if shared_real not in _shared_yml_cache:
with open(shared_path) as f:
_shared_yml_cache[shared_real] = yaml_load(f) or {}
shared = _shared_yml_cache[shared_real]
shared_groups = shared.get("shared_groups", {})
for system in config.get("systems", {}).values():
for group_name in system.get("includes", []):
if group_name in shared_groups:
existing = {
(f.get("name"), f.get("destination", f.get("name")))
for f in system.get("files", [])
}
existing_lower = {
fold(f.get("destination", f.get("name", "")))
for f in system.get("files", [])
}
for gf in shared_groups[group_name]:
key = (gf.get("name"), gf.get("destination", gf.get("name")))
dest_lower = fold(gf.get("destination", gf.get("name", "")))
if key not in existing and dest_lower not in existing_lower:
# Marked as ours: the platform's own file does not
# carry it, and an export must not count it kept.
system.setdefault("files", []).append(
{**gf, "shared_group": group_name}
)
existing.add(key)
# Recalbox hashes a .zip by its members, so a plain md5 never answers one
# of its archive declarations: the resolver reads the mark per entry.
if config.get("zip_digest") == "composite":
@@ -1513,6 +1520,9 @@ def group_identical_platforms(
emulators = [
cores if isinstance(cores, str) else sorted(map(str, cores or [])),
sorted(map(str, config.get("standalone_cores") or [])),
# One archive cannot serve a filesystem that folds case and one
# that does not: Lakka keeps bios.col beside BIOS.col.
bool(config.get("case_insensitive_fs")),
]
fp = hashlib.sha1(f"{fp}|{emulators}".encode()).hexdigest()
if target_cores_cache:
+1 -1
View File
@@ -8,7 +8,7 @@ as stored, never recompressed, so the parts depend on the pack alone.
Usage:
python scripts/split_pack.py dist/
python scripts/split_pack.py dist/RetroArch_Lakka_v1.22.2_BIOS_Pack.zip
python scripts/split_pack.py dist/RetroArch_v1.22.2_BIOS_Pack.zip
python scripts/split_pack.py dist/ --max-size 1900M
"""
+30
View File
@@ -62,5 +62,35 @@ class AChildLeavesItsParentAlone(unittest.TestCase):
self.assertEqual(child["systems"]["sys-a"]["files"], [{"name": "child.bin"}])
class ALinuxPlatformKeepsBothCases(unittest.TestCase):
"""Lakka and RetroPie inherited RetroArch's case folding although both
run on Linux: np2kai opens 2608_bd.wav and MesenCE bios.col with that
exact case, and only System.dat's 2608_BD.WAV and BIOS.col shipped."""
def setUp(self):
self.addCleanup(common._platform_config_cache.clear)
common._platform_config_cache.clear()
def test_the_linux_platforms_do_not_fold(self):
platforms = str(REPO_ROOT / "platforms")
self.assertTrue(common.load_platform_config("retroarch", platforms)["case_insensitive_fs"])
for name in ("lakka", "retropie"):
with self.subTest(platform=name):
config = common.load_platform_config(name, platforms)
self.assertFalse(config["case_insensitive_fs"])
dests = {
f.get("destination", f.get("name"))
for system in config["systems"].values() for f in system["files"]
}
self.assertTrue({"np2kai/2608_BD.WAV", "np2kai/2608_bd.wav"} <= dests)
def test_lakka_does_not_share_retroarchs_pack(self):
groups = common.group_identical_platforms(
["retroarch", "lakka"], str(REPO_ROOT / "platforms")
)
self.assertEqual(sorted(g for g, _rep in groups), [["lakka"], ["retroarch"]])
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -39,7 +39,7 @@ class VerifyPackChoice(unittest.TestCase):
self.db = self.out / "db.json"
self.db.write_text(json.dumps({"files": {}}))
self.stem = generate_pack._platform_pack_stem(
["retroarch", "lakka"], "retroarch", "platforms"
["retroarch"], "retroarch", "platforms"
)
def _args(self, **overrides) -> argparse.Namespace:
+3 -3
View File
@@ -202,9 +202,9 @@ python scripts/generate_pack.py --all --all-variants --output-dir dist/
```
`--all-variants` builds all six combinations in one run, producing names like
`RetroArch_Lakka_v1.22.2_BIOS_Pack.zip`,
`RetroArch_Lakka_v1.22.2_Platform_BIOS_Pack.zip` and
`RetroArch_Lakka_v1.22.2_Truth_Required_BIOS_Pack.zip`.
`RetroArch_v1.22.2_BIOS_Pack.zip`,
`RetroArch_v1.22.2_Platform_BIOS_Pack.zip` and
`RetroArch_v1.22.2_Truth_Required_BIOS_Pack.zip`.
Which one to pick: `full` is the one to ship, since it covers alternate cores
and optional firmware. `platform` is much smaller and matches exactly what the
+7 -5
View File
@@ -147,11 +147,13 @@ entries that are out of scope here.
## Pack grouping
Platforms that produce identical packs are grouped automatically.
Two platforms share a pack when they declare the same files and run the same
emulators. RetroArch and Lakka do, so they produce one combined pack
(`RetroArch_Lakka_BIOS_Pack.zip`). RetroPie inherits RetroArch's systems but
ships its own package list, standalone emulators included, so it gets a
separate pack and a separate install manifest.
Two platforms share a pack when they declare the same files, run the same
emulators and see file names the same way. Lakka declares RetroArch's files
and cores but runs on Linux, where `bios.col` and `BIOS.col` are two files
that different cores open; RetroArch's pack folds them for Windows and macOS.
So Lakka gets its own pack. RetroPie inherits RetroArch's systems but ships its
own package list, standalone emulators included, and is case-sensitive too, so
it gets a separate pack and a separate install manifest.
With `--target`, the fingerprint includes target cores so platforms
with different hardware filters get separate packs. Emulator and system packs
also retain system identity, `variant_group` and requested region; same-named
+5 -3
View File
@@ -267,9 +267,11 @@ python scripts/download.py --list
bash scripts/download.sh --list
```
Some platforms share packs (Lakka uses the RetroArch pack). The installer handles
this mapping automatically, but if you're downloading manually, check which pack
name corresponds to your platform.
A pack's name lists every platform it serves. Releases built before
October 2026 ship RetroArch and Lakka as one `RetroArch_Lakka` pack; later
ones give Lakka its own, since Lakka runs on Linux where two names that differ
by case are two files. The installer and the download scripts find the right
pack either way.
**A pack in several parts will not extract:**