From 8036d36fe8791a42e15046b876733fe5e1260016 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:44:33 +0200 Subject: [PATCH] fix: drop names DraStic never opens DraStic opens nds_bios_arm9.bin, nds_bios_arm7.bin and nds_firmware.bin and checks their size. Its nine aliases are in none of its binaries, and neither platform launcher passes them: ROCKNIX links the two BIOS names from its bios folder, and RetroPie runs DraStic from $md_conf_root/nds/drastic (drastic.sh:48), now a passes_path record. The collection answers DraStic's names with its DS dumps in generate_db, where repository names belong. --- emulators/drastic.yml | 3 -- platforms/retropie.yml | 9 ++++++ scripts/generate_db.py | 9 ++++-- scripts/scraper/retropie_launchers.py | 32 +++++++++++++++++++++ tests/test_db_aliases.py | 38 +++++++++++++++++++++++++ tests/test_launchers_retropie.py | 41 +++++++++++++++++++++++++++ wiki/adding-a-platform.md | 22 ++++++++++---- 7 files changed, 143 insertions(+), 11 deletions(-) diff --git a/emulators/drastic.yml b/emulators/drastic.yml index 3c489e8d..93465e8f 100644 --- a/emulators/drastic.yml +++ b/emulators/drastic.yml @@ -131,7 +131,6 @@ notes: | files: - name: nds_bios_arm9.bin - aliases: [NDS_Bios9.bin, bios9.bin, biosnds9.rom] path: system/nds_bios_arm9.bin system: nintendo-ds required: false @@ -154,7 +153,6 @@ files: source_ref: "drastic r2.5.2.2 initialize_memory 0x1c4b0-0x1c4d4, libdrastic_arm64.so 0x2b6f0-0x2b708, DraSticActivity.smali:4084-4090, drastic_bios_readme.txt:1-31" - name: nds_bios_arm7.bin - aliases: [NDS_Bios7.bin, bios7.bin, biosnds7.rom] path: system/nds_bios_arm7.bin system: nintendo-ds required: false @@ -177,7 +175,6 @@ files: source_ref: "drastic r2.5.2.2 initialize_memory 0x1c508-0x1c52c, libdrastic_arm64.so 0x2b740-0x2b758, DraSticActivity.smali:4078-4083" - name: nds_firmware.bin - aliases: [NDS_Lite_Firmware.bin, dsfirmware.bin, firmware.bin] path: system/nds_firmware.bin system: nintendo-ds required: false diff --git a/platforms/retropie.yml b/platforms/retropie.yml index b7849ea9..6909f888 100644 --- a/platforms/retropie.yml +++ b/platforms/retropie.yml @@ -506,6 +506,15 @@ launchers: kind: package names: [] source_ref: "scriptmodules/emulators/drastic.sh:12, 40" +- emulator: drastic + package: drastic + build: standalone + kind: passes_path + names: [] + search_dir: $md_conf_root/nds/drastic + passes_to: working directory of $md_inst/drastic.sh + fails_when_missing: false + source_ref: "scriptmodules/emulators/drastic.sh:48" - emulator: fbzx package: fbzx repo: "git https://github.com/rastersoft/fbzx :_get_branch_fbzx" diff --git a/scripts/generate_db.py b/scripts/generate_db.py index 855a6fd6..4eaddf74 100644 --- a/scripts/generate_db.py +++ b/scripts/generate_db.py @@ -614,9 +614,12 @@ def _collect_all_aliases(files: dict) -> dict: ["sgb2_bios.bin", "sgb2_boot.bin", "sgb2.boot.rom"], ["sgb1.program.rom", "SGB1.sfc/program.rom"], ["sgb2.program.rom", "SGB2.sfc/program.rom"], - # Nintendo DS - ["bios7.bin", "nds7.bin"], - ["bios9.bin", "nds9.bin"], + # Nintendo DS. DraStic opens nds_bios_arm9.bin, nds_bios_arm7.bin + # and nds_firmware.bin and checks their size only: the collection + # answers those names with its DS dumps. + ["NDS_Bios7.bin", "bios7.bin", "nds7.bin", "nds_bios_arm7.bin"], + ["NDS_Bios9.bin", "bios9.bin", "nds9.bin", "nds_bios_arm9.bin"], + ["NDS_Lite_Firmware.bin", "nds_firmware.bin"], ["dsi_sd_card.bin", "nds_sd_card.bin"], # MSX ["MSX.ROM", "MSX.rom", "Machines/Shared Roms/MSX.rom"], diff --git a/scripts/scraper/retropie_launchers.py b/scripts/scraper/retropie_launchers.py index 1d9e1df0..320ab841 100644 --- a/scripts/scraper/retropie_launchers.py +++ b/scripts/scraper/retropie_launchers.py @@ -48,6 +48,11 @@ OUTSIDE_BIOS: dict[str, tuple[str, ...]] = { "gngeopi": ("-B",), # gngeo -B/--biospath } +# Packages whose emulator reads its system files below the working directory +# the generated launch script enters with pushd, a folder outside $biosdir: +# DraStic opens system/ under it. Each must still pushd. +WORKING_DIRECTORY: tuple[str, ...] = ("drastic",) + _FUNCTION = re.compile(r"^function\s+([\w.+-]+)\s*\(\)\s*\{\s*$") _ASSIGN = re.compile(r"^(?:local\s+)?([A-Za-z_]\w*)=(.*)$") _DECLARE = re.compile(r"^local\s+([A-Za-z_]\w*(?:\s+[A-Za-z_]\w*)*)\s*$") @@ -518,6 +523,7 @@ class PackageReader: if links: self._links(links) self._outside_bios(events) + self._working_directory(events) return self.records @staticmethod @@ -821,6 +827,32 @@ class PackageReader: ) + def _working_directory(self, events: list[Event]) -> None: + """The folder a generated launch script enters before it runs the emulator.""" + if self.package.id not in WORKING_DIRECTORY: + return + found = False + for event in events: + if not event.heredoc or not event.words or event.words[0] != ["pushd"]: + continue + script = _redirect_target(event.opener) + if script is None or len(event.words) != 2 or len(event.words[1]) != 1: + raise LauncherPatternMissing( + f"{event.path}:{event.line}", f"pushd outside a launch script: {event.raw}" + ) + found = True + self.record( + "passes_path", [], [(event.path, event.line)], + search_dir=event.words[1][0].strip("\"'"), + passes_to=f"working directory of {script}", + fails_when_missing=False, depends_on_option=self._depends(event), + ) + if not found: + raise LauncherPatternMissing( + self.package.path, "the launch script no longer enters a working directory" + ) + + def _redirect_target(words: list[list[str]]) -> str | None: """The file a `cat > FILE << EOF` or `cat << EOF >> FILE` line writes.""" flat = [w[0] for w in words if w] diff --git a/tests/test_db_aliases.py b/tests/test_db_aliases.py index 3d2af69f..1ffe4fe2 100644 --- a/tests/test_db_aliases.py +++ b/tests/test_db_aliases.py @@ -153,5 +153,43 @@ class AnUnreadableSourceStopsTheRun(unittest.TestCase): self.assertIn("broken.yml", str(caught.exception)) +class DrasticNamesTheDSDumps(unittest.TestCase): + """DraStic opens nds_bios_arm9.bin, nds_bios_arm7.bin and nds_firmware.bin + and checks their size only. Its profile listed nine other names none of + its binaries contains, which were the only way the collection answered + its own three names.""" + + def _aliases(self, files: dict) -> dict: + with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp: + previous = os.getcwd() + os.chdir(tmp) + try: + Path("emulators").mkdir() + return generate_db._collect_all_aliases(files) + finally: + os.chdir(previous) + + def test_the_collection_answers_drastic_names(self): + files = { + "a9": {"name": "NDS_Bios9.bin", "path": "bios/Nintendo/DS/NDS_Bios9.bin", "md5": "m9"}, + "a7": {"name": "NDS_Bios7.bin", "path": "bios/Nintendo/DS/NDS_Bios7.bin", "md5": "m7"}, + "fw": {"name": "NDS_Lite_Firmware.bin", + "path": "bios/Nintendo/DS/NDS_Lite_Firmware.bin", "md5": "mf"}, + } + aliases = self._aliases(files) + named = {sha: {a["name"] for a in entries} for sha, entries in aliases.items()} + self.assertIn("nds_bios_arm9.bin", named["a9"]) + self.assertIn("nds_bios_arm7.bin", named["a7"]) + self.assertIn("nds_firmware.bin", named["fw"]) + + def test_the_profile_carries_no_name_drastic_never_opens(self): + import yaml + + profile = yaml.safe_load((REPO_ROOT / "emulators" / "drastic.yml").read_text()) + self.assertEqual( + [e["name"] for e in profile["files"] if e.get("aliases")], [], + ) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_launchers_retropie.py b/tests/test_launchers_retropie.py index a4280c73..56babfbf 100644 --- a/tests/test_launchers_retropie.py +++ b/tests/test_launchers_retropie.py @@ -269,6 +269,47 @@ class CommandLines(unittest.TestCase): rl.extract_launchers(files) +DRASTIC = module("drastic", """ + function configure_drastic() { + mkRomDir "nds" + + addEmulator 1 "$md_id" "nds" "$md_inst/drastic.sh %ROM%" + addSystem "nds" + + [[ "$md_mode" == "remove" ]] && return + + isPlatform "kms" && ! isPlatform "x11" && setBackend "$md_id" "x11" + + cat > "$md_inst/drastic.sh" << _EOF_ + #!/bin/bash + pushd "$md_conf_root/nds/drastic" + $md_inst/drastic "\\$1" + popd + _EOF_ + chmod +x "$md_inst/drastic.sh" + } +""", 36) + + +class WorkingDirectory(unittest.TestCase): + def test_drastic_reads_below_the_folder_its_script_enters(self): + found = records({f"{EMULATORS}/drastic.sh": DRASTIC}, "drastic") + entered = [r for r in found if r["kind"] == "passes_path"] + self.assertEqual( + [(r["search_dir"], r["passes_to"], r["source_ref"]) for r in entered], + [( + "$md_conf_root/nds/drastic", + "working directory of $md_inst/drastic.sh", + f"{EMULATORS}/drastic.sh:48", + )], + ) + + def test_a_launch_script_that_no_longer_enters_a_folder_raises(self): + files = {f"{EMULATORS}/drastic.sh": DRASTIC.replace('pushd "', 'echo "')} + with self.assertRaises(rl.LauncherPatternMissing): + rl.extract_launchers(files) + + class LaunchScripts(unittest.TestCase): def test_fs_uae_refuses_to_start_without_kick13(self): files = {f"{EMULATORS}/fs-uae.sh": FS_UAE, f"{EMULATORS}/fs-uae/fs-uae.sh": FS_UAE_LAUNCHER} diff --git a/wiki/adding-a-platform.md b/wiki/adding-a-platform.md index 166bac8f..d4d1349a 100644 --- a/wiki/adding-a-platform.md +++ b/wiki/adding-a-platform.md @@ -326,6 +326,10 @@ Each package also gets one `kind: package` record, and that record is what maps a package to the profile of the build it compiles: `repo` names the project (`minivmac` builds github.com/minivmac/minivmac, Mini vMac 37, not the libretro port), so the mapping never rests on stripping `lr-` from a name. +DraStic reads its BIOS below the folder its generated launch script enters +(`pushd "$md_conf_root/nds/drastic"`, `drastic.sh:48`), outside `$biosdir`: +that folder is a `passes_path` record, and the scrape stops if the script no +longer enters one. RetroDECK's records come from each component's `component_prepare.sh`, `component_launcher.sh` and `component_functions.sh`, and from the presets they copy, with `$bios_path` as the framework names it. In both, every line @@ -337,17 +341,25 @@ stops with `LauncherPatternMissing`. cross-reference both of them consume. A `names_searched` list is one place: the names are tried in order and one held file fills it, so a core file the launcher would never open once another name of the list is there is not -reported missing. A `fixed_name` or `core_option` name is a place of its own. +reported missing. A `fixed_name` or `core_option` name is a place of its own, +and so is a `links` name: ROCKNIX links `/storage/roms/bios/nds_bios_arm9.bin` +into DraStic's system folder, so that path is the one DraStic reads. Only a record that applies without condition settles the core's own name: a core option the user may never set, or a record tied to one system, leaves -the core's default in use elsewhere. +the core's default in use elsewhere. Settled, the core's own name is neither +reported nor packed, held or not: EmuDeck passes `Complex_4627v1.03.bin` to +xemu, which then never opens `Complex_4627.bin`. A place that no declared file and no packed core file fills receives, under the launcher's name, a file the core accepts: the file the collection knows under that name when the core takes its content, otherwise the copy held for the core entry read under that name. Records that only hand over a directory -(`passes_path`, `scans_dir`) or copy a file the platform already names -(`links`, `copies`) place nothing, and a `search_dir` the code describes -rather than names ("directory of the Chihiro BIOS found") is skipped. +(`passes_path`, `scans_dir`, a `links` record with no names) or copy a file +the platform already names (`copies`) place nothing, and a `search_dir` the +code describes rather than names ("directory of the Chihiro BIOS found") is +skipped. `$biosdir`, `${biosPath}` and `$bios_path` are the BIOS directory as +RetroPie, EmuDeck and RetroDECK spell it, and a path from a root +(`/userdata/bios`, `$HOME/RetroPie/BIOS/dc`) is placed by the platform's +`base_destination`. ### Inheritance