diff --git a/tests/test_doc_surfaces.py b/tests/test_doc_surfaces.py index 86c51ed9..8c7ec4c3 100644 --- a/tests/test_doc_surfaces.py +++ b/tests/test_doc_surfaces.py @@ -218,7 +218,7 @@ class EndUsersCheckTheirOwnFolder(unittest.TestCase): files points at the installer's --check, which hashes their folder.""" USER_SECTIONS = ( - ("wiki/troubleshooting.md", "## 1. Check your BIOS folder { #checking-what-you-installed }"), + ("wiki/troubleshooting.md", "## 1. Check my BIOS folder { #checking-what-you-installed }"), ("wiki/troubleshooting.md", "## 2. Fix the files"), ("wiki/faq.md", "## My game shows a black screen"), ("wiki/faq.md", "## How do I know which BIOS I need?"), diff --git a/tests/test_site_r3_wiki.py b/tests/test_site_r3_wiki.py new file mode 100644 index 00000000..78970435 --- /dev/null +++ b/tests/test_site_r3_wiki.py @@ -0,0 +1,302 @@ +"""Wiki copy (round 3, package 5). + +Each class names the finding it holds: a legal page that opened on case law, +no arcade answer where players look, a Dreamcast "yes" the Dreamcast +emulator contradicts, an offline install no one could find, setups with no +page of their own, link labels that named another page, an exit status that +could not tell missing files from a failure, and FAQ answers the community +asks for (region and language, keys and firmware). Every claim the copy +makes about an emulator or the installer is held against the repository. +""" + +from __future__ import annotations + +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)) + +from tests import sitepages # noqa: E402 + +WIKI = REPO_ROOT / "wiki" +FENCED = re.compile(r"^```.*?^```", re.S | re.M) + + +def _wiki(name: str) -> str: + return (WIKI / name).read_text(encoding="utf-8") + + +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():] + end = re.search(r"(?m)^#{1,2} ", FENCED.sub(lambda m: re.sub(r"[^\n]", " ", m.group(0)), rest)) + return rest[: end.start()] if end else rest + + +def _flat(text: str) -> str: + return " ".join(text.split()) + + +def _headings(text: str) -> list[str]: + return re.findall(r"(?m)^#{2,3} (.+?)\s*$", FENCED.sub("", text)) + + +def _profile(name: str) -> dict: + return yaml.safe_load((REPO_ROOT / "emulators" / f"{name}.yml").read_text(encoding="utf-8")) + + +def _entry(profile: dict, name: str) -> dict: + for entry in profile.get("files", []): + if entry.get("name") == name: + return entry + raise AssertionError(f"{name} not in the profile") + + +class LegalOpensInPlainWords(unittest.TestCase): + """C5: Is it legal? is a Start here page and opened on case law.""" + + def setUp(self): + self.text = _wiki("legal.md") + self.opening = self.text.split("## The reasoning in detail", 1)[0] + + def test_the_answer_comes_before_the_reasoning(self): + self.assertIn("## The reasoning in detail", self.text) + self.assertIn("It depends on your country. Nothing here is legal advice.", self.opening) + for case in ("Connectix", "Accolade", "DMCA", "CFR", "Chamberlain", "Yuzu"): + with self.subTest(case=case): + self.assertNotIn(case, self.opening) + self.assertIn(case, self.text) + + def test_the_plain_lines_hold_against_the_detail(self): + opening = _flat(self.opening) + self.assertIn("encryption keys (Switch, 3DS, Wii U)", opening) + self.assertIn("weakest part of the project's position", self.text) + self.assertIn("issues/new", opening) + self.assertIn("path or hash", opening) + self.assertIn("identifying the file by path or hash", _flat(self.text)) + + def test_the_detail_sits_under_the_reasoning_heading(self): + detail = self.text.split("## The reasoning in detail", 1)[1] + detail = detail.split("## Asking for a file to be removed", 1)[0] + self.assertEqual(re.findall(r"(?m)^## ", detail), []) + self.assertIn("### Emulation and BIOS redistribution", detail) + + def test_a_contact_section_names_only_a_channel_that_exists(self): + section = _section(self.text, "Contact") + self.assertIn("https://github.com/Abdess/retrobios/issues", section) + self.assertNotIn("@", section) + + +class ArcadeAnswerWherePlayersLook(unittest.TestCase): + """C3, N3: the romset answer sat in Reference only.""" + + def test_troubleshooting_answers_after_step_three(self): + text = _wiki("troubleshooting.md") + headings = _headings(text) + self.assertIn("An arcade game does not start", headings) + self.assertGreater(headings.index("An arcade game does not start"), + 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."): + 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") + 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")) + self.assertLessEqual(len(re.findall(r"[.!?](?:\s|$)", section)), 2) + + def test_emulator_pages_name_the_version_the_copy_promises(self): + for name in ("mame2003", "mame2000", "fbneo"): + with self.subTest(profile=name): + profile = _profile(name) + self.assertTrue(profile.get("core_version") or profile.get("version")) + + def test_the_check_step_holds_the_searched_words(self): + self.assertIn("## 1. Check my BIOS folder { #checking-what-you-installed }", + _wiki("troubleshooting.md")) + + +class BiosFineWhatNext(unittest.TestCase): + """Community need 11: the page assumed the BIOS was the cause.""" + + def setUp(self): + self.section = _flat(_section(_wiki("troubleshooting.md"), + "The BIOS is fine, the game still fails")) + + def test_the_three_causes(self): + for words in ("Another emulator?", "A BIOS setting?", ".cue", ".m3u"): + with self.subTest(words=words): + self.assertIn(words, self.section) + + def test_the_named_setting_is_the_core_option(self): + # libretro/pcsx_rearmed frontend/libretro_core_options.h: key + # pcsx_rearmed_bios, label "BIOS Selection", values Auto and HLE. + self.assertIn("**BIOS Selection** to **Auto**", self.section) + notes = _profile("pcsx_rearmed").get("notes", "") + self.assertIn('pcsx_rearmed_bios = "HLE"', notes) + + +class DreamcastIsBetterWithIt(unittest.TestCase): + """C7: "Yes for Dreamcast" while Flycast runs without dc_boot.bin.""" + + def test_flycast_falls_back(self): + entry = _entry(_profile("flycast"), "dc_boot.bin") + self.assertFalse(entry.get("required")) + self.assertTrue(entry.get("hle_fallback")) + + def test_no_page_says_yes_for_dreamcast(self): + for name in ("faq.md", "what-are-bios-files.md"): + text = _wiki(name) + with self.subTest(page=name): + yes = next(line for line in text.splitlines() if line.startswith("- **Yes")) + self.assertNotIn("Dreamcast", yes) + self.assertRegex(_flat(text), r"\*\*Better with it:?\*\*:? (for )?Dreamcast") + + +class OfflineInstallIsFindable(unittest.TestCase): + """N7: no page linked the offline install, and search did not find it.""" + + ANCHOR = "without-a-network-offline-install" + + def test_the_installer_heading_says_offline(self): + text = _wiki("installer.md") + self.assertIn(f"## Without a network (offline install) {{ #{self.ANCHOR} }}", text) + # Links written before the rename still land. + self.assertIn('', text) + self.assertNotIn("](#without-a-network)", text) + + def test_the_faq_answers_next_to_the_sd_card(self): + text = _wiki("faq.md") + headings = _headings(text) + self.assertEqual(headings.index("No internet on the device?"), + headings.index("Fill an SD card from a computer?") + 1) + section = _section(text, "No internet on the device?") + self.assertIn("--from-pack", section) + self.assertIn(f"installer.md#{self.ANCHOR}", section) + + def test_from_pack_is_an_installer_option(self): + self.assertIn('"--from-pack"', (REPO_ROOT / "install.py").read_text(encoding="utf-8")) + + +class SetupsWithoutAPage(unittest.TestCase): + """N9, J13: ES-DE, LaunchBox and iOS found nothing.""" + + def test_the_answer(self): + section = _flat(_section(_wiki("faq.md"), "My setup is not in the list")) + for words in ("--dest", "ES-DE", "LaunchBox", "iPhone"): + with self.subTest(words=words): + self.assertIn(words, section) + # No folder is written: the copy names none it cannot cite. + self.assertNotRegex(section, r"[~/\\][A-Za-z]") + + def test_the_installer_fills_the_emulators_a_launcher_starts(self): + installer = _flat(_wiki("installer.md")) + self.assertIn("they reference emulators rather than owning a BIOS directory", installer) + self.assertIn("the installer fills their own BIOS folders", installer) + + +class LinkLabelsMatchPages(unittest.TestCase): + """N5: "Your setup's page" landed on a page titled Get your files.""" + + def test_links_to_get_your_files_carry_its_name(self): + for name in ("first-install.md", "troubleshooting.md", "faq.md"): + with self.subTest(page=name): + for label in re.findall(r"\[([^\]]+)\]\(\.\./which-pack\.md\)", _wiki(name)): + self.assertEqual(label, "Get your files") + + +class ExitStatusAndPinnedInstaller(unittest.TestCase): + """D10, J12: --check exits 1 for missing files and for failures, and a + packager had no fixed installer to point at.""" + + def setUp(self): + self.text = _wiki("installer.md") + self.source = (REPO_ROOT / "install.py").read_text(encoding="utf-8") + + def test_the_check_event_tells_missing_from_failure(self): + section = _flat(_section(self.text, "Exit status")) + self.assertIn("read the `check` event", section) + self.assertIn("`missing` and `wrong_hash` count what is left", section) + fields = re.search(r'"check": \(([^)]*)\)', self.source).group(1) + for field in ("missing", "wrong_hash"): + with self.subTest(field=field): + self.assertIn(f'"{field}"', fields) + + def test_a_packager_pins_installer_and_lists_to_one_tag(self): + section = _section(self.text, "One line") + self.assertIn("/retrobios//install.py", section) + self.assertIn("RETROBIOS_REF=", section) + self.assertIn('os.environ.get("RETROBIOS_REF"', self.source) + + +class SmallCopyFixes(unittest.TestCase): + """C P3: the SD card example and the end line of a run.""" + + 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) + 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) + self.assertIn("--dest E:\\retroarch\\cores\\system", section) + + def test_the_end_line_is_the_printed_one(self): + section = _section(_wiki("first-install.md"), "5. Read the last line") + self.assertIn("`N files installed, M already up to date.`", section) + source = (REPO_ROOT / "install.py").read_text(encoding="utf-8") + self.assertIn("installed, \"\n f\"{_plural(up_to_date, 'file')} already up to date.", source) + + +class CommunityAnswers(unittest.TestCase): + """Community needs 6, 10 and 15: region and language, keys versus + firmware versus BIOS, zip or unzip.""" + + def setUp(self): + self.faq = _wiki("faq.md") + + def test_language_comes_from_the_game(self): + section = _flat(_section(self.faq, "Does the BIOS set the game's language?")) + self.assertIn("comes from the game", section) + self.assertIn("Flycast's **Language** option", section) + + def test_keys_firmware_bios(self): + section = _flat(_section(self.faq, "Keys, firmware or BIOS?")) + for name, profile in (("scph5501.bin", "duckstation"), ("PS3UPDAT.PUP", "rpcs3"), + ("prod.keys", "eden")): + with self.subTest(name=name): + self.assertIn(f"`{name}`", section) + _entry(_profile(profile), name) + self.assertIn("Firmware (system NCAs) must be installed through Eden's UI", + _profile("eden")["notes"]) + + def test_zip_or_unzip_stays(self): + self.assertIn("neogeo.zip", _section(self.faq, "Zip or unzip?")) + + +class StartPagesStayInBudget(unittest.TestCase): + """The built pages: the new answers fit the level 0 ceilings.""" + + def test_ceilings(self): + from tests import test_site_copy + + sitepages.require() + for name in ("wiki/faq.md", "wiki/troubleshooting.md", "wiki/first-install.md", + "wiki/what-are-bios-files.md"): + with self.subTest(page=name): + self.assertLessEqual(test_site_copy._load(name).words(), + test_site_copy.START_PAGES[name]) + + +if __name__ == "__main__": + unittest.main() diff --git a/wiki/faq.md b/wiki/faq.md index 0499bf38..3ae3cd47 100644 --- a/wiki/faq.md +++ b/wiki/faq.md @@ -7,74 +7,78 @@ order: 50 ## Do I need BIOS files? -For many consoles, yes: PlayStation 1 and 2, Saturn, Dreamcast, Neo Geo and -many home computers. For NES or Super Nintendo cartridges, usually not. +- **Yes** for PlayStation 1 and 2, Saturn and Neo Geo. +- **Better with it** for Dreamcast: some games need it. +- **Usually not** for NES or Super Nintendo cartridges. + [What is a BIOS?](what-are-bios-files.md) ## How do I know which BIOS I need? -Run the command with `--dry-run`. It lists what it would install and changes -nothing. +Run the command with `--dry-run`. It lists the files and changes nothing. ```sh curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh -s -- --dry-run ``` -Your setup's page lists every file. - ## Do I need every file of a console? -No. A console often has one BIOS per region. The ZIP holds them all. To keep -only yours, add your region, like `--region us`. +No. A console often has one BIOS per region. To keep only yours, add your +region, like `--region us`. + +## Does the BIOS set the game's language? + +Rarely. It comes from the game. Some games follow the console's setting, like +Flycast's **Language** option. ## Zip or unzip? { #zip-or-unzip } -Unzip the ZIP you downloaded once, into the folder your setup's page names. -Leave the ZIP files inside it as they are, like `neogeo.zip`. Arcade emulators -read them zipped. +Unzip the ZIP you downloaded into the folder your setup's page names. Leave +the ZIPs inside as they are, like `neogeo.zip`. ## Does a BIOS work on every setup? -The same file usually works everywhere. Some setups also check the version and -report one they do not expect. Arcade files like `neogeo.zip` change with the -emulator. The command installs the version your setup expects. +Usually. The command installs the version each setup expects, like its +`neogeo.zip`. + +## 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). + +## Keys, firmware or BIOS? + +- **BIOS** or **firmware**: the console's own program, like `scph5501.bin`. +- Some newer consoles need a system update, like `PS3UPDAT.PUP`, installed from the + emulator's menu. +- **Keys** unlock encrypted games, like `prod.keys` for Switch. ## Can I play without the BIOS? -Sometimes. Some emulators carry a stand-in for it. Some games then glitch or -do not start. +Sometimes. Some emulators carry a stand-in, and some games then fail. ## Is the command safe? { #is-piping-the-installer-into-a-shell-safe } -- It checks the installer's fingerprint first. If it does not match its expected - fingerprint, it stops. -- It checks each file before saving it. -- It writes in your BIOS folder. For some emulators it also adds files to your - save folder. +- It checks the installer's fingerprint, then each file before saving it. +- It writes in your BIOS folder, and for some emulators in your save folder. - It never changes or deletes a game or a save. -- It keeps any file it replaces in a `retrobios-backup` folder, next to your - BIOS folder. `--restore` puts it back - ([details](installer.md#files-you-already-had)). +- It keeps any file it replaces in `retrobios-backup`, next to your BIOS + folder. `--restore` puts it back. -Read the script first: [install.sh](https://github.com/Abdess/retrobios/blob/main/install.sh), -[install.ps1](https://github.com/Abdess/retrobios/blob/main/install.ps1) and +Read them first: [install.sh](https://github.com/Abdess/retrobios/blob/main/install.sh), +[install.ps1](https://github.com/Abdess/retrobios/blob/main/install.ps1), [install.py](https://github.com/Abdess/retrobios/blob/main/install.py). -Details: [How the installer checks itself](installer.md#what-the-bootstrap-does). +[How the installer checks itself](installer.md#what-the-bootstrap-does). ## Windows or my antivirus blocks the command -Download the ZIP of your setup instead, from [Get your files](../which-pack.md). -It holds the same files. - -## Can I run it again? - -Yes, to update. It downloads only what is missing or wrong. +Download your setup's ZIP from [Get your files](../which-pack.md) instead. ## Fill an SD card from a computer? -Yes. Name your setup and the BIOS folder on the card. On Windows, `E:` stands -for the card. +Yes. Name your setup and the BIOS folder on the card. For RetroArch on a Switch +card in drive `E:`: ```sh @@ -82,24 +86,28 @@ curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh \ | sh -s -- --platform retroarch --dest /path/to/sdcard/retroarch/cores/system ``` +## No internet on the device? + +Unzip your setup's ZIP into the folder its page names. To check each file, use +`--from-pack`: [Without a network](installer.md#without-a-network-offline-install). + +## 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. + ## Why is a file missing from the ZIP? -Some files come with the emulator. The ZIP leaves those out. - -## Required or optional? - -- **Required:** the emulator does not start games without it. -- **Optional:** games start, with small differences, like no start-up screen. +Some come with the emulator, so the ZIP leaves them out. ## My game shows a black screen -Most of the time, a BIOS file is missing or is the wrong version. -[When a game does not start](troubleshooting.md) shows how to check. +A BIOS file is often missing: [When a game does not start](troubleshooting.md). ## Is this legal? -Laws differ by country, and nothing here is legal advice. Details, and how to -ask for a file to be removed: [Is it legal?](legal.md) +Laws differ by country. [Is it legal?](legal.md) ## Next steps diff --git a/wiki/first-install.md b/wiki/first-install.md index a7b0bca7..9b5381b3 100644 --- a/wiki/first-install.md +++ b/wiki/first-install.md @@ -64,8 +64,8 @@ curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | -On Batocera, Recalbox and similar setups, follow -[your setup's page](../which-pack.md). +On Batocera, Recalbox and similar setups, see +[Get your files](../which-pack.md). ## 2. Check the folder it found @@ -96,7 +96,7 @@ You can stop with Ctrl+C at any time. Run the command again to carry on. ## 5. Read the last line -- **`Done.`** All files are in place. +- **`Done.`** `N files installed, M already up to date.` All files are in place. - **`Finished with errors.`** Some files failed. Run the same command again. It retries only those. - **`Nothing was installed.`** The lines above it say why. See @@ -161,6 +161,6 @@ See [When a game does not start](troubleshooting.md). ## Next steps -- [Your setup's page](../which-pack.md): its folder and how it checks files +- [Get your files](../which-pack.md) - [When a game does not start](troubleshooting.md) - Details: [Reading the installer's output, line by line](installer.md#reading-a-run) diff --git a/wiki/installer.md b/wiki/installer.md index 1544de5a..94f1f878 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -46,6 +46,17 @@ 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`: + +```bash +curl -fsSLO https://raw.githubusercontent.com/Abdess/retrobios//install.py +RETROBIOS_REF= python3 install.py +``` + +A tag lasts until the next release deletes it. What `RETROBIOS_REF` does not +pin is listed under [Environment overrides](#environment-overrides). + ## Passing options The shell bootstrap forwards everything after `-s --`: @@ -504,8 +515,8 @@ what the options name, everything by default. | `--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-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) | -| `--manifest FILE` | Read the file list from FILE, a copy of `install/.json`, instead of downloading it. See [Without a network](#without-a-network) | +| `--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/.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) | | `--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 | @@ -865,7 +876,9 @@ The launcher (`ports/RetroBIOS/RetroBIOS.sh`) and its screens (`retrobios/screens.txt`) read the installer through [Machine-readable events](#machine-readable-events) only. -## Without a network { #without-a-network } +## Without a network (offline install) { #without-a-network-offline-install } + + A pack downloaded once installs again with no network, on this machine or on one that has none: @@ -1014,6 +1027,10 @@ in the session. Failures that are per-file, such as a download exhausting its three attempts, are counted and reported at the end. +To tell missing files from a failure, read the `check` event of +[Machine-readable events](#machine-readable-events): `missing` and `wrong_hash` +count what is left. + ## Machine-readable events With `--events`, standard output carries only JSON objects, one per line, and diff --git a/wiki/legal.md b/wiki/legal.md index 638acafc..7f5d54ae 100644 --- a/wiki/legal.md +++ b/wiki/legal.md @@ -5,16 +5,28 @@ order: 55 # Is it legal? +It depends on your country. Nothing here is legal advice. + +- Making an emulator is legal in the US. EU law allows it under conditions. +- Sharing BIOS files is not settled law. The strongest case is old consoles no + longer sold. The weakest is encryption keys (Switch, 3DS, Wii U). +- The files are here for personal backup and preservation. +- A rights holder can ask for a file to be removed: + [open an issue](https://github.com/Abdess/retrobios/issues/new) with its path + or hash. + +## The reasoning in detail + Redistributing firmware is not settled law. Below is the reasoning the project acts on, with the strength of each argument, so anyone can weigh it. None of it is legal advice, and none of it has been tested in court. -## Emulation and BIOS redistribution +### Emulation and BIOS redistribution - **Writing an emulator is lawful.** *Sony v. Connectix* (2000) and *Sega v. Accolade* (1992) held that copying a work during reverse engineering, to reach the functional elements needed for interoperability, is fair use. Both concern the act of copying while developing, and *Connectix* specifically involved copying Sony's BIOS in the course of that work. Neither decision addresses distributing firmware to third parties, so they support the legality of emulation itself rather than of this collection. - **Fair use (US, 17 USC 107) is a defence, argued case by case.** The project's position is that non-commercial redistribution for personal backup and archival weighs favourably on purpose and on market effect, since these files are not sold separately. It weighs poorly on the amount used: each file is copied whole. Manufacturers do still monetise some of this firmware indirectly, through re-releases, subscription services and mini consoles, so the market factor is contestable rather than clear. - **Interoperability in the EU.** The Software Directive (2009/24/EC, Art. 5-6) permits decompilation and use to achieve interoperability, subject to conditions, and Art. 6 restricts passing the resulting information to others. It supports the emulation use case, but it is not a general redistribution permission. - **Discontinued hardware.** Most firmware here is for hardware no longer sold or supported. This bears on the fair-use market factor and on the practical likelihood of complaint. "Abandonware" is not a legal doctrine in any jurisdiction, and nothing here rests on it. -## Encryption keys (Switch prod.keys, 3DS AES keys, Wii U keys) +### Encryption keys (Switch prod.keys, 3DS AES keys, Wii U keys) This is the most contested area, and the weakest part of the project's position. @@ -24,13 +36,13 @@ This is the most contested area, and the weakest part of the project's position. - **Keys come from consumer hardware.** They are extracted from retail devices their owners bought. *Chamberlain v. Skylink* (2004) held that a 1201 claim requires some nexus to copyright infringement, in a dispute over a garage-door opener. Later cases have narrowed its reach, and it is a weak foundation for a general post-sale right. - **Trade secret is not a live issue.** Keys embedded in millions of shipped devices and widely republished are not kept secret by reasonable measures. -## Recent firmware (Switch 19.0.0, PS3UPDAT, PSVUPDAT) +### Recent firmware (Switch 19.0.0, PS3UPDAT, PSVUPDAT) - **Free download is not a licence.** Manufacturers publish these updates on open CDNs without authentication, which makes them easy to obtain but does not grant a right to redistribute them, and PlayStation firmware ships under an end-user licence agreement. The practical argument is that mirroring a file the manufacturer already gives away for free causes no identifiable loss, not that doing so is expressly permitted. - **Functional necessity.** Emulators cannot run the software these systems were built for without this firmware. That is why the files are collected. It is a statement of purpose, not a legal defence in itself. - **Yuzu context.** The Yuzu settlement (2024) concerned the emulator and its alleged facilitation of piracy. Yuzu settled without admitting liability, so it set no precedent either way on firmware or key redistribution. It does show that rights holders in this space litigate. -## Summary +### Summary This project preserves BIOS files, firmware and keys for personal use, archival and interoperability. The strongest ground is the older, discontinued firmware, where fair use and the absence of a market both point the same way. The weakest is the encryption keys, where DMCA 1201 applies and the interoperability exemption is narrower than the use made of it here. The project accepts that risk deliberately, in the belief that these files are worth preserving while they can still be obtained. @@ -40,6 +52,12 @@ This reflects the project's good-faith understanding, not legal advice. A rights holder, or anyone acting for one, can [open an issue](https://github.com/Abdess/retrobios/issues) identifying the file by path or hash. A file whose removal is requested by its rights holder is removed from the repository and from the next release. No formal notice is required and none will be demanded. State the file and the basis of the claim. The same channel is open to anyone else with a concern about a specific file. +## Contact + +The project answers in public, on its +[issue tracker](https://github.com/Abdess/retrobios/issues). A removal request +goes there, as described above. + ## Next steps - [Common questions](faq.md) diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md index 7955feec..fd25b2d7 100644 --- a/wiki/testing-guide.md +++ b/wiki/testing-guide.md @@ -54,7 +54,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_offline`, `test_install_selection` | | Handheld port | stubbed PortMaster dialogs, fake installer | `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_entry`, `test_site_r3_nav`, `test_site_r3_p0`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `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_entry`, `test_site_r3_nav`, `test_site_r3_p0`, `test_site_r3_wiki`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` | ## Test architecture diff --git a/wiki/troubleshooting.md b/wiki/troubleshooting.md index eb72017c..14f4ff73 100644 --- a/wiki/troubleshooting.md +++ b/wiki/troubleshooting.md @@ -5,11 +5,11 @@ order: 40 # When a game does not start -Usually a BIOS file is missing or the wrong version. Three checks find it. +Most often a BIOS file is missing or wrong. -## 1. Check your BIOS folder { #checking-what-you-installed } +## 1. Check my BIOS folder { #checking-what-you-installed } - + Run the command with `--check`. It changes nothing. @@ -24,50 +24,51 @@ Read the count line: 1/109 present (0 verified, 1 wrong hash) ``` -- **`wrong hash`**: wrong contents: another region, model, or damaged. -- **Fewer present than the total**: some files are missing. -- **A line under a file**, like `still zipped`: it says what to fix. -- **`0 wrong hash` and all present**: your files are fine. Go to step 3. +- **`wrong hash`**: another region, model, or damaged. Batocera says + `UNTESTED`. +- **Fewer present than the total**: files are missing. +- **A line under a file**, like `still zipped`, says what to fix. +- **`0 wrong hash`, all present**: go to step 3. ## 2. Fix the files -Run the same command without `--check`. It adds missing files and replaces -wrong ones, keeping the old ones. +Run it again without `--check`. It adds missing files and replaces wrong ones. ## 3. Check the folder and the names -- **Right folder?** Your emulator must read the folder the command used. - [Your setup's page](../which-pack.md) names it. -- **Is the name exact?** `scph5501.bin` and `SCPH-5501.BIN` are two different - names. On Linux, capital letters count too. -- **A `bios` folder inside your BIOS folder?** That ZIP holds its own top - folder. Move its files up one level. -- **Unzipped by hand on Linux?** Run the command once. It adds the names that - differ only by capital letters, which some ZIPs leave out. -- **A ZIP in parts, like `Pack.part1of2.zip`?** Unzip every part into the - same folder. `--check` names a part you missed. +- **Right folder?** [Get your files](../which-pack.md) names the one your setup + reads. +- **Exact name?** `scph5501.bin` and `SCPH-5501.BIN` differ. On Linux, + capitals count too. +- **A `bios` folder inside your BIOS folder?** Move its files up one level. +- **Unzipped by hand on Linux?** Run the command once. It adds names that + differ by capitals only. +- **A ZIP in parts, like `Pack.part1of2.zip`?** Unzip every part into one + folder. -## One console only +## An arcade game does not start -Add `--system` and its name: +It needs its BIOS set, like `neogeo.zip`, and a game set for the same emulator +version. - -```sh -curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh -s -- --check --system sony-playstation --verbose -``` +- 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. -`--verbose` names each missing or wrong file. `--list-systems` lists the names. +## The BIOS is fine, the game still fails -## My setup says UNTESTED - -Batocera shows `UNTESTED` for a file with unexpected contents. Run the command -without `--check` to replace it. +- **Another emulator?** Each one reads its own files. Its page lists them. +- **A BIOS setting?** Some emulators can skip the BIOS. In PCSX ReARMed, set + **BIOS Selection** to **Auto**. +- **A disc game in several files?** Open its `.cue` or `.m3u` file, not a + `.bin`. ## If the command fails { #installation-script-fails } -- **Network error:** check that github.com opens, then try again. +- **Network error:** try again once github.com opens. - **Permission denied:** do what the message says. It names what to run. -- **No setup found:** pick it from the list. Or name it and its folder: +- **No setup found:** name it and its folder: ```sh @@ -76,9 +77,9 @@ curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | Other messages: [When something goes wrong](installer.md#when-something-goes-wrong). - + ## Next steps -- [Your setup's page](../which-pack.md): its folder and how it checks files +- [Get your files](../which-pack.md) - [Common questions](faq.md) diff --git a/wiki/what-are-bios-files.md b/wiki/what-are-bios-files.md index c0070d53..58ce9e67 100644 --- a/wiki/what-are-bios-files.md +++ b/wiki/what-are-bios-files.md @@ -26,8 +26,9 @@ If step 3 fails, you see "BIOS missing" or a black screen. ## Do I need one? -- **Yes:** PlayStation 1 and 2, Saturn, Dreamcast, Neo Geo, PC Engine CD and - many home computers. +- **Yes:** PlayStation 1 and 2, Saturn, Neo Geo, PC Engine CD and many home + computers. +- **Better with it:** Dreamcast. Some games need it. - **Usually not:** NES, Super Nintendo, Mega Drive, Game Boy and Game Boy Advance. - **Add-ons need their own:** the Famicom Disk System and the Mega-CD.