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.
This commit is contained in:
Abdessamad Derraz
2026-03-29 07:40:00 +02:00
parent 7492777b47
commit 536300984d
5 changed files with 136 additions and 8 deletions

View File

@@ -2,7 +2,7 @@
Complete BIOS and firmware packs for Batocera, BizHawk, EmuDeck, Lakka, Recalbox, RetroArch, RetroBat, RetroDECK, RetroPie, and RomM. 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 ## 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 - **10 platforms** supported with platform-specific verification
- **328 emulators** profiled from source (RetroArch cores + standalone) - **328 emulators** profiled from source (RetroArch cores + standalone)
- **352 systems** covered (NES, SNES, PlayStation, Saturn, Dreamcast, ...) - **352 systems** covered (NES, SNES, PlayStation, Saturn, Dreamcast, ...)
- **7,616 files** verified with MD5, SHA1, CRC32 checksums - **7,626 files** verified with MD5, SHA1, CRC32 checksums
- **8828 MB** total collection size - **9295 MB** total collection size
## Supported systems ## Supported systems
@@ -130,4 +130,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
This repository provides BIOS files for personal backup and archival purposes. 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*

BIN
bios/Atari/Lynx/howard.o Normal file

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{ {
"generated_at": "2026-03-29T05:25:34Z", "generated_at": "2026-03-29T05:40:45Z",
"total_files": 7625, "total_files": 7626,
"total_size": 9746817953, "total_size": 9746818466,
"files": { "files": {
"520d3d1b5897800af47f92efd2444a26b7a7dead": { "520d3d1b5897800af47f92efd2444a26b7a7dead": {
"path": "bios/3DO Company/3DO/3do_arcade_saot.bin", "path": "bios/3DO Company/3DO/3do_arcade_saot.bin",
@@ -27353,6 +27353,16 @@
"crc32": "0d973c9d", "crc32": "0d973c9d",
"adler32": "e6adea85" "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": { "bfcc01ae105fa4cee47890c09ebad9e3775395a4": {
"path": "bios/Atari/ST/emutos.img", "path": "bios/Atari/ST/emutos.img",
"name": "emutos.img", "name": "emutos.img",
@@ -78991,6 +79001,7 @@
"ce6a86574d0c9de9075705f14e99d090": "d47baf4953fa3297f68886f392b373e204c20a8f", "ce6a86574d0c9de9075705f14e99d090": "d47baf4953fa3297f68886f392b373e204c20a8f",
"bcfe348c565d9dedb173822ee6850dea": "f8991b0c385f4e5002fa2a7e2f5e61e8c5213356", "bcfe348c565d9dedb173822ee6850dea": "f8991b0c385f4e5002fa2a7e2f5e61e8c5213356",
"fcd403db69f54290b51035d82f835e7b": "e4ed47fae31693e016b081c6bda48da5b70d7ccb", "fcd403db69f54290b51035d82f835e7b": "e4ed47fae31693e016b081c6bda48da5b70d7ccb",
"0bc067bfdbce0e7130264b2e3aade0ac": "bdca7bbb647066cf7555ce5d555ce09de4ebc41b",
"e2c861c588fca2d0cf6be3df3aaf05f2": "bfcc01ae105fa4cee47890c09ebad9e3775395a4", "e2c861c588fca2d0cf6be3df3aaf05f2": "bfcc01ae105fa4cee47890c09ebad9e3775395a4",
"b338bacb2fc453bab61bcc1e2cf5076b": "7157f6a8aff275cfbb5ea6aa8e788dda8a977e56", "b338bacb2fc453bab61bcc1e2cf5076b": "7157f6a8aff275cfbb5ea6aa8e788dda8a977e56",
"cd6408638f9dbef45cbbd5d0b6060c60": "5bcabba35bb8fbfe5a65b85efccf5ed657388308", "cd6408638f9dbef45cbbd5d0b6060c60": "5bcabba35bb8fbfe5a65b85efccf5ed657388308",
@@ -91622,6 +91633,9 @@
"Atari_LYNX_boot.img": [ "Atari_LYNX_boot.img": [
"e4ed47fae31693e016b081c6bda48da5b70d7ccb" "e4ed47fae31693e016b081c6bda48da5b70d7ccb"
], ],
"howard.o": [
"bdca7bbb647066cf7555ce5d555ce09de4ebc41b"
],
"emutos.img": [ "emutos.img": [
"bfcc01ae105fa4cee47890c09ebad9e3775395a4", "bfcc01ae105fa4cee47890c09ebad9e3775395a4",
"7f22e9b1de07d2466d17cf23187775c246ce9566" "7f22e9b1de07d2466d17cf23187775c246ce9566"
@@ -111152,6 +111166,7 @@
"1a48e4b3": "d47baf4953fa3297f68886f392b373e204c20a8f", "1a48e4b3": "d47baf4953fa3297f68886f392b373e204c20a8f",
"fb731aaa": "f8991b0c385f4e5002fa2a7e2f5e61e8c5213356", "fb731aaa": "f8991b0c385f4e5002fa2a7e2f5e61e8c5213356",
"0d973c9d": "e4ed47fae31693e016b081c6bda48da5b70d7ccb", "0d973c9d": "e4ed47fae31693e016b081c6bda48da5b70d7ccb",
"8fb3a531": "bdca7bbb647066cf7555ce5d555ce09de4ebc41b",
"dbfa7ceb": "bfcc01ae105fa4cee47890c09ebad9e3775395a4", "dbfa7ceb": "bfcc01ae105fa4cee47890c09ebad9e3775395a4",
"4a1f42af": "7157f6a8aff275cfbb5ea6aa8e788dda8a977e56", "4a1f42af": "7157f6a8aff275cfbb5ea6aa8e788dda8a977e56",
"ca5e7999": "5bcabba35bb8fbfe5a65b85efccf5ed657388308", "ca5e7999": "5bcabba35bb8fbfe5a65b85efccf5ed657388308",

View File

@@ -328,12 +328,18 @@ def _collect_emulator_extras(
- Respects data_directories coverage - Respects data_directories coverage
- Only returns files that exist in the repo (packable) - 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.) Works for ANY platform (RetroArch, Batocera, Recalbox, etc.)
""" """
from common import resolve_platform_cores
from verify import find_undeclared_files from verify import find_undeclared_files
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)
for u in undeclared: for u in undeclared:
if not u["in_repo"]: if not u["in_repo"]:
continue continue
@@ -342,8 +348,9 @@ def _collect_emulator_extras(
name = archive if archive else u["name"] name = archive if archive else u["name"]
dest = archive if archive else (u.get("path") or u["name"]) dest = archive if archive else (u.get("path") or u["name"])
full_dest = f"{base_dest}/{dest}" if base_dest else dest full_dest = f"{base_dest}/{dest}" if base_dest else dest
if full_dest in seen: if full_dest in seen_dests:
continue continue
seen_dests.add(full_dest)
extras.append({ extras.append({
"name": name, "name": name,
"destination": dest, "destination": dest,
@@ -351,6 +358,55 @@ def _collect_emulator_extras(
"hle_fallback": u.get("hle_fallback", False), "hle_fallback": u.get("hle_fallback", False),
"source_emulator": u.get("emulator", ""), "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 return extras

View File

@@ -462,6 +462,31 @@ class TestE2E(unittest.TestCase):
with open(os.path.join(self.emulators_dir, "test_validation.yml"), "w") as fh: with open(os.path.join(self.emulators_dir, "test_validation.yml"), "w") as fh:
yaml.dump(emu_val, 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 # 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) 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): def test_90_registry_install_metadata(self):
"""Registry install section is accessible.""" """Registry install section is accessible."""
import yaml import yaml