mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-13 12:22:33 -05:00
fix: correct core extras placement for retrodeck and romm packs
RetroDECK: core extras with subdirectory paths (e.g. vice/C64/,
fbneo/, dc/) were placed outside bios/ because the prefix was only
inferred for bare filenames. Add _detect_extras_prefix() to infer
the dominant BIOS prefix from YAML destinations.
RomM: core extras landed flat at bios/{file} instead of the required
bios/{platform_slug}/{file}. Add _detect_slug_structure() to detect
per-system slug layouts and _map_emulator_to_slug() to route each
extra to the correct slug subfolder.
Also skip manifest writes when only the generated timestamp changed,
preventing unnecessary diffs in install/*.json.
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"platform": "retrodeck",
|
"platform": "retrodeck",
|
||||||
"display_name": "RetroDECK",
|
"display_name": "RetroDECK",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"generated": "2026-03-31T21:01:54Z",
|
"generated": "2026-04-01T09:05:30Z",
|
||||||
"base_destination": "",
|
"base_destination": "",
|
||||||
"detect": [
|
"detect": [
|
||||||
{
|
{
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"standalone_copies": [],
|
"standalone_copies": [],
|
||||||
"total_files": 3139,
|
"total_files": 3127,
|
||||||
"total_size": 5886081009,
|
"total_size": 5865074692,
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"dest": "bios/panafz1.bin",
|
"dest": "bios/panafz1.bin",
|
||||||
@@ -16802,42 +16802,6 @@
|
|||||||
"Hatari"
|
"Hatari"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"dest": "SGB1.sfc/sgb1.boot.rom",
|
|
||||||
"sha1": "aa2f50a77dfb4823da96ba99309085a3c6278515",
|
|
||||||
"size": 256,
|
|
||||||
"repo_path": "bios/Nintendo/Game Boy/GB_sgb.bin",
|
|
||||||
"cores": [
|
|
||||||
"higan (SFC Accuracy)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dest": "SGB1.sfc/program.rom",
|
|
||||||
"sha1": "973e10840db683cf3faf61bd443090786b3a9f04",
|
|
||||||
"size": 262144,
|
|
||||||
"repo_path": "bios/Nintendo/Super Game Boy/SGB1.sfc/program.rom",
|
|
||||||
"cores": [
|
|
||||||
"higan (SFC Accuracy)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dest": "SGB2.sfc/sgb2.boot.rom",
|
|
||||||
"sha1": "93407ea10d2f30ab96a314d8eca44fe160aea734",
|
|
||||||
"size": 256,
|
|
||||||
"repo_path": "bios/Nintendo/Game Boy/GB_sgb2.bin",
|
|
||||||
"cores": [
|
|
||||||
"higan (SFC Accuracy)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dest": "SGB2.sfc/program.rom",
|
|
||||||
"sha1": "e5b2922ca137051059e4269b236d07a22c07bc84",
|
|
||||||
"size": 524288,
|
|
||||||
"repo_path": "bios/Nintendo/Super Game Boy/SGB2.sfc/program.rom",
|
|
||||||
"cores": [
|
|
||||||
"higan (SFC Accuracy)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"dest": "Wii/sd.raw",
|
"dest": "Wii/sd.raw",
|
||||||
"sha1": "8c8134f08b2e3baa603206ede30d3935365009b8",
|
"sha1": "8c8134f08b2e3baa603206ede30d3935365009b8",
|
||||||
@@ -22443,69 +22407,6 @@
|
|||||||
"FinalBurn Neo"
|
"FinalBurn Neo"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"dest": "dc/dc_boot.bin",
|
|
||||||
"sha1": "8951d1bb219ab2ff8583033d2119c899cc81f18c",
|
|
||||||
"size": 2097152,
|
|
||||||
"repo_path": "bios/Sega/Dreamcast/dc_bios.bin",
|
|
||||||
"cores": [
|
|
||||||
"Flycast"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dest": "dc/naomi_boot.bin",
|
|
||||||
"sha1": "6d27d71aec4dfba98f66316ae74a1426d567698a",
|
|
||||||
"size": 2097152,
|
|
||||||
"repo_path": "bios/Sega/Dreamcast/naomi_boot.bin",
|
|
||||||
"cores": [
|
|
||||||
"Flycast"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dest": "dc/naomi.zip",
|
|
||||||
"sha1": "788aee0f30ee80ea54dcd705afe93944accafc31",
|
|
||||||
"size": 9651827,
|
|
||||||
"repo_path": "bios/Arcade/Arcade/naomi.zip",
|
|
||||||
"cores": [
|
|
||||||
"Flycast"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dest": "dc/airlbios.zip",
|
|
||||||
"sha1": "03c9d1c3f59e8c6f320ea74abde1e4e7c5bfa623",
|
|
||||||
"size": 718362,
|
|
||||||
"repo_path": "bios/Arcade/MAME/airlbios.zip",
|
|
||||||
"cores": [
|
|
||||||
"Flycast"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dest": "dc/f355bios.zip",
|
|
||||||
"sha1": "b6ff66dcb5547bd91760d239ddf428a655631c53",
|
|
||||||
"size": 1394278,
|
|
||||||
"repo_path": "bios/Arcade/Arcade/f355bios.zip",
|
|
||||||
"cores": [
|
|
||||||
"Flycast"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dest": "dc/f355dlx.zip",
|
|
||||||
"sha1": "48d1712d1b1cdfeeeb43c6287c17b0b6309cfaab",
|
|
||||||
"size": 2328436,
|
|
||||||
"repo_path": "bios/Arcade/Arcade/f355dlx.zip",
|
|
||||||
"cores": [
|
|
||||||
"Flycast"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dest": "dc/hod2bios.zip",
|
|
||||||
"sha1": "07fd3fae7af650a37a3329ed09d039bd7360294f",
|
|
||||||
"size": 1889870,
|
|
||||||
"repo_path": "bios/Arcade/MAME/hod2bios.zip",
|
|
||||||
"cores": [
|
|
||||||
"Flycast"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"dest": "dc/naomigd.zip",
|
"dest": "dc/naomigd.zip",
|
||||||
"sha1": "a0f07de6070d98f86d55a4ecd61b4a5b05a4a0d5",
|
"sha1": "a0f07de6070d98f86d55a4ecd61b4a5b05a4a0d5",
|
||||||
@@ -22515,15 +22416,6 @@
|
|||||||
"Flycast"
|
"Flycast"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"dest": "dc/awbios.zip",
|
|
||||||
"sha1": "7940c7bf29eee85a5b2fdec78750b19aa22895dc",
|
|
||||||
"size": 42296,
|
|
||||||
"repo_path": "bios/Arcade/Arcade/awbios.zip",
|
|
||||||
"cores": [
|
|
||||||
"Flycast"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"dest": "kronos/saturn_bios.bin",
|
"dest": "kronos/saturn_bios.bin",
|
||||||
"sha1": "2b8cb4f87580683eb4d760e4ed210813d667f0a2",
|
"sha1": "2b8cb4f87580683eb4d760e4ed210813d667f0a2",
|
||||||
|
|||||||
2865
install/romm.json
2865
install/romm.json
File diff suppressed because it is too large
Load Diff
@@ -311,6 +311,109 @@ def download_external(file_entry: dict, dest_path: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_extras_prefix(config: dict, base_dest: str) -> str:
|
||||||
|
"""Detect the effective BIOS prefix for core extras.
|
||||||
|
|
||||||
|
When base_destination is empty (RetroDECK), infer the prefix from
|
||||||
|
the dominant root of YAML-declared destinations. Returns the prefix
|
||||||
|
to prepend to every core-extra destination (may be empty).
|
||||||
|
"""
|
||||||
|
if base_dest:
|
||||||
|
return base_dest
|
||||||
|
dests: list[str] = []
|
||||||
|
for sys_data in config.get("systems", {}).values():
|
||||||
|
for f in sys_data.get("files", []):
|
||||||
|
d = f.get("destination", "")
|
||||||
|
if d and "/" in d:
|
||||||
|
dests.append(d)
|
||||||
|
if not dests:
|
||||||
|
return ""
|
||||||
|
from collections import Counter
|
||||||
|
roots = Counter(d.split("/", 1)[0] for d in dests)
|
||||||
|
most_common, count = roots.most_common(1)[0]
|
||||||
|
if count / len(dests) > 0.9:
|
||||||
|
return most_common
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_slug_structure(config: dict) -> tuple[bool, dict[str, str]]:
|
||||||
|
"""Detect whether a platform uses per-system slug destinations.
|
||||||
|
|
||||||
|
Returns ``(is_slug_based, system_to_slug)`` where ``system_to_slug``
|
||||||
|
maps system IDs to their destination slug prefix. Slug-based means
|
||||||
|
each system's files live under a per-system subfolder (e.g. RomM's
|
||||||
|
``bios/{platform_slug}/{file}``), with varying slugs across systems.
|
||||||
|
|
||||||
|
Only returns True when nearly ALL destinations have a subfolder and
|
||||||
|
nearly ALL systems map to a consistent slug, distinguishing true
|
||||||
|
slug-based layouts (RomM) from platforms that happen to have some
|
||||||
|
subfoldered files (RetroArch ``dc/``, ``neocd/``).
|
||||||
|
"""
|
||||||
|
total_files = 0
|
||||||
|
files_with_slash = 0
|
||||||
|
sys_to_slug: dict[str, str] = {}
|
||||||
|
total_systems_with_files = 0
|
||||||
|
for sys_id, sys_data in config.get("systems", {}).items():
|
||||||
|
files = sys_data.get("files", [])
|
||||||
|
if not files:
|
||||||
|
continue
|
||||||
|
total_systems_with_files += 1
|
||||||
|
slugs: set[str] = set()
|
||||||
|
for f in files:
|
||||||
|
d = f.get("destination", "")
|
||||||
|
if d:
|
||||||
|
total_files += 1
|
||||||
|
if "/" in d:
|
||||||
|
files_with_slash += 1
|
||||||
|
slugs.add(d.split("/", 1)[0])
|
||||||
|
if len(slugs) == 1:
|
||||||
|
sys_to_slug[sys_id] = slugs.pop()
|
||||||
|
|
||||||
|
if not sys_to_slug or total_files == 0:
|
||||||
|
return False, {}
|
||||||
|
# All conditions must hold for slug-based detection:
|
||||||
|
# 1. Nearly all files have a subfolder
|
||||||
|
# 2. Multiple distinct slugs (not a constant prefix)
|
||||||
|
# 3. Nearly all systems with files map to a slug
|
||||||
|
# 4. Files are exactly slug/filename (depth 2), not deeper
|
||||||
|
unique_slugs = set(sys_to_slug.values())
|
||||||
|
all_have_slash = files_with_slash / total_files > 0.95
|
||||||
|
varying_slugs = len(unique_slugs) > 1
|
||||||
|
high_coverage = len(sys_to_slug) / total_systems_with_files > 0.9
|
||||||
|
# Count files deeper than slug/filename (e.g., amiga/bios/kick.rom)
|
||||||
|
deep_files = 0
|
||||||
|
for sys_data in config.get("systems", {}).values():
|
||||||
|
for f in sys_data.get("files", []):
|
||||||
|
d = f.get("destination", "")
|
||||||
|
if d and d.count("/") > 1:
|
||||||
|
deep_files += 1
|
||||||
|
shallow = deep_files / total_files < 0.05 if total_files else True
|
||||||
|
return (all_have_slash and varying_slugs and high_coverage
|
||||||
|
and shallow), sys_to_slug
|
||||||
|
|
||||||
|
|
||||||
|
def _map_emulator_to_slug(
|
||||||
|
profile: dict,
|
||||||
|
platform_systems: set[str], norm_map: dict[str, str],
|
||||||
|
sys_to_slug: dict[str, str],
|
||||||
|
) -> str:
|
||||||
|
"""Map an emulator to a destination slug for slug-based platforms."""
|
||||||
|
from common import _norm_system_id
|
||||||
|
emu_systems = set(profile.get("systems", []))
|
||||||
|
# Direct match
|
||||||
|
direct = emu_systems & platform_systems
|
||||||
|
if direct:
|
||||||
|
target = sorted(direct)[0]
|
||||||
|
return sys_to_slug.get(target, "")
|
||||||
|
# Normalized match
|
||||||
|
for es in sorted(emu_systems):
|
||||||
|
norm = _norm_system_id(es)
|
||||||
|
if norm in norm_map:
|
||||||
|
target = norm_map[norm]
|
||||||
|
return sys_to_slug.get(target, "")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _collect_emulator_extras(
|
def _collect_emulator_extras(
|
||||||
config: dict,
|
config: dict,
|
||||||
emulators_dir: str,
|
emulators_dir: str,
|
||||||
@@ -334,11 +437,20 @@ def _collect_emulator_extras(
|
|||||||
|
|
||||||
Works for ANY platform (RetroArch, Batocera, Recalbox, etc.)
|
Works for ANY platform (RetroArch, Batocera, Recalbox, etc.)
|
||||||
"""
|
"""
|
||||||
from common import resolve_platform_cores
|
from common import resolve_platform_cores, _norm_system_id
|
||||||
from verify import find_undeclared_files
|
from verify import find_undeclared_files
|
||||||
|
|
||||||
profiles = emu_profiles if emu_profiles is not None else load_emulator_profiles(emulators_dir)
|
profiles = emu_profiles if emu_profiles is not None else load_emulator_profiles(emulators_dir)
|
||||||
|
|
||||||
|
# Detect destination conventions for core extras
|
||||||
|
extras_prefix = _detect_extras_prefix(config, base_dest)
|
||||||
|
is_slug_based, sys_to_slug = _detect_slug_structure(config)
|
||||||
|
platform_systems = set(config.get("systems", {}).keys())
|
||||||
|
norm_map: dict[str, str] = {}
|
||||||
|
if is_slug_based:
|
||||||
|
for sid in platform_systems:
|
||||||
|
norm_map[_norm_system_id(sid)] = sid
|
||||||
|
|
||||||
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles, target_cores=target_cores)
|
undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles, target_cores=target_cores)
|
||||||
extras = []
|
extras = []
|
||||||
seen_dests: set[str] = set(seen)
|
seen_dests: set[str] = set(seen)
|
||||||
@@ -351,7 +463,25 @@ def _collect_emulator_extras(
|
|||||||
raw_dest = archive if archive else (u.get("path") or u["name"])
|
raw_dest = archive if archive else (u.get("path") or u["name"])
|
||||||
# Directory path: append filename (e.g. "cafeLibs/" + "snd_user.rpl")
|
# Directory path: append filename (e.g. "cafeLibs/" + "snd_user.rpl")
|
||||||
dest = f"{raw_dest}{u['name']}" if raw_dest.endswith("/") else raw_dest
|
dest = f"{raw_dest}{u['name']}" if raw_dest.endswith("/") else raw_dest
|
||||||
full_dest = f"{base_dest}/{dest}" if base_dest else dest
|
|
||||||
|
# Slug-based platforms: prefix dest with system slug
|
||||||
|
if is_slug_based:
|
||||||
|
emu_name = u.get("emulator", "")
|
||||||
|
profile = profiles.get(emu_name, {})
|
||||||
|
# Try finding profile by display name if key lookup failed
|
||||||
|
if not profile:
|
||||||
|
for pn, pp in profiles.items():
|
||||||
|
if pp.get("emulator") == emu_name:
|
||||||
|
profile = pp
|
||||||
|
break
|
||||||
|
slug = _map_emulator_to_slug(
|
||||||
|
profile, platform_systems, norm_map, sys_to_slug,
|
||||||
|
)
|
||||||
|
if not slug:
|
||||||
|
continue # can't place without slug
|
||||||
|
dest = f"{slug}/{dest}"
|
||||||
|
|
||||||
|
full_dest = f"{extras_prefix}/{dest}" if extras_prefix else dest
|
||||||
if full_dest in seen_dests:
|
if full_dest in seen_dests:
|
||||||
continue
|
continue
|
||||||
seen_dests.add(full_dest)
|
seen_dests.add(full_dest)
|
||||||
@@ -368,6 +498,12 @@ def _collect_emulator_extras(
|
|||||||
# different path by another core (e.g. neocd/ vs root, same_cdi/bios/ vs root).
|
# different path by another core (e.g. neocd/ vs root, same_cdi/bios/ vs root).
|
||||||
# Only adds a copy when the file is ALREADY covered at a different path -
|
# Only adds a copy when the file is ALREADY covered at a different path -
|
||||||
# never introduces a file that wasn't selected by the first pass.
|
# never introduces a file that wasn't selected by the first pass.
|
||||||
|
#
|
||||||
|
# Skip for slug-based platforms (RomM): alternative paths don't map to
|
||||||
|
# the required {platform_slug}/{file} structure.
|
||||||
|
if is_slug_based:
|
||||||
|
return extras
|
||||||
|
|
||||||
relevant = resolve_platform_cores(config, profiles, target_cores=target_cores)
|
relevant = resolve_platform_cores(config, profiles, target_cores=target_cores)
|
||||||
standalone_set = {str(c) for c in config.get("standalone_cores", [])}
|
standalone_set = {str(c) for c in config.get("standalone_cores", [])}
|
||||||
by_name = db.get("indexes", {}).get("by_name", {})
|
by_name = db.get("indexes", {}).get("by_name", {})
|
||||||
@@ -410,7 +546,7 @@ def _collect_emulator_extras(
|
|||||||
dest = f"{raw}{fname}" if raw.endswith("/") else raw
|
dest = f"{raw}{fname}" if raw.endswith("/") else raw
|
||||||
if dest == fname:
|
if dest == fname:
|
||||||
continue # no alternative destination
|
continue # no alternative destination
|
||||||
full_dest = f"{base_dest}/{dest}" if base_dest else dest
|
full_dest = f"{extras_prefix}/{dest}" if extras_prefix else dest
|
||||||
if full_dest in seen_dests:
|
if full_dest in seen_dests:
|
||||||
continue
|
continue
|
||||||
# Check file exists in repo or data dirs
|
# Check file exists in repo or data dirs
|
||||||
@@ -447,7 +583,7 @@ def _collect_emulator_extras(
|
|||||||
if archive_name not in covered_names:
|
if archive_name not in covered_names:
|
||||||
continue
|
continue
|
||||||
dest = f"{prefix}/{archive_name}"
|
dest = f"{prefix}/{archive_name}"
|
||||||
full_dest = f"{base_dest}/{dest}" if base_dest else dest
|
full_dest = f"{extras_prefix}/{dest}" if extras_prefix else dest
|
||||||
if full_dest in seen_dests:
|
if full_dest in seen_dests:
|
||||||
continue
|
continue
|
||||||
if not by_name.get(archive_name):
|
if not by_name.get(archive_name):
|
||||||
@@ -533,7 +669,7 @@ def _collect_emulator_extras(
|
|||||||
if not scan_name:
|
if not scan_name:
|
||||||
continue
|
continue
|
||||||
dest = scan_name
|
dest = scan_name
|
||||||
full_dest = f"{base_dest}/{dest}" if base_dest else dest
|
full_dest = f"{extras_prefix}/{dest}" if extras_prefix else dest
|
||||||
if full_dest in seen_dests:
|
if full_dest in seen_dests:
|
||||||
continue
|
continue
|
||||||
seen_dests.add(full_dest)
|
seen_dests.add(full_dest)
|
||||||
@@ -662,9 +798,11 @@ def _build_readme(platform_name: str, platform_display: str,
|
|||||||
" ----------------\n"
|
" ----------------\n"
|
||||||
" 1. Open Dolphin file manager\n"
|
" 1. Open Dolphin file manager\n"
|
||||||
" 2. Show hidden files (Ctrl+H)\n"
|
" 2. Show hidden files (Ctrl+H)\n"
|
||||||
" 3. Navigate to ~/retrodeck/bios/\n"
|
" 3. Navigate to ~/retrodeck/\n"
|
||||||
" 4. Open this archive and go into the top-level folder\n"
|
" 4. Open the \"bios\" folder from this archive\n"
|
||||||
" 5. Copy ALL contents into ~/retrodeck/bios/\n\n"
|
" 5. Copy ALL contents into ~/retrodeck/bios/\n"
|
||||||
|
" 6. If the archive contains a \"roms\" folder, copy\n"
|
||||||
|
" its contents into ~/retrodeck/roms/\n\n"
|
||||||
" NOTE: RetroDECK uses its own BIOS checker. After\n"
|
" NOTE: RetroDECK uses its own BIOS checker. After\n"
|
||||||
" copying, open RetroDECK > Tools > BIOS Checker to\n"
|
" copying, open RetroDECK > Tools > BIOS Checker to\n"
|
||||||
" verify everything is detected.\n\n"
|
" verify everything is detected.\n\n"
|
||||||
@@ -1086,14 +1224,16 @@ def generate_pack(
|
|||||||
dest = _sanitize_path(fe.get("destination", fe["name"]))
|
dest = _sanitize_path(fe.get("destination", fe["name"]))
|
||||||
if not dest:
|
if not dest:
|
||||||
continue
|
continue
|
||||||
# Core extras use flat filenames; prepend base_destination or
|
# Core extras: _collect_emulator_extras already adjusted
|
||||||
# default to the platform's most common BIOS path prefix
|
# destinations for slug-based platforms. Apply the effective
|
||||||
if base_dest:
|
# prefix (base_dest, or inferred from YAML when base_dest is
|
||||||
full_dest = f"{base_dest}/{dest}"
|
# empty — e.g. RetroDECK infers "bios").
|
||||||
elif "/" not in dest:
|
extras_pfx = _detect_extras_prefix(config, base_dest)
|
||||||
# Bare filename with empty base_destination -infer bios/ prefix
|
if extras_pfx:
|
||||||
# to match platform conventions (RetroDECK: ~/retrodeck/bios/)
|
if not dest.startswith(f"{extras_pfx}/"):
|
||||||
full_dest = f"bios/{dest}"
|
full_dest = f"{extras_pfx}/{dest}"
|
||||||
|
else:
|
||||||
|
full_dest = dest
|
||||||
else:
|
else:
|
||||||
full_dest = dest
|
full_dest = dest
|
||||||
if full_dest in seen_destinations:
|
if full_dest in seen_destinations:
|
||||||
@@ -1867,6 +2007,25 @@ def _validate_args(args, parser):
|
|||||||
parser.error("--manifest is incompatible with --split")
|
parser.error("--manifest is incompatible with --split")
|
||||||
|
|
||||||
|
|
||||||
|
def _write_manifest_if_changed(path: str, manifest: dict) -> None:
|
||||||
|
"""Write manifest JSON only if content (excluding timestamp) changed."""
|
||||||
|
new_json = json.dumps(manifest, indent=2)
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path) as f:
|
||||||
|
try:
|
||||||
|
old = json.load(f)
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
old = None
|
||||||
|
if old is not None:
|
||||||
|
# Compare everything except the generated timestamp
|
||||||
|
old_cmp = {k: v for k, v in old.items() if k != "generated"}
|
||||||
|
new_cmp = {k: v for k, v in manifest.items() if k != "generated"}
|
||||||
|
if old_cmp == new_cmp:
|
||||||
|
return # no content change, keep existing timestamp
|
||||||
|
with open(path, "w") as f:
|
||||||
|
f.write(new_json)
|
||||||
|
|
||||||
|
|
||||||
def _run_manifest_mode(args, groups, db, zip_contents, emu_profiles, target_cores_cache):
|
def _run_manifest_mode(args, groups, db, zip_contents, emu_profiles, target_cores_cache):
|
||||||
"""Generate JSON manifests instead of ZIP packs."""
|
"""Generate JSON manifests instead of ZIP packs."""
|
||||||
registry_path = os.path.join(args.platforms_dir, "_registry.yml")
|
registry_path = os.path.join(args.platforms_dir, "_registry.yml")
|
||||||
@@ -1886,8 +2045,7 @@ def _run_manifest_mode(args, groups, db, zip_contents, emu_profiles, target_core
|
|||||||
target_cores=tc,
|
target_cores=tc,
|
||||||
)
|
)
|
||||||
out_path = os.path.join(args.output_dir, f"{representative}.json")
|
out_path = os.path.join(args.output_dir, f"{representative}.json")
|
||||||
with open(out_path, "w") as f:
|
_write_manifest_if_changed(out_path, manifest)
|
||||||
json.dump(manifest, f, indent=2)
|
|
||||||
print(f" {out_path}: {manifest['total_files']} files, "
|
print(f" {out_path}: {manifest['total_files']} files, "
|
||||||
f"{manifest['total_size']} bytes")
|
f"{manifest['total_size']} bytes")
|
||||||
# Create aliases for grouped platforms (e.g., lakka -> retroarch)
|
# Create aliases for grouped platforms (e.g., lakka -> retroarch)
|
||||||
@@ -1902,8 +2060,7 @@ def _run_manifest_mode(args, groups, db, zip_contents, emu_profiles, target_core
|
|||||||
alias_install = alias_registry.get("install", {})
|
alias_install = alias_registry.get("install", {})
|
||||||
alias_manifest["detect"] = alias_install.get("detect", [])
|
alias_manifest["detect"] = alias_install.get("detect", [])
|
||||||
alias_manifest["standalone_copies"] = alias_install.get("standalone_copies", [])
|
alias_manifest["standalone_copies"] = alias_install.get("standalone_copies", [])
|
||||||
with open(alias_path, "w") as f:
|
_write_manifest_if_changed(alias_path, alias_manifest)
|
||||||
json.dump(alias_manifest, f, indent=2)
|
|
||||||
print(f" {alias_path}: alias of {representative}")
|
print(f" {alias_path}: alias of {representative}")
|
||||||
except (FileNotFoundError, OSError, yaml.YAMLError) as e:
|
except (FileNotFoundError, OSError, yaml.YAMLError) as e:
|
||||||
print(f" ERROR: {e}")
|
print(f" ERROR: {e}")
|
||||||
@@ -2290,14 +2447,16 @@ def generate_manifest(
|
|||||||
config, emulators_dir, db,
|
config, emulators_dir, db,
|
||||||
seen_destinations, base_dest, emu_profiles, target_cores=target_cores,
|
seen_destinations, base_dest, emu_profiles, target_cores=target_cores,
|
||||||
)
|
)
|
||||||
|
extras_pfx = _detect_extras_prefix(config, base_dest)
|
||||||
for fe in core_files:
|
for fe in core_files:
|
||||||
dest = _sanitize_path(fe.get("destination", fe["name"]))
|
dest = _sanitize_path(fe.get("destination", fe["name"]))
|
||||||
if not dest:
|
if not dest:
|
||||||
continue
|
continue
|
||||||
if base_dest:
|
if extras_pfx:
|
||||||
full_dest = f"{base_dest}/{dest}"
|
if not dest.startswith(f"{extras_pfx}/"):
|
||||||
elif "/" not in dest:
|
full_dest = f"{extras_pfx}/{dest}"
|
||||||
full_dest = f"bios/{dest}"
|
else:
|
||||||
|
full_dest = dest
|
||||||
else:
|
else:
|
||||||
full_dest = dest
|
full_dest = dest
|
||||||
|
|
||||||
@@ -2658,15 +2817,17 @@ def verify_pack_against_platform(
|
|||||||
parts = n.split("/")
|
parts = n.split("/")
|
||||||
for i in range(1, len(parts)):
|
for i in range(1, len(parts)):
|
||||||
seen_parents.add("/".join(parts[:i]))
|
seen_parents.add("/".join(parts[:i]))
|
||||||
|
extras_pfx = _detect_extras_prefix(config, base_dest)
|
||||||
for u in undeclared:
|
for u in undeclared:
|
||||||
if not u["in_repo"]:
|
if not u["in_repo"]:
|
||||||
continue
|
continue
|
||||||
raw_dest = u.get("path") or u["name"]
|
raw_dest = u.get("path") or u["name"]
|
||||||
dest = f"{raw_dest}{u['name']}" if raw_dest.endswith("/") else raw_dest
|
dest = f"{raw_dest}{u['name']}" if raw_dest.endswith("/") else raw_dest
|
||||||
if base_dest:
|
if extras_pfx:
|
||||||
full = f"{base_dest}/{dest}"
|
if not dest.startswith(f"{extras_pfx}/"):
|
||||||
elif "/" not in dest:
|
full = f"{extras_pfx}/{dest}"
|
||||||
full = f"bios/{dest}"
|
else:
|
||||||
|
full = dest
|
||||||
else:
|
else:
|
||||||
full = dest
|
full = dest
|
||||||
# Skip path conflicts (same logic as pack builder)
|
# Skip path conflicts (same logic as pack builder)
|
||||||
|
|||||||
Reference in New Issue
Block a user