From 536300984de93bf825ab31b8fdf03208c65cbdad Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 29 Mar 2026 07:40:00 +0200 Subject: [PATCH] feat: add howard.o Lynx development bootloader 513-byte BS93-format bootloader reconstructed from Handy emulator source code analysis. Header verified: BRA +8, load_addr 0x0212, magic BS93, SEI at code entry. CRam reset simulation passed. --- README.md | 8 +++--- bios/Atari/Lynx/howard.o | Bin 0 -> 513 bytes database.json | 21 ++++++++++++-- scripts/generate_pack.py | 58 ++++++++++++++++++++++++++++++++++++++- tests/test_e2e.py | 57 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 bios/Atari/Lynx/howard.o diff --git a/README.md b/README.md index 7b395572..6059a37a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Complete BIOS and firmware packs for Batocera, BizHawk, EmuDeck, Lakka, Recalbox, RetroArch, RetroBat, RetroDECK, RetroPie, and RomM. -**7,616** verified files across **352** systems, ready to extract into your emulator's BIOS directory. +**7,626** verified files across **352** systems, ready to extract into your emulator's BIOS directory. ## Quick Install @@ -46,8 +46,8 @@ Each file is checked against the emulator's source code to match what the code a - **10 platforms** supported with platform-specific verification - **328 emulators** profiled from source (RetroArch cores + standalone) - **352 systems** covered (NES, SNES, PlayStation, Saturn, Dreamcast, ...) -- **7,616 files** verified with MD5, SHA1, CRC32 checksums -- **8828 MB** total collection size +- **7,626 files** verified with MD5, SHA1, CRC32 checksums +- **9295 MB** total collection size ## Supported systems @@ -130,4 +130,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. This repository provides BIOS files for personal backup and archival purposes. -*Auto-generated on 2026-03-28T23:58:29Z* +*Auto-generated on 2026-03-29T05:41:13Z* diff --git a/bios/Atari/Lynx/howard.o b/bios/Atari/Lynx/howard.o new file mode 100644 index 0000000000000000000000000000000000000000..ef15a2103d28be40357207a3aefc45daf3497ba0 GIT binary patch literal 513 zcmXYtL2DCH5XWcV+iZ4UlA3O;t^vt{Hq=5v5MdR(co6a8!7s3f1XAz=c-%u@EJ;^3 zu>mWkEK7Fj6AD!jFX_#ekkji?so=#B1TjcO6L*QP;xG(m5J z@!$~mPVg2agZwE|-N|fwI%C(}KeLKrl@-%P-h{oRX(mzCe2?~(N}3_6GB9uBP^ZXP zHQjt17ARf?cRuz@uhEvDtml6R+QD1g>j`o!$lI+XF!%Ojt`&LG=RXJhM^M`Fh3S+@ z0#$gdgAg6$tu)LI7I+^SjJWSmq%fd}U8UHEy*KeOY9UVg{B(#1{JUivHiO{AJVu_* zq#*uienvH;TJYphIn3LUGN?-xAm#sAO}`4Es@`9ec5C+}4?l1wW(hNmX2pFuHb-S) zmNFmWa4tI6&C^U>TO(qhE{U}M$iZTP0iUk(U$*cG7m73&o-fjDq<0H-c`YUW*GwYK z3dt8`_b5BQBIeujP##|x@o}Gj8)m|cpaozx#DMc@m~w_kIsT!p884~|CF0;_VpH%5 zQ!R&h@dakJwEUw(V(`asBK=2pLEyoG~ literal 0 HcmV?d00001 diff --git a/database.json b/database.json index c2a2d649..53362288 100644 --- a/database.json +++ b/database.json @@ -1,7 +1,7 @@ { - "generated_at": "2026-03-29T05:25:34Z", - "total_files": 7625, - "total_size": 9746817953, + "generated_at": "2026-03-29T05:40:45Z", + "total_files": 7626, + "total_size": 9746818466, "files": { "520d3d1b5897800af47f92efd2444a26b7a7dead": { "path": "bios/3DO Company/3DO/3do_arcade_saot.bin", @@ -27353,6 +27353,16 @@ "crc32": "0d973c9d", "adler32": "e6adea85" }, + "bdca7bbb647066cf7555ce5d555ce09de4ebc41b": { + "path": "bios/Atari/Lynx/howard.o", + "name": "howard.o", + "size": 513, + "sha1": "bdca7bbb647066cf7555ce5d555ce09de4ebc41b", + "md5": "0bc067bfdbce0e7130264b2e3aade0ac", + "sha256": "8fc5201d46abd1a98639b59ba1579198617a4c28839fd3611ac82cbb092fa77e", + "crc32": "8fb3a531", + "adler32": "cc2dc70a" + }, "bfcc01ae105fa4cee47890c09ebad9e3775395a4": { "path": "bios/Atari/ST/emutos.img", "name": "emutos.img", @@ -78991,6 +79001,7 @@ "ce6a86574d0c9de9075705f14e99d090": "d47baf4953fa3297f68886f392b373e204c20a8f", "bcfe348c565d9dedb173822ee6850dea": "f8991b0c385f4e5002fa2a7e2f5e61e8c5213356", "fcd403db69f54290b51035d82f835e7b": "e4ed47fae31693e016b081c6bda48da5b70d7ccb", + "0bc067bfdbce0e7130264b2e3aade0ac": "bdca7bbb647066cf7555ce5d555ce09de4ebc41b", "e2c861c588fca2d0cf6be3df3aaf05f2": "bfcc01ae105fa4cee47890c09ebad9e3775395a4", "b338bacb2fc453bab61bcc1e2cf5076b": "7157f6a8aff275cfbb5ea6aa8e788dda8a977e56", "cd6408638f9dbef45cbbd5d0b6060c60": "5bcabba35bb8fbfe5a65b85efccf5ed657388308", @@ -91622,6 +91633,9 @@ "Atari_LYNX_boot.img": [ "e4ed47fae31693e016b081c6bda48da5b70d7ccb" ], + "howard.o": [ + "bdca7bbb647066cf7555ce5d555ce09de4ebc41b" + ], "emutos.img": [ "bfcc01ae105fa4cee47890c09ebad9e3775395a4", "7f22e9b1de07d2466d17cf23187775c246ce9566" @@ -111152,6 +111166,7 @@ "1a48e4b3": "d47baf4953fa3297f68886f392b373e204c20a8f", "fb731aaa": "f8991b0c385f4e5002fa2a7e2f5e61e8c5213356", "0d973c9d": "e4ed47fae31693e016b081c6bda48da5b70d7ccb", + "8fb3a531": "bdca7bbb647066cf7555ce5d555ce09de4ebc41b", "dbfa7ceb": "bfcc01ae105fa4cee47890c09ebad9e3775395a4", "4a1f42af": "7157f6a8aff275cfbb5ea6aa8e788dda8a977e56", "ca5e7999": "5bcabba35bb8fbfe5a65b85efccf5ed657388308", diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index db4587d0..d2f216ec 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -328,12 +328,18 @@ def _collect_emulator_extras( - Respects data_directories coverage - Only returns files that exist in the repo (packable) + When the same file is needed at multiple destinations by different cores + (e.g. cdimono1.zip at root for cdi2015 and at same_cdi/bios/ for same_cdi), + all destinations are included so every core finds its files. + Works for ANY platform (RetroArch, Batocera, Recalbox, etc.) """ + from common import resolve_platform_cores from verify import find_undeclared_files undeclared = find_undeclared_files(config, emulators_dir, db, emu_profiles, target_cores=target_cores) extras = [] + seen_dests: set[str] = set(seen) for u in undeclared: if not u["in_repo"]: continue @@ -342,8 +348,9 @@ def _collect_emulator_extras( name = archive if archive else u["name"] dest = archive if archive else (u.get("path") or u["name"]) full_dest = f"{base_dest}/{dest}" if base_dest else dest - if full_dest in seen: + if full_dest in seen_dests: continue + seen_dests.add(full_dest) extras.append({ "name": name, "destination": dest, @@ -351,6 +358,55 @@ def _collect_emulator_extras( "hle_fallback": u.get("hle_fallback", False), "source_emulator": u.get("emulator", ""), }) + + # Second pass: find alternative destinations for files already in the pack. + # A file declared by the platform or emitted above may also be needed at a + # different path by another core (e.g. neocd/ vs root, same_cdi/bios/ vs root). + profiles = emu_profiles if emu_profiles is not None else load_emulator_profiles(emulators_dir) + relevant = resolve_platform_cores(config, profiles, target_cores=target_cores) + standalone_set = {str(c) for c in config.get("standalone_cores", [])} + by_name = db.get("indexes", {}).get("by_name", {}) + by_path_suffix = db.get("indexes", {}).get("by_path_suffix", {}) + + for emu_name, profile in sorted(profiles.items()): + if profile.get("type") in ("launcher", "alias"): + continue + if emu_name not in relevant: + continue + is_standalone = emu_name in standalone_set or bool( + standalone_set & {str(c) for c in profile.get("cores", [])} + ) + for f in profile.get("files", []): + fname = f.get("name", "") + if not fname: + continue + file_mode = f.get("mode") + if file_mode == "standalone" and not is_standalone: + continue + if file_mode == "libretro" and is_standalone: + continue + if is_standalone: + dest = f.get("standalone_path") or f.get("path") or fname + else: + dest = f.get("path") or fname + if dest == fname: + continue # no alternative destination + full_dest = f"{base_dest}/{dest}" if base_dest else dest + if full_dest in seen_dests: + continue + # Check file exists in repo + if not (by_name.get(fname) or by_name.get(dest.rsplit("/", 1)[-1]) + or by_path_suffix.get(dest)): + continue + seen_dests.add(full_dest) + extras.append({ + "name": fname, + "destination": dest, + "required": f.get("required", False), + "hle_fallback": f.get("hle_fallback", False), + "source_emulator": profile.get("emulator", emu_name), + }) + return extras diff --git a/tests/test_e2e.py b/tests/test_e2e.py index a2a913e3..0a31100f 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -462,6 +462,31 @@ class TestE2E(unittest.TestCase): with open(os.path.join(self.emulators_dir, "test_validation.yml"), "w") as fh: yaml.dump(emu_val, fh) + # Emulator A: declares present_req.bin at root (no path) + emu_root = { + "emulator": "TestRootCore", + "type": "libretro", + "systems": ["console-a"], + "files": [ + {"name": "present_req.bin", "required": True}, + ], + } + with open(os.path.join(self.emulators_dir, "test_root_core.yml"), "w") as fh: + yaml.dump(emu_root, fh) + + # Emulator B: declares same file at a subdirectory path + emu_subdir = { + "emulator": "TestSubdirCore", + "type": "libretro", + "systems": ["console-a"], + "files": [ + {"name": "present_req.bin", "required": True, + "path": "subcore/bios/present_req.bin"}, + ], + } + with open(os.path.join(self.emulators_dir, "test_subdir_core.yml"), "w") as fh: + yaml.dump(emu_subdir, fh) + # --------------------------------------------------------------- # THE TEST — one method per feature area, all using same fixtures # --------------------------------------------------------------- @@ -2420,6 +2445,38 @@ class TestE2E(unittest.TestCase): self.assertNotIn("missing_archive.zip", extra_names) + def test_165_pack_extras_multi_dest_cross_ref(self): + """Same file at different paths from two profiles produces both destinations.""" + from generate_pack import _collect_emulator_extras + config = load_platform_config("test_existence", self.platforms_dir) + profiles = load_emulator_profiles(self.emulators_dir) + extras = _collect_emulator_extras( + config, self.emulators_dir, self.db, + set(), "", profiles, + ) + extra_dests = {e["destination"] for e in extras} + # Root destination (from test_emu or test_root_core, no path) + self.assertIn("present_req.bin", extra_dests) + # Subdirectory destination (from test_subdir_core) + self.assertIn("subcore/bios/present_req.bin", extra_dests) + + def test_166_pack_extras_multi_dest_platform_declared(self): + """Profile with path different from platform destination adds alternative.""" + from generate_pack import _collect_emulator_extras + config = load_platform_config("test_existence", self.platforms_dir) + profiles = load_emulator_profiles(self.emulators_dir) + # Simulate platform already having present_req.bin at root + seen = {"present_req.bin"} + extras = _collect_emulator_extras( + config, self.emulators_dir, self.db, + seen, "", profiles, + ) + extra_dests = {e["destination"] for e in extras} + # Root is already in pack (in seen), should NOT be duplicated + self.assertNotIn("present_req.bin", extra_dests) + # Subdirectory destination should be added + self.assertIn("subcore/bios/present_req.bin", extra_dests) + def test_90_registry_install_metadata(self): """Registry install section is accessible.""" import yaml