mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-05 17:43:54 -06:00
docs: round 4 wiki and release docs
This commit is contained in:
commit
3bbe7225af
12 files changed
+565
-113
No files matched your search
@@ -7,7 +7,9 @@ SHA256SUMS.txt lists for it, as must each handheld launcher (RetroBIOS.zip
|
||||
and the others build_port.py writes) the list names, the parts of a pack must add up to the download
|
||||
size the README prints, and nothing else may be attached. The record holds
|
||||
one pack for each platform the registry marks release_pack, and no other
|
||||
pack. An upload that
|
||||
pack. The installer (install.py, install.sh, install.ps1) and every file
|
||||
list of install/ are listed and attached too, so a package can pin one
|
||||
release. An upload that
|
||||
failed in the loop, or a part re-uploaded from an older build, otherwise
|
||||
publishes a release that `sha256sum --check` rejects for every user.
|
||||
|
||||
@@ -37,6 +39,12 @@ from release_record import RECORD, load_record
|
||||
|
||||
SUMS = "SHA256SUMS.txt"
|
||||
SIGNATURE = SUMS + ".sig"
|
||||
INSTALLER = ("install.py", "install.sh", "install.ps1")
|
||||
|
||||
|
||||
def installer_files(install_dir: str | Path = "install") -> list[str]:
|
||||
"""The release files a package pins: the installer and each file list."""
|
||||
return [*INSTALLER, *sorted(p.name for p in Path(install_dir).glob("*.json"))]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -73,6 +81,7 @@ def compare_release(
|
||||
tag: str,
|
||||
local_files: dict[str, str] | None = None,
|
||||
released: set[str] | None = None,
|
||||
installer: list[str] | None = None,
|
||||
) -> list[Finding]:
|
||||
"""Every way the release can differ from what was built.
|
||||
|
||||
@@ -80,6 +89,7 @@ def compare_release(
|
||||
signature) to its local sha256: the copy on the release must be that one.
|
||||
*released* names the platforms the registry marks release_pack: each one
|
||||
has a recorded pack, and no recorded pack serves only other platforms.
|
||||
*installer* names the installer files each release lists and carries.
|
||||
"""
|
||||
findings: list[Finding] = []
|
||||
if record.get("tag") != tag:
|
||||
@@ -112,9 +122,11 @@ def compare_release(
|
||||
findings.append(Finding("extra", name, "attached but neither recorded nor listed"))
|
||||
for name in sorted(set(recorded) - set(sums)):
|
||||
findings.append(Finding("unlisted", name, f"absent from {SUMS}"))
|
||||
for name in sorted(set(installer or ()) - set(sums)):
|
||||
findings.append(Finding("no-installer", name, f"absent from {SUMS}"))
|
||||
# The handheld launchers are release files but no packs: release.json
|
||||
# records packs, and the checksum list alone vouches for the launchers.
|
||||
for name in sorted(set(sums) - set(recorded) - set(ARCHIVES)):
|
||||
for name in sorted(set(sums) - set(recorded) - set(ARCHIVES) - set(installer or ())):
|
||||
findings.append(Finding("unrecorded", name, f"listed in {SUMS}, absent from release.json"))
|
||||
|
||||
wanted = {**sums, **(local_files or {})}
|
||||
@@ -190,6 +202,10 @@ def main() -> int:
|
||||
"--platforms-dir", default="platforms",
|
||||
help="registry whose release_pack platforms each need a pack (default: %(default)s)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--install-dir", default="install",
|
||||
help="file lists the release must carry beside the installer (default: %(default)s)",
|
||||
)
|
||||
parser.add_argument("--json", action="store_true", help="JSON output")
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -223,7 +239,8 @@ def main() -> int:
|
||||
return 2
|
||||
|
||||
released = set(list_release_pack_platforms(args.platforms_dir, include_archived=True))
|
||||
findings = compare_release(record, sums, assets, tag, local_files, released)
|
||||
findings = compare_release(record, sums, assets, tag, local_files, released,
|
||||
installer_files(args.install_dir))
|
||||
if args.json:
|
||||
print(json.dumps(
|
||||
{"tag": tag, "draft": draft, "assets": len(assets),
|
||||
|
||||
@@ -205,6 +205,11 @@ class DeepLinksCarryALabel(unittest.TestCase):
|
||||
says it too: there a label read as a broken crumb beside the Reference
|
||||
tab (round 4)."""
|
||||
|
||||
# Answers a start page sends its reader to on purpose: the arcade
|
||||
# version table opens on one plain sentence, and "Reference:" before
|
||||
# its link read as a broken crumb (round 4, C4 and C9).
|
||||
ANSWERS = ("/retrobios/wiki/technical-faq/#which-mame-version-do-the-arcade-bios-sets-match",)
|
||||
|
||||
def test_start_and_setup_pages(self):
|
||||
import sitestarlight # noqa: PLC0415
|
||||
|
||||
@@ -232,6 +237,8 @@ class DeepLinksCarryALabel(unittest.TestCase):
|
||||
deeper = levels[target]
|
||||
if ORDER.index(deeper) - ORDER.index(level) < 2:
|
||||
continue
|
||||
if any(f"]({answer})" in match.group(0) for answer in self.ANSWERS):
|
||||
continue
|
||||
label = f"{generate_site.LEVELS[deeper]}:"
|
||||
if line[: match.start()].rstrip().endswith("?"):
|
||||
continue
|
||||
|
||||
@@ -366,11 +366,15 @@ class InstallerOptionsAtAGlance(unittest.TestCase):
|
||||
listed = {usage.split()[0].rstrip(",") for usage, _ in sitediagrams.installer_options()}
|
||||
self.assertEqual(declared | {"--help"}, listed)
|
||||
|
||||
def test_the_built_page_opens_on_the_table(self):
|
||||
def test_the_built_page_opens_on_a_link_per_option(self):
|
||||
text = _built("wiki/installer.md")
|
||||
table = text.index("| Option | What it does | More |")
|
||||
self.assertLess(table, text.index("## One line"))
|
||||
self.assertIn("| `--check` |", text)
|
||||
glance = text.index("## Options at a glance")
|
||||
self.assertLess(glance, text.index("## One line"))
|
||||
listed = text[glance:text.index("## One line")]
|
||||
for usage, _ in sitediagrams.installer_options():
|
||||
flag = usage.split()[0].rstrip(",")
|
||||
with self.subTest(option=flag):
|
||||
self.assertIn(f"[`{flag}`](#option-{flag.lstrip('-')})", listed)
|
||||
self.assertNotIn("<!-- installer-options", text)
|
||||
|
||||
|
||||
|
||||
@@ -105,12 +105,13 @@ class ArcadeAnswerWherePlayersLook(unittest.TestCase):
|
||||
headings.index("3. Check the folder and the names"))
|
||||
section = _flat(_section(text, "An arcade game does not start"))
|
||||
for words in ("neogeo.zip", "same emulator version", "MAME version", "Details:",
|
||||
"../emulators/index.md", "Run the command once."):
|
||||
"technical-faq.md#which-mame-version-do-the-arcade-bios-sets-match",
|
||||
"Run the command once."):
|
||||
with self.subTest(words=words):
|
||||
self.assertIn(words, section)
|
||||
|
||||
def test_the_faq_links_the_technical_answer(self):
|
||||
section = _section(_wiki("faq.md"), "My arcade game does not start")
|
||||
section = _section(_wiki("faq.md"), "My arcade game fails (which MAME version?)")
|
||||
anchor = "technical-faq.md#which-mame-version-do-the-arcade-bios-sets-match"
|
||||
self.assertIn(anchor, section)
|
||||
self.assertIn("## Which MAME version do the arcade BIOS sets match?", _wiki("technical-faq.md"))
|
||||
@@ -235,7 +236,8 @@ class ExitStatusAndPinnedInstaller(unittest.TestCase):
|
||||
|
||||
def test_a_packager_pins_installer_and_lists_to_one_tag(self):
|
||||
section = _section(self.text, "One line")
|
||||
self.assertIn("/retrobios/<tag>/install.py", section)
|
||||
self.assertIn("releases/download/<tag>", section)
|
||||
self.assertIn("$base/install.py", section)
|
||||
self.assertIn("RETROBIOS_REF=<tag>", section)
|
||||
self.assertIn('os.environ.get("RETROBIOS_REF"', self.source)
|
||||
|
||||
@@ -245,7 +247,7 @@ class SmallCopyFixes(unittest.TestCase):
|
||||
|
||||
def test_the_sd_card_example_names_its_setup(self):
|
||||
section = _flat(_section(_wiki("faq.md"), "Fill an SD card from a computer?"))
|
||||
self.assertIn("For RetroArch on a Switch card in drive `E:`", section)
|
||||
self.assertIn("For RetroArch on a Switch, with the card in drive `E:`, run:", section)
|
||||
guides = (REPO_ROOT / "platforms" / "_guides.yml").read_text(encoding="utf-8")
|
||||
self.assertIn("where: Nintendo Switch", guides)
|
||||
self.assertIn("`retroarch/cores/system/` on the SD card", guides)
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
"""Wiki and release docs (round 4, package 5).
|
||||
|
||||
Each class names the finding it holds: an error message a reader types
|
||||
into search and no page that says it, an arcade answer written for
|
||||
maintainers, a subfolder setting that hides every file, FAQ labels that
|
||||
read as broken crumbs, no legal route to a BIOS, a packager who cannot pin
|
||||
the installer, a backslash in a PowerShell command, an events table with no
|
||||
sample, the installer options printed twice, and a glossary "Pack" that
|
||||
ignored parts. Every claim the copy makes is held against the repository.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
|
||||
import sitediagrams # noqa: E402
|
||||
from check_release import ( # noqa: E402
|
||||
INSTALLER,
|
||||
SUMS,
|
||||
Asset,
|
||||
compare_release,
|
||||
installer_files,
|
||||
)
|
||||
|
||||
WIKI = REPO_ROOT / "wiki"
|
||||
FENCED = re.compile(r"^```.*?^```", re.S | re.M)
|
||||
ARCADE_ANCHOR = "technical-faq.md#which-mame-version-do-the-arcade-bios-sets-match"
|
||||
|
||||
|
||||
def _wiki(name: str) -> str:
|
||||
return (WIKI / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _front(text: str) -> dict:
|
||||
return yaml.safe_load(text.split("---", 2)[1])
|
||||
|
||||
|
||||
def _section(text: str, heading: str) -> str:
|
||||
start = re.search(rf"(?m)^#+ {re.escape(heading)}(?:\s*\{{[^}}]*\}})?\n", text)
|
||||
if start is None:
|
||||
raise AssertionError(f"no section {heading!r}")
|
||||
rest = text[start.end():]
|
||||
blank = FENCED.sub(lambda m: re.sub(r"[^\n]", " ", m.group(0)), rest)
|
||||
end = re.search(r"(?m)^#{1,2} ", blank)
|
||||
return rest[: end.start()] if end else rest
|
||||
|
||||
|
||||
def _flat(text: str) -> str:
|
||||
return " ".join(text.split())
|
||||
|
||||
|
||||
def _profile(name: str) -> dict:
|
||||
return yaml.safe_load((REPO_ROOT / "emulators" / f"{name}.yml").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
class TroubleshootingSaysTheWordsOnScreen(unittest.TestCase):
|
||||
"""C6, N3: "bios not found" ranked the page 4th, and the most quoted
|
||||
on-screen message found nothing."""
|
||||
|
||||
def setUp(self):
|
||||
self.text = _wiki("troubleshooting.md")
|
||||
|
||||
def test_step_one_opens_on_the_messages(self):
|
||||
step = _flat(_section(self.text, "1. Check my BIOS folder"))
|
||||
self.assertIn("**BIOS not found**, **BIOS missing** or **firmware missing**?", step)
|
||||
self.assertLess(step.index("BIOS not found"), step.index("```"))
|
||||
|
||||
def test_each_message_is_cited(self):
|
||||
for source in ("bsx.cpp:1232", "fds.c:312", "libretro.cpp:866", "msg_hash_us.h:32805"):
|
||||
with self.subTest(source=source):
|
||||
self.assertIn(source, self.text)
|
||||
|
||||
def test_the_disc_message(self):
|
||||
section = _flat(_section(self.text, "The BIOS is fine, the game still fails"))
|
||||
self.assertIn('"Please insert PlayStation CD-ROM"?** The BIOS works.', section)
|
||||
self.assertIn("open its `.cue` or `.m3u` file, not a `.bin`", section)
|
||||
|
||||
def test_terms_for_search(self):
|
||||
terms = _front(self.text)["terms"]
|
||||
for word in ("bios not found", "bios missing", "firmware missing", "bios error"):
|
||||
with self.subTest(word=word):
|
||||
self.assertIn(word, terms)
|
||||
|
||||
|
||||
class SubfolderTrap(unittest.TestCase):
|
||||
"""C8: a BIOS setting pointed at system/dc, while Flycast adds dc/ itself."""
|
||||
|
||||
def test_step_three_names_it(self):
|
||||
step = _flat(_section(_wiki("troubleshooting.md"), "3. Check the folder and the names"))
|
||||
self.assertIn("subfolder, like `system/dc`?", step)
|
||||
self.assertIn("like `dc/` for Flycast", step)
|
||||
|
||||
def test_flycast_reads_dc(self):
|
||||
flycast = _profile("flycast")
|
||||
boot = next(e for e in flycast["files"] if e["name"] == "dc_boot.bin")
|
||||
self.assertEqual(boot["path"], "dc/dc_boot.bin")
|
||||
self.assertIn('appends "dc/" to the system directory', flycast["notes"])
|
||||
|
||||
|
||||
def _arcade_profiles() -> dict[str, dict]:
|
||||
"""The MAME and FinalBurn libretro cores whose name does not give the version."""
|
||||
found = {}
|
||||
for path in sorted((REPO_ROOT / "emulators").glob("*.yml")):
|
||||
if not re.match(r"(mame|fbneo|fbalpha)", path.stem):
|
||||
continue
|
||||
profile = yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||
if "libretro" not in str(profile.get("type", "")):
|
||||
continue
|
||||
# Recalbox's MAME 0.258 and 0.278 carry the answer in their name.
|
||||
if _version(profile) in _retroarch_name(profile):
|
||||
continue
|
||||
found[path.stem] = profile
|
||||
return found
|
||||
|
||||
|
||||
def _retroarch_name(profile: dict) -> str:
|
||||
""""Arcade (MAME 2003)" gives "MAME 2003", "Arcade (FinalBurn Neo) (Neo-Geo)"
|
||||
gives "FinalBurn Neo (Neo-Geo)"."""
|
||||
inner = profile["display_name"].split("(", 1)[1]
|
||||
return inner.replace(")", "", 1).strip()
|
||||
|
||||
|
||||
def _version(profile: dict) -> str:
|
||||
return str(profile.get("mame_version") or profile["core_version"]).split()[0].lstrip("v")
|
||||
|
||||
|
||||
class ArcadeVersionTable(unittest.TestCase):
|
||||
"""C4, N3: the answer named profile ids and left out FinalBurn Neo."""
|
||||
|
||||
def setUp(self):
|
||||
self.text = _wiki("technical-faq.md")
|
||||
section = _section(self.text, "Which MAME version do the arcade BIOS sets match?")
|
||||
self.section = section
|
||||
self.rows = {}
|
||||
for line in section.splitlines():
|
||||
cells = [c.strip() for c in line.strip().strip("|").split("|")]
|
||||
if (line.startswith("|") and len(cells) == 2 and not set(cells[0]) <= {"-"}
|
||||
and cells[1] != "Sets from"):
|
||||
for name in cells[0].split(", "):
|
||||
self.rows[name] = cells[1]
|
||||
|
||||
def test_the_section_opens_on_the_answer(self):
|
||||
self.assertTrue(self.section.lstrip().startswith(
|
||||
"Your game sets and BIOS sets must come from the same version as your arcade\nemulator."))
|
||||
self.assertIn("| Emulator, as RetroArch names it | Sets from |", self.section)
|
||||
self.assertLess(self.section.index("| Emulator"), self.section.index("### How this is known"))
|
||||
|
||||
def test_every_core_has_its_row_and_version(self):
|
||||
profiles = _arcade_profiles()
|
||||
self.assertGreaterEqual(len(profiles), 20)
|
||||
for key, profile in profiles.items():
|
||||
name = _retroarch_name(profile)
|
||||
with self.subTest(profile=key, name=name):
|
||||
self.assertIn(name, self.rows)
|
||||
self.assertIn(_version(profile), self.rows[name])
|
||||
|
||||
def test_no_row_without_a_core(self):
|
||||
names = {_retroarch_name(p) for p in _arcade_profiles().values()}
|
||||
self.assertEqual(set(self.rows) - names, set())
|
||||
|
||||
def test_terms_and_links(self):
|
||||
terms = _front(self.text)["terms"]
|
||||
for word in ("mame version", "romset version", "which mame"):
|
||||
with self.subTest(word=word):
|
||||
self.assertIn(word, terms)
|
||||
arcade = _section(_wiki("troubleshooting.md"), "An arcade game does not start")
|
||||
self.assertIn(ARCADE_ANCHOR, arcade)
|
||||
self.assertNotIn("emulators/index.md", arcade)
|
||||
faq = _wiki("faq.md")
|
||||
self.assertIn("## My arcade game fails (which MAME version?) "
|
||||
"{ #my-arcade-game-does-not-start }", faq)
|
||||
|
||||
|
||||
class FaqLabels(unittest.TestCase):
|
||||
"""C9, N10: "Reference:" read as a broken crumb, "E::" as a typo, iOS unfound."""
|
||||
|
||||
def setUp(self):
|
||||
self.text = _wiki("faq.md")
|
||||
|
||||
def test_no_reference_label(self):
|
||||
self.assertNotRegex(self.text, r"(?m)^Reference:")
|
||||
arcade = _section(self.text, "My arcade game fails (which MAME version?)")
|
||||
self.assertIn(f"Game sets must match the emulator's version:\n[the list]({ARCADE_ANCHOR}).",
|
||||
arcade)
|
||||
|
||||
def test_the_card_sentence(self):
|
||||
self.assertIn("For RetroArch on a Switch,\nwith the card in drive `E:`, run:", self.text)
|
||||
|
||||
def test_ios(self):
|
||||
self.assertIn("iPhone, iPad (iOS)", self.text)
|
||||
|
||||
|
||||
class CopyYourOwnBios(unittest.TestCase):
|
||||
"""C-other, need 12: no page answered the legal route."""
|
||||
|
||||
def test_each_console_has_a_guide(self):
|
||||
section = _section(_wiki("faq.md"), "Can I copy my own console's BIOS?")
|
||||
links = re.findall(r"\[([^\]]+)\]\((https://[^)]+)\)", section)
|
||||
self.assertEqual(dict(links), {
|
||||
"PCSX2": "https://pcsx2.net/docs/setup/bios/",
|
||||
"melonDS": "https://melonds.kuribo64.net/faq.php",
|
||||
})
|
||||
|
||||
|
||||
def _sums_with(names: list[str]) -> dict[str, str]:
|
||||
return {name: f"{i:064x}" for i, name in enumerate(names, 1)}
|
||||
|
||||
|
||||
class PinnedInstaller(unittest.TestCase):
|
||||
"""J9, D5: release tags are deleted and the release carried no installer."""
|
||||
|
||||
def test_the_list_holds_the_installer_and_every_file_list(self):
|
||||
names = installer_files(REPO_ROOT / "install")
|
||||
self.assertEqual(names[:3], list(INSTALLER))
|
||||
self.assertIn("retroarch.json", names)
|
||||
self.assertEqual(len(names), 3 + len(list((REPO_ROOT / "install").glob("*.json"))))
|
||||
|
||||
def test_a_release_without_the_installer_is_refused(self):
|
||||
record = {"tag": "v1", "packs": {"P.zip": {"download_size": 5, "assets": ["P.zip"]}}}
|
||||
files = ["install.py", "install.sh", "install.ps1", "retroarch.json"]
|
||||
sums = _sums_with(["P.zip", *files])
|
||||
assets = {name: Asset(5, digest) for name, digest in sums.items()}
|
||||
assets[SUMS] = Asset(1, None)
|
||||
self.assertEqual(compare_release(record, sums, assets, "v1", installer=files), [])
|
||||
|
||||
del sums["install.py"]
|
||||
del assets["install.py"]
|
||||
kinds = {(f.kind, f.name) for f in compare_release(record, sums, assets, "v1",
|
||||
installer=files)}
|
||||
self.assertEqual(kinds, {("no-installer", "install.py")})
|
||||
|
||||
def test_listed_but_not_uploaded_is_missing(self):
|
||||
record = {"tag": "v1", "packs": {}}
|
||||
sums = _sums_with(list(INSTALLER))
|
||||
assets = {name: Asset(5, digest) for name, digest in sums.items() if name != "install.sh"}
|
||||
assets[SUMS] = Asset(1, None)
|
||||
kinds = {(f.kind, f.name) for f in compare_release(record, sums, assets, "v1",
|
||||
installer=list(INSTALLER))}
|
||||
self.assertEqual(kinds, {("missing", "install.sh")})
|
||||
|
||||
def test_the_release_steps_ship_them(self):
|
||||
text = _wiki("release-process.md")
|
||||
self.assertIn("cp install.py install.sh install.ps1 install/*.json dist/", text)
|
||||
sums = re.search(r"sha256sum [^>]+> SHA256SUMS.txt", text, re.S).group(0)
|
||||
upload = re.search(r"for f in dist/SHA256SUMS.txt.*?; do", text, re.S).group(0)
|
||||
for name in (*INSTALLER, "*.json"):
|
||||
with self.subTest(name=name):
|
||||
self.assertIn(name, sums)
|
||||
for name in INSTALLER:
|
||||
with self.subTest(upload=name):
|
||||
self.assertIn(f"dist/{name}", upload)
|
||||
self.assertIn("install/*.json", upload)
|
||||
|
||||
def test_the_installer_page_names_the_release_addresses(self):
|
||||
text = _wiki("installer.md")
|
||||
section = _flat(_section(text, "For packagers"))
|
||||
for words in ("releases/download/<tag>", "--manifest retroarch.json",
|
||||
"sha256sum --check", "SHA256SUMS.txt", "releases/latest/download/",
|
||||
"release-process.md#verifying-a-release"):
|
||||
with self.subTest(words=words):
|
||||
self.assertIn(words, section)
|
||||
self.assertNotIn("raw.githubusercontent.com/Abdess/retrobios/<tag>/install.py", text)
|
||||
terms = _front(text)["terms"]
|
||||
for word in ("manifest", "events", "exit code", "schema"):
|
||||
with self.subTest(term=word):
|
||||
self.assertIn(word, terms)
|
||||
|
||||
|
||||
class NoBackslashInProseCode(unittest.TestCase):
|
||||
"""D9: `irm \\| iex` showed its backslash outside a table."""
|
||||
|
||||
def test_wiki(self):
|
||||
for path in sorted(WIKI.glob("*.md")):
|
||||
text = FENCED.sub("", path.read_text(encoding="utf-8"))
|
||||
for number, line in enumerate(text.splitlines(), 1):
|
||||
if line.lstrip().startswith("|"):
|
||||
continue
|
||||
with self.subTest(page=path.name, line=number):
|
||||
self.assertNotRegex(line, r"`[^`]*\\\|[^`]*`")
|
||||
|
||||
|
||||
def _install_module():
|
||||
spec = importlib.util.spec_from_file_location("install_r4wiki", REPO_ROOT / "install.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class EventsTranscript(unittest.TestCase):
|
||||
"""D10: the events table had no sample of what a run writes."""
|
||||
|
||||
def test_five_lines_that_follow_the_schema(self):
|
||||
section = _section(_wiki("installer.md"), "Machine-readable events")
|
||||
block = re.search(r"```json\n(.*?)\n```", section, re.S)
|
||||
self.assertIsNotNone(block)
|
||||
lines = block.group(1).splitlines()
|
||||
self.assertEqual(len(lines), 5)
|
||||
keys = _install_module().EVENT_KEYS
|
||||
events = [json.loads(line) for line in lines]
|
||||
for event in events:
|
||||
with self.subTest(event=event["event"]):
|
||||
self.assertEqual(list(event), ["event", *keys[event["event"]]])
|
||||
self.assertEqual(events[0]["event"], "start")
|
||||
self.assertEqual(events[0]["mode"], "check")
|
||||
self.assertEqual(events[-1], {"event": "end", "code": 1})
|
||||
|
||||
|
||||
class OneOptionsTable(unittest.TestCase):
|
||||
"""T-F9: the options printed twice, the offline install 57,750 px down."""
|
||||
|
||||
def setUp(self):
|
||||
self.text = _wiki("installer.md")
|
||||
|
||||
def test_each_option_links_to_its_row(self):
|
||||
glance = _section(self.text, "Options at a glance")
|
||||
table = _section(self.text, "Options")
|
||||
self.assertNotIn("installer-options", glance)
|
||||
self.assertNotIn("|", glance)
|
||||
for usage, _ in sitediagrams.installer_options():
|
||||
flag = usage.split()[0].rstrip(",")
|
||||
anchor = f"option-{flag.lstrip('-')}"
|
||||
with self.subTest(option=flag):
|
||||
self.assertIn(f"[`{flag}`](#{anchor})", glance)
|
||||
self.assertRegex(table, rf'(?m)^\| <span id="{anchor}"></span>`{flag}[ `]')
|
||||
|
||||
def test_the_offline_install_comes_early(self):
|
||||
headings = re.findall(r"(?m)^## (.+?)(?:\s*\{[^}]*\})?$", FENCED.sub("", self.text))
|
||||
self.assertLess(headings.index("Without a network (offline install)"),
|
||||
headings.index("The full-screen interface"))
|
||||
|
||||
|
||||
class GlossaryPack(unittest.TestCase):
|
||||
"""C-other: packs come in parts and leave out files the emulator ships."""
|
||||
|
||||
def test_definition(self):
|
||||
text = _flat(_wiki("glossary.md"))
|
||||
self.assertIn("**Pack** : The ZIP, sometimes in parts, with one setup's BIOS files.", text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+14
-10
@@ -38,12 +38,12 @@ the ZIPs inside zipped, like `neogeo.zip`.
|
||||
|
||||
## Does a BIOS work on every setup?
|
||||
|
||||
Usually. The command installs each setup's own version, like its `neogeo.zip`.
|
||||
Usually. The command installs each setup's own, like its `neogeo.zip`.
|
||||
|
||||
## My arcade game does not start
|
||||
## My arcade game fails (which MAME version?) { #my-arcade-game-does-not-start }
|
||||
|
||||
The game set must match the emulator's version, and `neogeo.zip` stays zipped.
|
||||
Reference: [Which version each emulator reads](technical-faq.md#which-mame-version-do-the-arcade-bios-sets-match).
|
||||
Game sets must match the emulator's version:
|
||||
[the list](technical-faq.md#which-mame-version-do-the-arcade-bios-sets-match).
|
||||
|
||||
## Keys, firmware or BIOS?
|
||||
|
||||
@@ -58,8 +58,7 @@ Sometimes, with an emulator's stand-in, but some games fail.
|
||||
|
||||
## Is the command safe? { #is-piping-the-installer-into-a-shell-safe }
|
||||
|
||||
- It stops if the installer does not match its expected fingerprint, and checks
|
||||
each file.
|
||||
- It checks the installer and each file against their fingerprints.
|
||||
- It writes in your BIOS folder, and for some emulators your save folder.
|
||||
- It never touches a game or a save.
|
||||
- It keeps each file it replaces in `retrobios-backup`. `--restore` puts it
|
||||
@@ -80,8 +79,8 @@ setup's files and checks each one. `--dry-run` shows the list first.
|
||||
|
||||
## Fill an SD card from a computer?
|
||||
|
||||
Yes. Name your setup and the card's BIOS folder. For RetroArch on a Switch
|
||||
card in drive `E:`:
|
||||
Name your setup and the card's BIOS folder. For RetroArch on a Switch,
|
||||
with the card in drive `E:`, run:
|
||||
|
||||
<!-- os-tabs windows: --platform retroarch --dest E:\retroarch\cores\system -->
|
||||
```sh
|
||||
@@ -97,8 +96,8 @@ Fill its folder from a computer, as above, or unzip your ZIP and check it with
|
||||
## My setup is not in the list
|
||||
|
||||
- Name your emulator's BIOS folder with `--dest`.
|
||||
- ES-DE, LaunchBox: the command fills the folders of the emulators they start.
|
||||
- iPhone, iPad: no terminal. Use the ZIP.
|
||||
- ES-DE, LaunchBox: the command fills their emulators' folders.
|
||||
- iPhone, iPad (iOS): no terminal. Use the ZIP.
|
||||
|
||||
## Why is a file missing from the ZIP?
|
||||
|
||||
@@ -108,6 +107,11 @@ Some come with the emulator, so the ZIP skips them.
|
||||
|
||||
Often a missing BIOS: [When a game does not start](troubleshooting.md).
|
||||
|
||||
## Can I copy my own console's BIOS? { #copy-your-own-bios }
|
||||
|
||||
Yes: [PCSX2](https://pcsx2.net/docs/setup/bios/) for PlayStation 2,
|
||||
[melonDS](https://melonds.kuribo64.net/faq.php) for DS and DSi.
|
||||
|
||||
## Is this legal?
|
||||
|
||||
Laws differ by country. [Is it legal?](legal.md)
|
||||
|
||||
+2
-2
@@ -22,8 +22,8 @@ order: 60
|
||||
: The folder where your setup looks for BIOS files.
|
||||
|
||||
**Pack**
|
||||
: The ZIP file with every BIOS file for one setup. Made for the setups most
|
||||
people download.
|
||||
: The ZIP, sometimes in parts, with one setup's BIOS files. Made for the
|
||||
setups most people download.
|
||||
|
||||
**Platform**
|
||||
: A setup, as the project's data calls it.
|
||||
|
||||
+102
-76
@@ -1,6 +1,7 @@
|
||||
---
|
||||
level: setup
|
||||
order: 100
|
||||
terms: manifest, events, exit code, schema
|
||||
---
|
||||
|
||||
# All installer options
|
||||
@@ -22,10 +23,13 @@ How the command protects your machine is under
|
||||
|
||||
## Options at a glance
|
||||
|
||||
Read from the installer's own option list, the one `--help` prints. The
|
||||
[Options](#options) table below gives each one in full.
|
||||
Each links to its row in the [Options](#options) table.
|
||||
|
||||
<!-- installer-options -->
|
||||
- **Where and for what**: [`--platform`](#option-platform), [`--dest`](#option-dest), [`--target`](#option-target), [`--every-board`](#option-every-board), [`--save-dir`](#option-save-dir), [`--romm-firmware`](#option-romm-firmware), [`--standalone-copies`](#option-standalone-copies)
|
||||
- **Fewer files**: [`--system`](#option-system), [`--core`](#option-core), [`--emulator`](#option-emulator), [`--region`](#option-region), [`--no-game-data`](#option-no-game-data), [`--installed-cores`](#option-installed-cores), [`--cores-dir`](#option-cores-dir)
|
||||
- **Look first**: [`--check`](#option-check), [`--dry-run`](#option-dry-run), [`--list-platforms`](#option-list-platforms), [`--list-targets`](#option-list-targets), [`--list-systems`](#option-list-systems), [`--list-cores`](#option-list-cores), [`--list-emulators`](#option-list-emulators)
|
||||
- **From files you have**: [`--from`](#option-from), [`--from-pack`](#option-from-pack), [`--manifest`](#option-manifest), [`--restore`](#option-restore)
|
||||
- **How the run talks**: [`--tui`](#option-tui), [`--plain`](#option-plain), [`--no-input`](#option-no-input), [`--yes`](#option-yes), [`--quiet`](#option-quiet), [`--verbose`](#option-verbose), [`--events`](#option-events), [`--jobs`](#option-jobs), [`--version`](#option-version), [`--help`](#option-help)
|
||||
|
||||
## One line
|
||||
|
||||
@@ -46,16 +50,27 @@ needed, and Python 3.8 is the floor both bootstraps enforce before handing over:
|
||||
python install.py
|
||||
```
|
||||
|
||||
A package that must not change under its users pins the installer to a
|
||||
release tag, and the file lists to the same tag with `RETROBIOS_REF`:
|
||||
### For packagers { #for-packagers }
|
||||
|
||||
A package that must not change under its users takes the installer and its
|
||||
file list from one release. Each release carries `install.py`, `install.sh`,
|
||||
`install.ps1` and the file list of every platform (`retroarch.json` and the
|
||||
others from `install/`), all listed in the signed `SHA256SUMS.txt`:
|
||||
|
||||
```bash
|
||||
curl -fsSLO https://raw.githubusercontent.com/Abdess/retrobios/<tag>/install.py
|
||||
RETROBIOS_REF=<tag> python3 install.py
|
||||
base=https://github.com/Abdess/retrobios/releases/download/<tag>
|
||||
curl -fsSLO "$base/install.py" -O "$base/retroarch.json" -O "$base/SHA256SUMS.txt"
|
||||
sha256sum --check --ignore-missing SHA256SUMS.txt
|
||||
RETROBIOS_REF=<tag> python3 install.py --manifest retroarch.json
|
||||
```
|
||||
|
||||
A tag lasts until the next release deletes it. What `RETROBIOS_REF` does not
|
||||
pin is listed under [Environment overrides](#environment-overrides).
|
||||
For contributors: [Verifying a release](release-process.md#verifying-a-release)
|
||||
checks the signature of the list. Each release deletes the previous tag and its assets,
|
||||
so `releases/latest/download/install.py` and the other files under
|
||||
`releases/latest/download/` are the stable addresses: they serve the latest
|
||||
release, whose `SHA256SUMS.txt` names the bytes. v2026.10.10 and the releases
|
||||
before it carry neither the installer nor the file lists. What `RETROBIOS_REF`
|
||||
does not pin is listed under [Environment overrides](#environment-overrides).
|
||||
|
||||
## Passing options
|
||||
|
||||
@@ -82,6 +97,36 @@ from the text and calling it passes the arguments through:
|
||||
--platform retroarch --dest D:\bios
|
||||
```
|
||||
|
||||
## Without a network (offline install) { #without-a-network-offline-install }
|
||||
|
||||
<span id="without-a-network"></span>
|
||||
|
||||
A pack downloaded once installs again with no network, on this machine or on
|
||||
one that has none:
|
||||
|
||||
```bash
|
||||
python install.py --from-pack ~/Downloads/RetroArch_v1.22.2_BIOS_Pack.zip
|
||||
python install.py --from-pack ~/Downloads/RetroArch_v1.22.2_BIOS_Pack.part1of3.zip
|
||||
python install.py --from-pack ~/Downloads --dest /media/card/retroarch/system
|
||||
```
|
||||
|
||||
One part is enough to name a split pack: the others are taken from the same
|
||||
folder, and a part that is not there is named before anything is written.
|
||||
The platform is read from the pack's name when `--platform` is not given. The
|
||||
file list is the one the pack carries (`manifest.json`), with each file's
|
||||
SHA-1 and size, and every file is checked against it before it takes its
|
||||
place. A data tree (Dolphin's `Sys/`, PPSSPP's assets) comes from the pack
|
||||
too, not from its source archive. `--target` reads a core list from the
|
||||
network and is refused with `--from-pack`. A board found on the device is not
|
||||
applied either: every board's files are installed.
|
||||
|
||||
`--manifest FILE` reads the file list from a copy of `install/<platform>.json`
|
||||
instead of downloading it. Alone, the files still come from the repository.
|
||||
With `--from-pack`, the run reads that list and takes every file from the
|
||||
pack: `--system`, `--core`, `--region` and `--no-game-data` then apply as
|
||||
they do online, and a file of the list the pack does not hold fails as
|
||||
`not in the pack`. The list's platform has to be the one installed for.
|
||||
|
||||
## The full-screen interface { #the-full-screen-interface }
|
||||
|
||||
Run at a terminal, the installer opens a full screen that a keyboard or a
|
||||
@@ -629,41 +674,41 @@ what the options name, everything by default.
|
||||
|
||||
| Option | Effect |
|
||||
|--------|--------|
|
||||
| `--platform NAME` | Install for this platform instead of the detected one. A system `--list-platforms` names under its own files (`muos`, `arkos`) stands for the file list it gets, and the run says which. Unknown names are refused with the closest ones |
|
||||
| `--dest PATH` | Destination directory, overriding detection. Without `--platform`, the file list is the platform detected here: at a terminal the installer asks, Enter taking the one found, and otherwise it uses the one found, RetroArch's when nothing is found, and refuses when several are. The run prints whose list it is. A folder that does not exist is named with the nearest one that does, then created. When more than its last level is missing (a mistyped path), a run at a terminal asks first, and the answer defaults to no |
|
||||
| `--target NAME` | Keep only the files the cores of that hardware target need, and the systems they emulate. An unknown target is refused rather than ignored, since carrying on would install everything. On Batocera, Recalbox, ROCKNIX and Lakka the board is read from the system (`batocera.arch`, `recalbox.arch`, `HW_DEVICE`, `LIBREELEC_DEVICE` or `LIBREELEC_PROJECT`) and applied when no `--target` is given |
|
||||
| `--every-board` | Install the files of every board instead of the one the system names, for a card that moves between devices |
|
||||
| `--installed-cores` | Keep only what the cores already installed read (RetroArch, Lakka, RetroPie). See [Installed cores](#installed-cores) |
|
||||
| `--cores-dir PATH` | The folder RetroArch loads its cores from, for `--installed-cores` when it is not found. Implies `--installed-cores` |
|
||||
| `--save-dir PATH` | The frontend's save directory (RetroArch's `savefile_directory`), for the files some cores read there, when the run cannot find it. The core's own folder is still added below it when the frontend sorts saves by core |
|
||||
| `--system NAMES` | Install only these systems. Comma-separated or repeated. `--list-systems` prints the names, `other` included. A short name the platform does not use (`gba`, `psx`) is refused with the ids it may stand for, never taken in their place |
|
||||
| `--core NAMES` | Keep the platform's own list and only the extra files these cores load. `--list-cores` prints the names |
|
||||
| `--emulator NAMES` | Install what these emulators read on the detected platform, and nothing else: their own files and the platform's files of the systems they run. Each emulator page on the site gives this command. `--list-emulators` prints the names, buildbot names such as `mednafen_psx` included |
|
||||
| `--region LIST` | Preferred regions, best first (`us,eu,jp`): keep one BIOS per region where a system has several. Accepts the names and aliases of `generate_pack.py --region` |
|
||||
| `--no-game-data` | Leave out game data (a game's own files, not firmware) and install the firmware only. What it leaves out is named. See [Firmware without game data](#firmware-without-game-data) |
|
||||
| `--list-systems` | Print a platform's systems with the files and size each holds, the files other systems share too counted apart |
|
||||
| `--list-cores` | Print the cores whose extra files a platform installs |
|
||||
| `--list-emulators` | Print every name `--emulator` accepts, each buildbot name beside the emulator it stands for |
|
||||
| `--romm-firmware TEMPLATE` | RomM: where each platform's firmware goes in the library, as `filesystem.structure.firmware` in RomM's `config.yml` says (`{platform}/bios` for a library laid out as `{platform}/roms`). Read from `config.yml` beside the library when it is there. See [RomM firmware layout](#romm-firmware-layout) |
|
||||
| `--no-input` | Never ask a question, even at a terminal |
|
||||
| `--check` | Report and exit without writing |
|
||||
| `--dry-run` | Show what would be downloaded, by system and with sizes, without writing anything |
|
||||
| `--yes`, `-y` | Do not ask before a download of 1 GB or more |
|
||||
| `--quiet`, `-q` | Print only failures and the final summary. Implies `--no-input` |
|
||||
| `--events` | Write the run as JSON events on standard output, one object per line, and the usual text on standard error. Implies `--no-input`. See [Machine-readable events](#machine-readable-events) |
|
||||
| `--from FOLDER` | Copy the BIOS files a folder holds (an old SD card, a backup, a ZIP) instead of downloading them, each one recognised by its hash and copied, never moved. Repeat for several folders. See [Copy from an old card](#copy-from-an-old-card) |
|
||||
| `--from-pack PATH` | Install from a pack already downloaded: its ZIP, one of its parts (the others are found in the same folder), or the folder holding them. Makes no network request and checks every file's hash. See [Without a network](#without-a-network-offline-install) |
|
||||
| `--manifest FILE` | Read the file list from FILE, a copy of `install/<platform>.json`, instead of downloading it. See [Without a network](#without-a-network-offline-install) |
|
||||
| `--restore FOLDER` | Put back the files a run replaced and kept in FOLDER, a dated folder inside `retrobios-backup`. Takes no other option but `--dry-run` and `--no-input`. See [Files you already had](#files-you-already-had) |
|
||||
| `--tui` | Open the full-screen interface, or stop with the reason it cannot open here (code 2) instead of falling back to the questions. Wins over `RETROBIOS_PLAIN` and `ACCESSIBLE`. See [The full-screen interface](#the-full-screen-interface) |
|
||||
| `--plain` | Ask the questions line by line instead of the full-screen interface, the mode for a screen reader. See [The full-screen interface](#the-full-screen-interface) |
|
||||
| `--version` | Print the installer's identity, the first 12 hex digits of the SHA-256 the bootstraps pin |
|
||||
| `--list-platforms` | Print the supported platforms and what was detected here |
|
||||
| `--list-targets` | Print the hardware targets a platform publishes, with the core count each carries |
|
||||
| `--jobs N`, `-j N` | Parallel downloads, 1 to 32, default 8 |
|
||||
| `--verbose`, `-v` | Print per-attempt failures and hash mismatches |
|
||||
| `--standalone-copies` | Also copy into detected standalone emulator directories |
|
||||
| `--help`, `-h` | Print the option list, a few examples in the form the run was started with, and the address of this page |
|
||||
| <span id="option-platform"></span>`--platform NAME` | Install for this platform instead of the detected one. A system `--list-platforms` names under its own files (`muos`, `arkos`) stands for the file list it gets, and the run says which. Unknown names are refused with the closest ones |
|
||||
| <span id="option-dest"></span>`--dest PATH` | Destination directory, overriding detection. Without `--platform`, the file list is the platform detected here: at a terminal the installer asks, Enter taking the one found, and otherwise it uses the one found, RetroArch's when nothing is found, and refuses when several are. The run prints whose list it is. A folder that does not exist is named with the nearest one that does, then created. When more than its last level is missing (a mistyped path), a run at a terminal asks first, and the answer defaults to no |
|
||||
| <span id="option-target"></span>`--target NAME` | Keep only the files the cores of that hardware target need, and the systems they emulate. An unknown target is refused rather than ignored, since carrying on would install everything. On Batocera, Recalbox, ROCKNIX and Lakka the board is read from the system (`batocera.arch`, `recalbox.arch`, `HW_DEVICE`, `LIBREELEC_DEVICE` or `LIBREELEC_PROJECT`) and applied when no `--target` is given |
|
||||
| <span id="option-every-board"></span>`--every-board` | Install the files of every board instead of the one the system names, for a card that moves between devices |
|
||||
| <span id="option-installed-cores"></span>`--installed-cores` | Keep only what the cores already installed read (RetroArch, Lakka, RetroPie). See [Installed cores](#installed-cores) |
|
||||
| <span id="option-cores-dir"></span>`--cores-dir PATH` | The folder RetroArch loads its cores from, for `--installed-cores` when it is not found. Implies `--installed-cores` |
|
||||
| <span id="option-save-dir"></span>`--save-dir PATH` | The frontend's save directory (RetroArch's `savefile_directory`), for the files some cores read there, when the run cannot find it. The core's own folder is still added below it when the frontend sorts saves by core |
|
||||
| <span id="option-system"></span>`--system NAMES` | Install only these systems. Comma-separated or repeated. `--list-systems` prints the names, `other` included. A short name the platform does not use (`gba`, `psx`) is refused with the ids it may stand for, never taken in their place |
|
||||
| <span id="option-core"></span>`--core NAMES` | Keep the platform's own list and only the extra files these cores load. `--list-cores` prints the names |
|
||||
| <span id="option-emulator"></span>`--emulator NAMES` | Install what these emulators read on the detected platform, and nothing else: their own files and the platform's files of the systems they run. Each emulator page on the site gives this command. `--list-emulators` prints the names, buildbot names such as `mednafen_psx` included |
|
||||
| <span id="option-region"></span>`--region LIST` | Preferred regions, best first (`us,eu,jp`): keep one BIOS per region where a system has several. Accepts the names and aliases of `generate_pack.py --region` |
|
||||
| <span id="option-no-game-data"></span>`--no-game-data` | Leave out game data (a game's own files, not firmware) and install the firmware only. What it leaves out is named. See [Firmware without game data](#firmware-without-game-data) |
|
||||
| <span id="option-list-systems"></span>`--list-systems` | Print a platform's systems with the files and size each holds, the files other systems share too counted apart |
|
||||
| <span id="option-list-cores"></span>`--list-cores` | Print the cores whose extra files a platform installs |
|
||||
| <span id="option-list-emulators"></span>`--list-emulators` | Print every name `--emulator` accepts, each buildbot name beside the emulator it stands for |
|
||||
| <span id="option-romm-firmware"></span>`--romm-firmware TEMPLATE` | RomM: where each platform's firmware goes in the library, as `filesystem.structure.firmware` in RomM's `config.yml` says (`{platform}/bios` for a library laid out as `{platform}/roms`). Read from `config.yml` beside the library when it is there. See [RomM firmware layout](#romm-firmware-layout) |
|
||||
| <span id="option-no-input"></span>`--no-input` | Never ask a question, even at a terminal |
|
||||
| <span id="option-check"></span>`--check` | Report and exit without writing |
|
||||
| <span id="option-dry-run"></span>`--dry-run` | Show what would be downloaded, by system and with sizes, without writing anything |
|
||||
| <span id="option-yes"></span>`--yes`, `-y` | Do not ask before a download of 1 GB or more |
|
||||
| <span id="option-quiet"></span>`--quiet`, `-q` | Print only failures and the final summary. Implies `--no-input` |
|
||||
| <span id="option-events"></span>`--events` | Write the run as JSON events on standard output, one object per line, and the usual text on standard error. Implies `--no-input`. See [Machine-readable events](#machine-readable-events) |
|
||||
| <span id="option-from"></span>`--from FOLDER` | Copy the BIOS files a folder holds (an old SD card, a backup, a ZIP) instead of downloading them, each one recognised by its hash and copied, never moved. Repeat for several folders. See [Copy from an old card](#copy-from-an-old-card) |
|
||||
| <span id="option-from-pack"></span>`--from-pack PATH` | Install from a pack already downloaded: its ZIP, one of its parts (the others are found in the same folder), or the folder holding them. Makes no network request and checks every file's hash. See [Without a network](#without-a-network-offline-install) |
|
||||
| <span id="option-manifest"></span>`--manifest FILE` | Read the file list from FILE, a copy of `install/<platform>.json`, instead of downloading it. See [Without a network](#without-a-network-offline-install) |
|
||||
| <span id="option-restore"></span>`--restore FOLDER` | Put back the files a run replaced and kept in FOLDER, a dated folder inside `retrobios-backup`. Takes no other option but `--dry-run` and `--no-input`. See [Files you already had](#files-you-already-had) |
|
||||
| <span id="option-tui"></span>`--tui` | Open the full-screen interface, or stop with the reason it cannot open here (code 2) instead of falling back to the questions. Wins over `RETROBIOS_PLAIN` and `ACCESSIBLE`. See [The full-screen interface](#the-full-screen-interface) |
|
||||
| <span id="option-plain"></span>`--plain` | Ask the questions line by line instead of the full-screen interface, the mode for a screen reader. See [The full-screen interface](#the-full-screen-interface) |
|
||||
| <span id="option-version"></span>`--version` | Print the installer's identity, the first 12 hex digits of the SHA-256 the bootstraps pin |
|
||||
| <span id="option-list-platforms"></span>`--list-platforms` | Print the supported platforms and what was detected here |
|
||||
| <span id="option-list-targets"></span>`--list-targets` | Print the hardware targets a platform publishes, with the core count each carries |
|
||||
| <span id="option-jobs"></span>`--jobs N`, `-j N` | Parallel downloads, 1 to 32, default 8 |
|
||||
| <span id="option-verbose"></span>`--verbose`, `-v` | Print per-attempt failures and hash mismatches |
|
||||
| <span id="option-standalone-copies"></span>`--standalone-copies` | Also copy into detected standalone emulator directories |
|
||||
| <span id="option-help"></span>`--help`, `-h` | Print the option list, a few examples in the form the run was started with, and the address of this page |
|
||||
|
||||
`--check` reads the same file list, hashes the destination and exits, with
|
||||
code 1 when a file is missing or does not match and 0 when every one does. Files
|
||||
@@ -1064,36 +1109,6 @@ the [one-line command](#one-line) there with
|
||||
`--platform <platform> --dest <BIOS folder on the card>`. This works for
|
||||
every system above too.
|
||||
|
||||
## Without a network (offline install) { #without-a-network-offline-install }
|
||||
|
||||
<span id="without-a-network"></span>
|
||||
|
||||
A pack downloaded once installs again with no network, on this machine or on
|
||||
one that has none:
|
||||
|
||||
```bash
|
||||
python install.py --from-pack ~/Downloads/RetroArch_v1.22.2_BIOS_Pack.zip
|
||||
python install.py --from-pack ~/Downloads/RetroArch_v1.22.2_BIOS_Pack.part1of3.zip
|
||||
python install.py --from-pack ~/Downloads --dest /media/card/retroarch/system
|
||||
```
|
||||
|
||||
One part is enough to name a split pack: the others are taken from the same
|
||||
folder, and a part that is not there is named before anything is written.
|
||||
The platform is read from the pack's name when `--platform` is not given. The
|
||||
file list is the one the pack carries (`manifest.json`), with each file's
|
||||
SHA-1 and size, and every file is checked against it before it takes its
|
||||
place. A data tree (Dolphin's `Sys/`, PPSSPP's assets) comes from the pack
|
||||
too, not from its source archive. `--target` reads a core list from the
|
||||
network and is refused with `--from-pack`. A board found on the device is not
|
||||
applied either: every board's files are installed.
|
||||
|
||||
`--manifest FILE` reads the file list from a copy of `install/<platform>.json`
|
||||
instead of downloading it. Alone, the files still come from the repository.
|
||||
With `--from-pack`, the run reads that list and takes every file from the
|
||||
pack: `--system`, `--core`, `--region` and `--no-game-data` then apply as
|
||||
they do online, and a file of the list the pack does not hold fails as
|
||||
`not in the pack`. The list's platform has to be the one installed for.
|
||||
|
||||
## Where the files come from
|
||||
|
||||
Each manifest entry names either `repo_path`, served from the repository at the
|
||||
@@ -1216,11 +1231,11 @@ install exits `143`. During an install it stops the install first, and exits
|
||||
`130`.
|
||||
|
||||
`install.ps1` passes the installer's code on unchanged: run as a file it exits
|
||||
with it, run through `irm \| iex` it sets `$LASTEXITCODE` (an `exit` there
|
||||
with it, run through `irm | iex` it sets `$LASTEXITCODE` (an `exit` there
|
||||
would close the PowerShell window). It declares no parameter of its own, so
|
||||
every argument reaches `install.py` as typed, short forms such as `-v`
|
||||
included. Its body runs in a script block of its own,
|
||||
so `irm \| iex` leaves neither its variables nor its `$ErrorActionPreference`
|
||||
so `irm | iex` leaves neither its variables nor its `$ErrorActionPreference`
|
||||
in the session.
|
||||
|
||||
Failures that are per-file, such as a download exhausting its three attempts,
|
||||
@@ -1263,6 +1278,17 @@ existing kind: a reader skips a kind it does not know. An option refused
|
||||
before the run starts (`install.py: error:`, code 2) writes
|
||||
no event at all. A program reads a missing `end` as that case.
|
||||
|
||||
Five lines of a run of `--check --events --system sony-playstation` on a
|
||||
RetroArch folder that holds only `scph5501.bin`, in the order written:
|
||||
|
||||
```json
|
||||
{"event": "start", "schema": 1, "installer": "c90676dcc433", "mode": "check"}
|
||||
{"event": "file", "platform": "retroarch", "dest": "scph5501.bin", "systems": ["sony-playstation"], "size": 524288, "state": "present"}
|
||||
{"event": "check", "platform": "retroarch", "name": "RetroArch", "bios_dir": "system", "files": 118, "present": 1, "missing": 117, "wrong_hash": 0, "bytes_needed": 344457216, "data_missing": 0, "not_installable": 0}
|
||||
{"event": "summary", "mode": "check", "installed": 0, "up_to_date": 1, "pending": 117, "failed": [], "skipped": [], "left_out": [], "kept": [], "interrupted": false}
|
||||
{"event": "end", "code": 1}
|
||||
```
|
||||
|
||||
## Environment overrides
|
||||
|
||||
| Variable | Effect |
|
||||
|
||||
@@ -227,11 +227,17 @@ python scripts/split_pack.py dist/
|
||||
# check_release.py compares each asset with its line there.
|
||||
python scripts/build_port.py
|
||||
|
||||
# 2c. The installer and the file lists step 1 wrote, so a package can pin
|
||||
# one release (installer page, "For packagers"). check_release.py
|
||||
# refuses a release that lacks one of them.
|
||||
cp install.py install.sh install.ps1 install/*.json dist/
|
||||
|
||||
# 3. Checksums of the files as published, then sign the list. The checksums
|
||||
# answer corruption, and the signature answers a rewritten release, which is
|
||||
# the one thing a checksum published beside its own artifacts cannot
|
||||
# answer.
|
||||
(cd dist && sha256sum *.zip RetroBIOS.muxapp > SHA256SUMS.txt)
|
||||
(cd dist && sha256sum *.zip RetroBIOS.muxapp install.py install.sh install.ps1 \
|
||||
$(cd ../install && ls *.json) > SHA256SUMS.txt)
|
||||
ssh-keygen -Y sign -f ~/.ssh/retrobios_signing -n file dist/SHA256SUMS.txt
|
||||
|
||||
# 4. Record what each pack holds, read from the archives just checked:
|
||||
@@ -253,7 +259,8 @@ python scripts/release_notes.py --whats-new tmp/whats-new.md --output tmp/notes.
|
||||
# A public release with half its assets is a broken download for everyone
|
||||
# during the whole upload.
|
||||
gh release create "v${DATE}" --draft --title "BIOS Pack v${DATE}" --notes-file tmp/notes.md
|
||||
for f in dist/SHA256SUMS.txt dist/SHA256SUMS.txt.sig dist/*.zip dist/RetroBIOS.muxapp; do
|
||||
for f in dist/SHA256SUMS.txt dist/SHA256SUMS.txt.sig dist/*.zip dist/RetroBIOS.muxapp \
|
||||
dist/install.py dist/install.sh dist/install.ps1 $(ls install/*.json | sed 's|^install/|dist/|'); do
|
||||
[ -f "$f" ] && gh release upload "v${DATE}" "$f#$(basename "$f")" --clobber
|
||||
done
|
||||
# A count does not see a failed upload beside a stray file, nor a part
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
level: reference
|
||||
order: 120
|
||||
terms: mame version, romset version, which mame
|
||||
---
|
||||
|
||||
# Technical questions
|
||||
@@ -120,6 +121,29 @@ per catalog and the full list of catalogued dumps still missing.
|
||||
|
||||
## Which MAME version do the arcade BIOS sets match?
|
||||
|
||||
Your game sets and BIOS sets must come from the same version as your arcade
|
||||
emulator.
|
||||
|
||||
| Emulator, as RetroArch names it | Sets from |
|
||||
|---|---|
|
||||
| MAME 2000 | MAME 0.37b5 |
|
||||
| MAME 2003, MAME 2003 Midway | MAME 0.78 |
|
||||
| MAME 2003-Plus | MAME 0.78, with later fixes |
|
||||
| MAME 2009 | MAME 0.135u4 |
|
||||
| MAME 2010 | MAME 0.139 |
|
||||
| MAME 2014, MAME 2015 | MAME 0.160 |
|
||||
| MAME 2016 | MAME 0.174 |
|
||||
| MAME/MESS | MAME 0.287 |
|
||||
| MAME, MAME/Arcade | MAME 0.289 |
|
||||
| FB Alpha 2012 CPS-1, FB Alpha 2012 CPS-2 | FB Alpha 0.2.97.28 |
|
||||
| FB Alpha 2012, FB Alpha 2012 CPS-3, FB Alpha 2012 Neo Geo | FB Alpha 0.2.97.29 |
|
||||
| FB Alpha 2019 | FB Alpha 0.2.97.44 |
|
||||
| FinalBurn Neo, FinalBurn Neo (Neo-Geo), FinalBurn Neo (CPS-1 / CPS-2) | FinalBurn Neo 1.0.0.2 |
|
||||
|
||||
Each emulator's page names its version too.
|
||||
|
||||
### How this is known
|
||||
|
||||
Arcade BIOS sets are coupled to the romset version, so there is one
|
||||
profile per MAME core generation: MAME 2000 (0.37b5), MAME 2003 (0.78),
|
||||
MAME 2003-Plus, MAME 2009 (0.135u4), MAME 2010 (0.139), MAME 2015
|
||||
|
||||
@@ -57,7 +57,7 @@ Every module in `tests/`, by the area it guards.
|
||||
| Installer and downloads | loopback server | `test_check_release`, `test_download`, `test_install`, `test_install_cores`, `test_install_experience`, `test_install_from`, `test_install_offline`, `test_install_selection`, `test_install_tui` |
|
||||
| Handheld launchers | a pty as the console, fake installer and firmware tools, stubbed PortMaster dialogs | `test_handheld_launchers`, `test_port` |
|
||||
| Truth and native export | synthetic + cached originals | `test_export_counts`, `test_export_native_layer`, `test_export_report`, `test_exporters`, `test_truth_data_dir_readers`, `test_truth_diff`, `test_truth_layout` |
|
||||
| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_r2_components`, `test_site_r2_css`, `test_site_r2_emulators`, `test_site_r2_entry`, `test_site_r2_gate`, `test_site_r2_nav`, `test_site_r2_p0`, `test_site_r2_reference`, `test_site_r2_systems`, `test_site_r2_wiki`, `test_site_r3_collection`, `test_site_r3_components`, `test_site_r3_css`, `test_site_r3_emulators`, `test_site_r3_entry`, `test_site_r3_nav`, `test_site_r3_p0`, `test_site_r3_reference`, `test_site_r3_systems`, `test_site_r3_wiki`, `test_site_r4_components`, `test_site_r4_css`, `test_site_r4_p0`, `test_site_r4_search`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_source_ref_targets`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
|
||||
| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_r2_components`, `test_site_r2_css`, `test_site_r2_emulators`, `test_site_r2_entry`, `test_site_r2_gate`, `test_site_r2_nav`, `test_site_r2_p0`, `test_site_r2_reference`, `test_site_r2_systems`, `test_site_r2_wiki`, `test_site_r3_collection`, `test_site_r3_components`, `test_site_r3_css`, `test_site_r3_emulators`, `test_site_r3_entry`, `test_site_r3_nav`, `test_site_r3_p0`, `test_site_r3_reference`, `test_site_r3_systems`, `test_site_r3_wiki`, `test_site_r4_components`, `test_site_r4_css`, `test_site_r4_p0`, `test_site_r4_search`, `test_site_r4_wiki`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_source_ref_targets`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
|
||||
|
||||
## Test architecture
|
||||
|
||||
|
||||
+20
-11
@@ -1,6 +1,7 @@
|
||||
---
|
||||
level: start
|
||||
order: 40
|
||||
terms: bios not found, bios missing, firmware missing, bios error
|
||||
---
|
||||
|
||||
# When a game does not start
|
||||
@@ -11,7 +12,13 @@ Most often a BIOS file is missing or wrong.
|
||||
|
||||
<span id="game-wont-start-black-screen"></span><span id="my-setup-says-untested"></span>
|
||||
|
||||
Run the command with `--check`. It changes nothing.
|
||||
<!-- Words as emulators print them: snes9x bsx.cpp:1232 "BS: BIOS not found!"
|
||||
(1bcc369e), puNES src/core/fds.c:312 "bios not found" (b1758ec2), Gearcoleco
|
||||
platforms/libretro/libretro.cpp:866 "OS-7 BIOS missing" (23d0ec28), RetroArch
|
||||
intl/msg_hash_us.h:32805 "One or more firmware files are missing" (5333a7c5). -->
|
||||
|
||||
Your emulator says **BIOS not found**, **BIOS missing** or **firmware
|
||||
missing**? Run the command with `--check`. It changes nothing.
|
||||
|
||||
<!-- os-tabs -->
|
||||
```sh
|
||||
@@ -36,37 +43,39 @@ Run it without `--check`. It adds missing files and replaces wrong ones.
|
||||
## 3. Check the folder and the names
|
||||
|
||||
- **Right folder?** [Get your files](../which-pack.md) names it.
|
||||
- **Exact name?** `scph5501.bin` and `SCPH-5501.BIN` differ. On Linux,
|
||||
capitals count too.
|
||||
- **Exact name?** `scph5501.bin` and `SCPH-5501.BIN` differ.
|
||||
- **A `bios` folder inside your BIOS folder?** Run the installer: it copies
|
||||
those files up one level.
|
||||
- **Unzipped by hand on Linux?** The command adds names that differ by
|
||||
capitals.
|
||||
- **BIOS setting on a subfolder, like `system/dc`?** Point it at the BIOS
|
||||
folder. Emulators add their own, like `dc/` for Flycast.
|
||||
- **A ZIP in parts, like `Pack.part1of2.zip`?** Unzip every part into one
|
||||
folder.
|
||||
|
||||
<!-- Flycast's libretro port appends "dc/" to the system directory:
|
||||
shell/libretro/libretro.cpp:2252-2253, emulators/flycast.yml. -->
|
||||
|
||||
## An arcade game does not start
|
||||
|
||||
It needs its BIOS set, like `neogeo.zip`, and a game set for the same emulator
|
||||
version.
|
||||
|
||||
- Keep `neogeo.zip` zipped.
|
||||
- A game set for another MAME version often fails.
|
||||
Details: [the version of each emulator](../emulators/index.md).
|
||||
- Run the command once. It installs the BIOS sets your emulators read.
|
||||
- Another MAME version's game set often fails.
|
||||
Details: [the version of each emulator](technical-faq.md#which-mame-version-do-the-arcade-bios-sets-match).
|
||||
- Run the command once. It installs the BIOS sets.
|
||||
|
||||
## The BIOS is fine, the game still fails
|
||||
|
||||
- **Another emulator?** Each reads its own files, listed on its page.
|
||||
- **A BIOS setting?** Some emulators can skip the BIOS. In PCSX ReARMed, set
|
||||
**BIOS Selection** to **Auto**.
|
||||
- **A disc in several files?** Open its `.cue` or `.m3u`, not a `.bin`.
|
||||
- **The screen says "Please insert PlayStation CD-ROM"?** The BIOS works.
|
||||
The game did not load: open its `.cue` or `.m3u` file, not a `.bin`.
|
||||
|
||||
## If the command fails { #installation-script-fails }
|
||||
|
||||
- **Network error:** try again later.
|
||||
- **`The clock reads ...`:** set the right date.
|
||||
- **Permission denied:** do what the message says. It names what to run.
|
||||
- **Permission denied:** do what the message says.
|
||||
- **No setup found:** name it and its folder:
|
||||
|
||||
<!-- os-tabs windows: --platform retroarch --dest C:\path\to\RetroArch\system -->
|
||||
|
||||
Reference in new issue
Block a user