diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index 5353269cd..23c2a59a7 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -17,6 +17,7 @@ on: - "scripts/cross_reference.py" - "scripts/crypto_verify.py" - "scripts/derivations.py" + - "scripts/dedup.py" - "scripts/deterministic_zip.py" - "scripts/dumpcatalog.py" - "scripts/exporter/baseline.py" @@ -61,6 +62,7 @@ on: - "scripts/split_pack.py" - "scripts/truth.py" - "scripts/upstream.py" + - "scripts/validate_pr.py" - "scripts/validate_schemas.py" - "scripts/validate_site.py" - "scripts/validation.py" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 165b3e672..ae2c25459 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,8 +24,12 @@ the most useful contribution. From a clone, the file goes to `bios/Manufacturer/Console/filename`, and `python scripts/validate_pr.py path/to/file` names that folder for a file still -outside `bios/`. Given a folder, such as an old card's BIOS folder, it checks -every file under it. A file over the browser's 25 MB limit goes in that way. +outside `bios/`, and `--place` copies it there under its declared name. Given a +folder, such as an old card's BIOS folder, it checks every file under it. A +file over the browser's 25 MB limit goes in that way, up to the 100 MB GitHub +accepts in a push. A larger file stays out of the pull request: [open an +issue](https://github.com/Abdess/retrobios/issues/new?template=missing-bios.yml) with the hashes `validate_pr.py` prints, and a +maintainer adds it to the `large-files` release. ## Add a platform @@ -64,18 +68,22 @@ validation above is its check. For code, profiles or platform files, these are the checks CI runs on a pull request (Python 3.12 or newer): ```bash -pip install pyyaml "jsonschema[format-nongpl]" markdown +pip install pyyaml "jsonschema[format-nongpl]" markdown pyte hypothesis vermin python scripts/validate_pr.py --changed # the BIOS files the branch adds python scripts/validate_schemas.py --source-only # profiles and platform files python scripts/profile_sync.py --added --base upstream/main --strict # each profile the branch adds python scripts/refresh_data_dirs.py # data directories the pack-building tests read python scripts/generate_site.py # the pages the site tests read +npm ci --prefix website && npm run build --prefix website # the built site, Node 24 python -m unittest discover -s tests -t . +python scripts/validate_site.py --site-dir website/dist # links and page budgets +vermin --no-tips -t=3.8- --violations install.py # install.py stays on Python 3.8 ``` `upstream` is this repository as a remote of your fork -(`git remote add upstream https://github.com/Abdess/retrobios`). A profile you -only edit is not held to `--strict`: its older refs are reviewed by hand. +(`git remote add upstream https://github.com/Abdess/retrobios`, then +`git fetch upstream`). A profile you only edit is not held to `--strict`: its +older refs are reviewed by hand. The full pipeline is not needed for a contribution: it rebuilds every pack in `dist/`, which is why maintainers run it before a release. diff --git a/emulators/gearboy.yml b/emulators/gearboy.yml index d1a091cb3..e8b472e38 100644 --- a/emulators/gearboy.yml +++ b/emulators/gearboy.yml @@ -19,8 +19,9 @@ systems: notes: | Gearboy is a Game Boy / Game Boy Color emulator by drhelius (Ignacio Sanchez). - The buildbot builds from drhelius/Gearboy (upstream), not from libretro/Gearboy - (abandoned fork frozen at v2.6.0 from 2018). + The buildbot builds from drhelius/Gearboy (upstream), not from libretro/Gearboy, + an archived fork (its description reads "NOTE: PUBLIC ARCHIVE") whose + src/definitions.h states version 2.6.0. Bootrom loading is gated behind core options (disabled by default). Without bootroms, the core HLE-skips the boot animation. diff --git a/emulators/np2kai.yml b/emulators/np2kai.yml index c002e341e..5056fd9ac 100644 --- a/emulators/np2kai.yml +++ b/emulators/np2kai.yml @@ -16,7 +16,7 @@ cores: [np2kai] systems: [pc-98] # NP2kai is a PC-9801/9821 emulator by AZO234, enhanced fork of Neko Project II -# by Yui (original upstream dead: np2.yui.ne.jp). All files load from +# by Yui (original site: np2.yui.ne.jp). All files load from # /np2kai/ subdirectory, set in retro_load_game() # (sdl/libretro/libretro.c:1800-1815). All getbiospath() calls resolve # relative to that directory. diff --git a/emulators/pcsx1.yml b/emulators/pcsx1.yml index 4cfba40bf..6191bc337 100644 --- a/emulators/pcsx1.yml +++ b/emulators/pcsx1.yml @@ -15,7 +15,7 @@ display_name: "Sony - PlayStation (PCSX1)" cores: [pcsx1] systems: [sony-playstation] notes: > - Never-completed fork of PCSX-ReARMed targeting general PC hardware. Experimental, abandoned since 2018. + Fork of PCSX-ReARMed targeting general PC hardware. The last commit on libretro/pcsx1-libretro is from 2018-02-11. HLE BIOS built-in: if no BIOS file found, core warns and falls back to HLE automatically. BIOS search order: scph1001.bin, scph5501.bin, scph7001.bin by exact filename, then scans system directory for any file matching "scph*" (case-insensitive) with size == 512 KB. diff --git a/emulators/rustation.yml b/emulators/rustation.yml index 699ba38e5..477fec99b 100644 --- a/emulators/rustation.yml +++ b/emulators/rustation.yml @@ -30,7 +30,7 @@ notes: > Database contains 24 entries (22 good, 2 known-bad rejected) covering BIOS versions 1.0 through 4.5. .info declares firmware_count=0, while the core reads the BIOS images this profile lists. - Project abandoned, superseded by rustation-ng. + Its README says it is no longer developed and points to rustation-ng. files: # -- Region: Japan -- diff --git a/emulators/yabause.yml b/emulators/yabause.yml index 9e4473539..2140005e7 100644 --- a/emulators/yabause.yml +++ b/emulators/yabause.yml @@ -17,9 +17,9 @@ systems: - sega-saturn notes: | - Yabause is a software-rendered Sega Saturn emulator. Upstream project is - dead. The libretro port is maintained by the community on the master branch of - libretro/yabause. + Yabause is a software-rendered Sega Saturn emulator. The last commit on the + master branch of Yabause/yabause is from 2018-07-07. The libretro port is + maintained by the community on the master branch of libretro/yabause. Saturn BIOS search order: 1. {system}/saturn_bios.bin diff --git a/scripts/cross_reference.py b/scripts/cross_reference.py index abb464df6..0a0e36c6f 100644 --- a/scripts/cross_reference.py +++ b/scripts/cross_reference.py @@ -27,6 +27,7 @@ from common import ( platform_arg, ProfileSelectionError, _norm_system_id, + archive_entry, directory_members, expand_directory_entries, get_mame_clone_map, @@ -573,18 +574,27 @@ class FileConsumers: for dest, sha1 in members.items() ] else: - # The archive members the profile reads decide between - # same-named copies, as in the emulator pack and verify. - found = [self.sha1_of( - with_archive_members(entry, files), entry.get("path") or "", - owner=name, - )] + found = [self.sha1_of(*entry_target(entry, files), owner=name)] for sha1 in found: if sha1: self.emulators.setdefault(sha1, set()).add(name) self.emulator_entries.setdefault(sha1, {}).setdefault(name, index) +def entry_target(entry: dict, files: list[dict]) -> tuple[dict, str]: + """The entry a profile entry resolves through, and its destination. + + A ROM read inside an archive is the archive: FBNeo opens sm1.sm1 in + neogeo.zip, and resolved by its crc32 it credited a loose copy FBNeo + never opens, while no neogeo.zip card named FBNeo. The archive + members the profile reads decide between same-named copies, as in + the emulator pack and verify. + """ + if entry.get("archive"): + return archive_entry(entry["archive"], files), "" + return with_archive_members(entry, files), entry.get("path") or "" + + def entry_source(f: dict, index: dict) -> str | None: """Where the collection holds a profile entry, or None. diff --git a/scripts/generate_readme.py b/scripts/generate_readme.py index fb6bfa462..006aab519 100644 --- a/scripts/generate_readme.py +++ b/scripts/generate_readme.py @@ -37,6 +37,7 @@ from common import ( from nativemode import check_label import release_record from siteguides import install_command +from validate_pr import LARGE_FILE_ISSUE_URL from verify import verify_platform @@ -929,8 +930,12 @@ the most useful contribution. From a clone, the file goes to `bios/Manufacturer/Console/filename`, and `python scripts/validate_pr.py path/to/file` names that folder for a file still -outside `bios/`. Given a folder, such as an old card's BIOS folder, it checks -every file under it. A file over the browser's 25 MB limit goes in that way. +outside `bios/`, and `--place` copies it there under its declared name. Given a +folder, such as an old card's BIOS folder, it checks every file under it. A +file over the browser's 25 MB limit goes in that way, up to the 100 MB GitHub +accepts in a push. A larger file stays out of the pull request: [open an +issue]({LARGE_FILE_ISSUE_URL}) with the hashes `validate_pr.py` prints, and a +maintainer adds it to the `large-files` release. ## Add a platform @@ -969,18 +974,22 @@ validation above is its check. For code, profiles or platform files, these are the checks CI runs on a pull request (Python 3.12 or newer): ```bash -pip install pyyaml "jsonschema[format-nongpl]" markdown +pip install pyyaml "jsonschema[format-nongpl]" markdown pyte hypothesis vermin python scripts/validate_pr.py --changed # the BIOS files the branch adds python scripts/validate_schemas.py --source-only # profiles and platform files python scripts/profile_sync.py --added --base upstream/main --strict # each profile the branch adds python scripts/refresh_data_dirs.py # data directories the pack-building tests read python scripts/generate_site.py # the pages the site tests read +npm ci --prefix website && npm run build --prefix website # the built site, Node 24 python -m unittest discover -s tests -t . +python scripts/validate_site.py --site-dir website/dist # links and page budgets +vermin --no-tips -t=3.8- --violations install.py # install.py stays on Python 3.8 ``` `upstream` is this repository as a remote of your fork -(`git remote add upstream https://github.com/Abdess/retrobios`). A profile you -only edit is not held to `--strict`: its older refs are reviewed by hand. +(`git remote add upstream https://github.com/Abdess/retrobios`, then +`git fetch upstream`). A profile you only edit is not held to `--strict`: its +older refs are reviewed by hand. The full pipeline is not needed for a contribution: it rebuilds every pack in `dist/`, which is why maintainers run it before a release. diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 04724b67b..e92ea2803 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -63,7 +63,7 @@ from common import ( ) from artifacts import ArtifactLockBusy, artifact_lock from artifacts import json_text as _json_text -from cross_reference import FileConsumers, held_copy +from cross_reference import FileConsumers, entry_target, held_copy from nativemode import check_label, digest_algorithm, reads_file_contents @@ -679,9 +679,13 @@ def release_line(platform_name: str, record: dict) -> list[str]: return lines -# What a reader on another version of the setup does with the pack. Our data -# says which list the pack was made from, not what an older list lacks. -OTHER_VERSION = "Other version? Run `--check` after copying." +def other_version(platform_name: str, prefix: str = "../") -> str: + """What a reader on another version of the setup does with the pack. + Our data says which list the pack was made from, not what an older list + lacks. A bare `--check` was no command to run for someone holding a ZIP: + the line links the setup page's commands that check.""" + target = f"{prefix}platforms/{platform_name}.md#how-to-check" + return f"Other version? [Check the files]({target}) after copying." def setup_version(platform_name: str, record: dict | None = None) -> str: @@ -700,13 +704,15 @@ def setup_version(platform_name: str, record: dict | None = None) -> str: return f"{config.get('platform', platform_name)} {version.removeprefix('v')}" -def version_line(platform_name: str, record: dict, named: bool = False) -> str: +def version_line(platform_name: str, record: dict, named: bool = False, + prefix: str = "../") -> str: """The version the pack fits, and what to do on another. *named* when the release line already named the pack's version.""" made_from = setup_version(platform_name, record) + other = other_version(platform_name, prefix) if named or not made_from: - return OTHER_VERSION - return f"Made for {made_from}. {OTHER_VERSION}" + return other + return f"Made for {made_from}. {other}" # Why a setup has no ZIP, answered once on the FAQ. @@ -793,7 +799,8 @@ def _pack_buttons(platform_name: str, label: str = "Pack", prefix: str = "../", ) released = release_line(platform_name, record) return "\n\n".join([buttons, *released, - version_line(platform_name, record, named=len(released) > 1)]) + version_line(platform_name, record, named=len(released) > 1, + prefix=prefix)]) def generate_get_page(platform_name: str) -> str: @@ -2191,9 +2198,11 @@ def generate_data_page(exports: list[dict], measurements_sqlite: Path | None = N "when one is known, the `system_name` the site gives its system. " "[Find or check a file](files/index.md) reads it.", "", - "In a browser, the SHA1 of a file the reader picks finds its records. Web " - "Crypto has no MD5, and none is needed: a setup that lists a file by MD5 " - "or CRC32 is filed under the SHA1 of the collection file it names too.", + "In a browser, the SHA1 of a file the reader picks finds its records when " + "the collection holds that file. A list naming a file the collection lacks " + "is filed under its MD5 or CRC32 alone, and Web Crypto has no MD5: " + "that page computes MD5 and CRC32 in script and looks " + "up all four.", "", "```js", "// The records of the file a reader picked, by its SHA1.", @@ -2443,7 +2452,7 @@ def _browser_title(relative: Path, title: str) -> str: # The words a web search for a console or an emulator types: "dreamcast # bios", "duckstation bios". The h1 and the sidebar keep the name. if key.startswith("emulators/"): - has_files = PAGE_DATA.get(key, {}).get("hasPart") + has_files = PAGE_DATA.get(key, {}).get("hasPart") and key not in NOTHING_TO_FETCH # Lexaloffle, QEMU and ScummVM name a system page too. own = " emulator" if f"systems/{relative.name}" in PAGE_DATA else "" return f"{title}{own} BIOS files" if has_files else f"{title} emulator" @@ -2514,6 +2523,12 @@ def _page_description(markdown: str, relative: Path, title: str) -> str: return _system_description(markdown, browser) if key.startswith("emulators/") and relative.stem != "index": return _emulator_description(markdown, key, title, _first_paragraph(lead)) + if key.startswith("platforms/") and relative.stem != "index": + # The summary ends on a link to the platform's site, whose label + # alone was left: "A system for handhelds. ROCKNIX website." + summary = _first_paragraph(lead).removesuffix(f" {title} website.") + return _shorten(f"The BIOS files for {title}, the folder they go in and the " + f"command that installs them. {summary}", 155) first = _first_paragraph(lead, minimum=35) questions = [_plain_markdown(re.sub(r"\s*\{[^}]*\}\s*$", "", heading)) for heading in re.findall(r"^## (.+\?)(?:[ \t]*\{[^}]*\})?[ \t]*$", markdown, re.M)] @@ -2539,7 +2554,9 @@ def _emulator_description(markdown: str, key: str, title: str, intro: str) -> st its profile table when the profile names no system.""" notes = re.search(r"Technical notes\s*\n\n(.+?)(?:\n\n|$)", markdown, re.S) if not intro and notes: - sentence = re.split(r"(?<=\.)\s", _plain_markdown(notes[1]), maxsplit=1)[0] + # An initial is no sentence end: hcl's read "open-source port of E." + sentence = re.split(r"(? st elif title not in intro: # "It reads 1 file": the profile names no system to open on. intro = f"{title}: {intro[:1].lower()}{intro[1:]}" + if key in NOTHING_TO_FETCH: + return _shorten(f"{intro} Every file it reads comes with the app.", 155) # An alias page sends the reader to the core whose files it shares. if PAGE_DATA.get(key, {}).get("hasPart") or "uses the same firmware as" in intro: return _shorten(intro, 155) @@ -2623,6 +2642,9 @@ def _system_description(markdown: str, browser: str) -> str: # Structured data a page generator knows beyond its text, by page path # relative to docs/: read by decorate_markdown_pages. PAGE_DATA: dict[str, dict] = {} +# Emulator pages whose every file comes with the app: no "BIOS files" in +# their title, and their description says so (DaedalusX64, ZEsarUX). +NOTHING_TO_FETCH: set[str] = set() # Pages that hold part of a page too long for one (a console of a split # manufacturer, a run of an emulator's cards), by path relative to docs/: # their H1 already says what they are. @@ -2950,6 +2972,17 @@ def release_versions(profile: dict) -> dict[str, str]: if re.search(r"[0-9]", value) and not names_a_commit(value)} +def record_emulator_page(name: str, profile: dict) -> None: + """What the title and the description of an emulator page read.""" + key = f"emulators/{name}.md" + PAGE_DATA[key] = emulator_structured_data(profile) + listed = [f for f in profile.get("files") or [] if isinstance(f, dict)] + if listed and all(comes_with_app(f) for f in listed): + NOTHING_TO_FETCH.add(key) + else: + NOTHING_TO_FETCH.discard(key) + + def emulator_structured_data(profile: dict) -> dict: """What an emulator page states, for a reader of its JSON-LD. @@ -4176,7 +4209,7 @@ def _setup_routes(name: str, display: str, page_link: bool = False) -> list[str] # lines are on the get/ page, the one the command is shared from. tabs = ("windows",) if page_link else ("windows", "macos", "linux") computer += [ - "Needs the device on your network. Run the command on the computer.", + "Needs the device on your network.", "", *terminal_tabs(tabs, args, how=how), *([f"[Mac or Linux](../{get_page_link(name)}#from-your-computer)", ""] @@ -6443,13 +6476,6 @@ def _region_order() -> dict[str, int]: return {region: index for index, region in enumerate(ordered)} -_REGION_WORDS = {"uk": "UK", "asia-ntsc": "Asia (NTSC)", "asia-pal": "Asia (PAL)"} - - -def _region_word(region: str) -> str: - return _REGION_WORDS.get(region) or region.replace("-", " ").title() - - def _reading_pairs(sha1: str, consumers: FileConsumers) -> list[tuple[str, dict]]: """The profile entries that read a file, each with its emulator.""" files_of = getattr(consumers, "emulator_files", {}) @@ -6662,7 +6688,7 @@ def _start_groups( for f in primaries: sha1 = str(f.get("sha1", "")) for emulator, entry in _reading_pairs(sha1, consumers): - if entry.get("bundled"): + if comes_with_app(entry): continue system = None systems = _entry_systems(emulator, entry) @@ -6677,7 +6703,7 @@ def _start_groups( for sha1, emulator, entry, system in _entries_by_section(consumers).get(here, []): record = known.get(sha1) parts = str((record or {}).get("path", "")).split("/") - if (sha1 in held or not record or entry.get("bundled") or ".variants" in parts + if (sha1 in held or not record or comes_with_app(entry) or ".variants" in parts or len(parts) < 3 or parts[1] != "Arcade"): continue if sha1 not in kept: @@ -7031,7 +7057,7 @@ def _group_start(group: _StartGroup, first_below: bool) -> list[tuple[str, str]] else: regional[(*same, region)] = regional.pop(same) - out = [(", ".join(_region_word(r) for r in regions), regional[regions]) + out = [(", ".join(_region_words(r) for r in regions), regional[regions]) for regions in sorted(regional, key=lambda rs: (order.get(rs[0], len(order)), rs))] # A name a region line gives already is no "also", and one only # emulators no region line answers reads is an "instead": armsx1 reads @@ -7819,12 +7845,15 @@ def generate_emulators_index(profiles: dict) -> str: def comes_with_app(f: dict) -> bool: - """The emulator installs the file in every build the profile documents. + """The emulator installs the file in the build the entry describes. - ``bundled`` narrowed by ``mode`` is installed by one build only, and the - other build still reads it from the user's folder. + The rule verify and the pack read (``installed_with_emulator``). An entry + narrowed by ``mode`` is that build's alone: the other build reads its + own entry, or nothing. """ - return f.get("bundled") is True and not f.get("mode") + from validation import installed_with_emulator # noqa: PLC0415 + + return installed_with_emulator(f) def _file_badges(f: dict, in_repo: bool, for_system: str = "") -> list[str]: @@ -7841,7 +7870,6 @@ def _file_badges(f: dict, in_repo: bool, for_system: str = "") -> list[str]: category = f.get("category", "") region = f.get("region", "") storage = f.get("storage", "") - bundled = f.get("bundled", False) embedded = f.get("embedded", False) has_builtin = f.get("has_builtin", False) archive = f.get("archive", "") @@ -7851,9 +7879,10 @@ def _file_badges(f: dict, in_repo: bool, for_system: str = "") -> list[str]: if shipped: # NetherSX2's three interface fonts sat beside the BIOS as # "required" and "missing": the app ships them, nobody fetches them. - badges.append( - 'comes with the app' - ) + # Narrowed to one build, the build is named. + build = f.get("mode") if f.get("mode") in ("standalone", "libretro") else "" + label = f"installed with the {build} emulator" if build else "comes with the app" + badges.append(f'{label}') elif required: needed = f"required for {html.escape(for_system)}" if for_system else "required" badges.append( @@ -7879,7 +7908,7 @@ def _file_badges(f: dict, in_repo: bool, for_system: str = "") -> list[str]: badges.append( 'HLE fallback' ) - if mode: + if mode and not (shipped and mode in ("standalone", "libretro")): badges.append( f'{mode}' ) @@ -7896,13 +7925,6 @@ def _file_badges(f: dict, in_repo: bool, for_system: str = "") -> list[str]: badges.append( f'{storage}' ) - if bundled and not shipped: - # Said in words: the pack leaves the file out because the emulator, - # in the build the entry names, already installs it. - build = f" {f['mode']}" if f.get("mode") in ("standalone", "libretro") else "" - badges.append( - f'installed with the{build} emulator' - ) if embedded: badges.append( 'embedded' @@ -7995,10 +8017,14 @@ def _build_rows(profile: dict) -> list[str]: found = _found_by_text(profile.get("found_by")) if found: rows.append(f"| Files found by | {found} |") - frozen = profile.get("pin_frozen") - if frozen: - held = _escape_tags(' '.join(str(frozen).split())).replace('|', r'\|') - rows.append(f"| Revision held | {held} |") + # Each pin says why it is held: a bare upstream revision read as a + # profile left behind HEAD (mupen64plus, aps3e). + for field, label in (("pin_frozen", "Revision held"), + ("upstream_frozen", "Upstream revision held")): + frozen = profile.get(field) + if frozen: + held = _escape_tags(' '.join(str(frozen).split())).replace('|', r'\|') + rows.append(f"| {label} | {held} |") announced = profile.get("version_source") if isinstance(announced, dict) and announced.get("url"): url = str(announced["url"]) @@ -8193,10 +8219,13 @@ def _render_emulator_file( if consumers and f.get("type") == "directory": details.extend(_directory_copies(f, consumers)) if consumers: - sha1 = consumers.sha1_of(f, f.get("path") or "", owner=owner) + sha1 = consumers.sha1_of( + *entry_target(f, profile.get("files") or []), owner=owner) held = consumers.path_of(sha1) if sha1 and in_repo else "" parts = held.split("/") - if len(parts) >= 3 and parts[0] == "bios": + if len(parts) >= 3 and parts[0] == "bios" and f.get("archive"): + details.append(_held_copy_line((held, f["name"]), consumers)) + elif len(parts) >= 3 and parts[0] == "bios": # Which copy answers this entry, linked to its collection card # (provenance, every hash, download): an entry declaring no hash # said "in repo" without naming the file. @@ -8473,8 +8502,9 @@ def emulator_romset(profile: dict) -> str: return "" -def _version_said(value: object, basis: dict) -> str: - """One build's version and where the profile reads it.""" +def _version_said(value: object, basis: dict, profile: dict, mode: str = "") -> str: + """One build's version and where the profile reads it, linked to the + file or the tag where the source allows.""" kind = basis.get("kind") if kind == "none": return "none stated in the source" @@ -8483,13 +8513,18 @@ def _version_said(value: object, basis: dict) -> str: return f"unknown: {_UNVERIFIED.get(str(basis.get('why')), 'unread')}" return "none published" value = str(value) + url = version_basis_url(profile, basis, mode) if kind in ("file", "tag") else "" + + def shown(text: object) -> str: + return f"[{_code(str(text))}]({url})" if url else _code(str(text)) + if kind == "file": - return f"{value}, stated in `{basis.get('path')}`" + return f"{value}, stated in {shown(basis.get('path'))}" if kind == "tag": distance = int(basis.get("distance") or 0) if not distance: return f"{value}, the tag at this revision" - return f"{value}, {distance} commit{'s' if distance != 1 else ''} after tag `{basis.get('tag')}`" + return f"{value}, {distance} commit{'s' if distance != 1 else ''} after tag {shown(basis.get('tag'))}" if kind == "package": return f"{value}, read from {basis.get('artifact')}" if kind == "unverified": @@ -8524,10 +8559,10 @@ def version_markdown(profile: dict) -> str: values = value if isinstance(value, dict) else {} modes = list(dict.fromkeys([*values, *basis])) return ", ".join( - f"{_version_said(values.get(mode), basis.get(mode) or {})} ({mode})" + f"{_version_said(values.get(mode), basis.get(mode) or {}, profile, mode)} ({mode})" for mode in modes ) - return _version_said(value, basis) + return _version_said(value, basis, profile) ROMSET_FAQ_PAGE = Path(WIKI_SRC_DIR) / "technical-faq.md" @@ -8860,7 +8895,7 @@ def standalone_place(name: str, profile: dict, entry: dict, key: str = "") -> st if not isinstance(entry, dict) or not profile.get("data_root"): return "" mode = entry.get("mode") - if mode == "libretro" or (entry.get("bundled") is True and mode in (None, "standalone")): + if mode == "libretro" or comes_with_app(entry): return "" excluded = _folder_only(name, entry) if "*" in excluded or (key in excluded if key else excluded): @@ -8898,7 +8933,7 @@ def _data_root_folder(name: str, profile: dict, key: str, entries: list[dict]) - folders = set() for f in entries: mode = f.get("mode") - if mode == "libretro" or (f.get("bundled") is True and mode in (None, "standalone")): + if mode == "libretro" or comes_with_app(f): continue place = standalone_place(name, profile, f, key) if not place: @@ -12142,15 +12177,16 @@ def _generate(args: argparse.Namespace) -> None: # The test each emulator page builds for itself: the parts a page is # cut into weigh the status its rows show. file_held = _availability_check(db, suppl_names) + # The plan names systems: the sections must be this run's data first. + manufacturers = _group_by_manufacturer(db) + set_console_sections(manufacturers, program_names(profiles)) plan_emulator_pages(profiles, file_held) # Build system_id -> manufacturer page map (needed by all generators) print("Building system cross-reference map...") - manufacturers = _group_by_manufacturer(db) set_system_page_map( _system_pages_from_data(manufacturers, coverages, db, profiles) ) print(f" {len(_system_page_map)} system IDs mapped to pages") - set_console_sections(manufacturers, program_names(profiles)) from siterender import link_coverage, system_ids # noqa: PLC0415 print("Generating system pages...") @@ -12300,7 +12336,7 @@ def _generate(args: argparse.Namespace) -> None: name, profile, db, consumers, suppl_names, data_registry, data_dirs=page_data_directories(name, profiles), ) - PAGE_DATA[f"emulators/{name}.md"] = emulator_structured_data(profile) + record_emulator_page(name, profile) # A frozen copy answers after the emulator: a frozen_snapshot, and a # profile pinned to the revision one platform builds (pin_frozen). # Reicast and Flycast 2021 LE answered "dc bios missing" before Flycast. @@ -12466,6 +12502,7 @@ from sitesources import ( # noqa: E402,F401 _forge_sources, _source_permalink, _source_ref_markdown, + version_basis_url, ) from siterender import ( # noqa: E402,F401 _by_mode, diff --git a/scripts/pipeline.py b/scripts/pipeline.py index 90f08e7dc..eff53ae63 100644 --- a/scripts/pipeline.py +++ b/scripts/pipeline.py @@ -35,8 +35,8 @@ come from .cache/large/ only. Usage: python scripts/pipeline.py # active platforms python scripts/pipeline.py --include-archived # all platforms - python scripts/pipeline.py --skip-packs # steps 0 to 3b, 4d, 7 and 8 - python scripts/pipeline.py --skip-docs # skip steps 7 and 8 + python scripts/pipeline.py --skip-packs # skip steps 4, 4b, 4c, 5 and 6 + python scripts/pipeline.py --skip-docs # skip steps 7, 8 and 8b python scripts/pipeline.py --offline # no network, see above """ @@ -747,13 +747,13 @@ def main() -> None: parser.add_argument( "--skip-packs", action="store_true", - help="Skip steps 4 to 6 except 4d: packs, manifests and their checks. " - "Steps 7 and 8 still rewrite README.md and the site pages in website/, " - "add --skip-docs to " - "stop after verify", + help="Skip steps 4, 4b, 4c, 5 and 6: packs, manifests and their checks. " + "Step 4d still gates the run, step 4e still rewrites install/games/, and " + "steps 7 to 8b rewrite README.md and the site unless --skip-docs is given", ) parser.add_argument( - "--skip-docs", action="store_true", help="Skip README and site generation" + "--skip-docs", action="store_true", + help="Skip steps 7, 8 and 8b: README, installer page and site" ) parser.add_argument( "--offline", action="store_true", diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 7fde29014..4e92d3941 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -4378,6 +4378,10 @@ def branch_profiles(emulators_dir: str, base: str, added_only: bool) -> list[str branch point are not counted, and ends at the working tree, so a profile not yet committed is checked too. A rename reads as an addition. """ + if subprocess.run(["git", "rev-parse", "--verify", "--quiet", base], + capture_output=True, check=False).returncode: + remote = base.split("/", 1)[0] if "/" in base else "upstream" + raise SystemExit(f"ERROR: {base} is not in this clone: run git fetch {remote} first") merge_base = _git_lines(["merge-base", base, "HEAD"])[0] paths = _git_lines([ "diff", "--name-only", "--no-renames", diff --git a/scripts/siteguides.py b/scripts/siteguides.py index 0436ca768..786653620 100644 --- a/scripts/siteguides.py +++ b/scripts/siteguides.py @@ -454,6 +454,10 @@ def fence(shell: str, command: str, indent: str = "") -> list[str]: return [f'{indent}```{shell} title="{FENCE_TITLES[shell]}"', f"{indent}{command}", f"{indent}```"] +# Terminals named by where the command runs, not by the system it runs on. +PLACES = ("device", "server") + + def terminal_tabs( tabs: tuple[str, ...], args: dict[str, str], indent: str = "", steps: bool = True, how: dict[str, str] | None = None, @@ -465,6 +469,8 @@ def terminal_tabs( items = [] for key in tabs: label, shared, shell = TERMINALS[key] + if len(tabs) == 1 and key in PLACES: + label = "" # one place, which the text around already names text = (how or {}).get(key) or shared body = [text, ""] if steps else [] items.append((label, body + fence(shell, install_command(shell, args.get(key, ""))))) diff --git a/scripts/sitemarkup.py b/scripts/sitemarkup.py index cbd995606..e440d5cc5 100644 --- a/scripts/sitemarkup.py +++ b/scripts/sitemarkup.py @@ -139,8 +139,17 @@ def details(kind: str, title: str, body: list[str], indent: str = "", opened: bo def tabs(items: list[tuple[str, list[str]]], indent: str = "", sync: str = "os") -> list[str]: """Content tabs. Tabs that share a label share the reader's choice - across pages.""" + across pages. + + One item is no choice: 142 bars offered a single tab, most of them + "On the device" under a heading that says so. Its body stands alone, + led by its label when it has one: the RetroPie card is read from Linux + only, and the label is where the page said so.""" lines: list[str] = [] + if len(items) == 1: + label, body = items[0] + lead = [f"{indent}**{label}**", ""] if label else [] + return [*lead, *_indented(_trim(body), indent), ""] if _engine == "mkdocs": for label, body in items: lines += [f'{indent}=== "{label}"', "", *_indented(_trim(body), indent + " "), ""] diff --git a/scripts/sitesources.py b/scripts/sitesources.py index 20844939b..faf307241 100644 --- a/scripts/sitesources.py +++ b/scripts/sitesources.py @@ -240,8 +240,15 @@ def _walk(value: object) -> Iterator[tuple[str, object]]: def cited_paths(profile: dict) -> set[str]: """Every path a page of the profile could link: the parts of each - source_ref and the file:line citations of its prose.""" - paths: set[str] = set() + source_ref, the file:line citations of its prose and the file its + version is read from.""" + basis = profile.get("version_basis") + bases = ([basis] if isinstance(basis, dict) and "kind" in basis + else list(basis.values()) if isinstance(basis, dict) else []) + paths: set[str] = { + str(b["path"]) for b in bases + if isinstance(b, dict) and b.get("kind") == "file" and b.get("path") + } for key, value in _walk(profile): if key == "source_ref" and isinstance(value, (str, dict)): for _label, refs in source_ref_values(value): @@ -355,6 +362,30 @@ def _commit_url(repo: upstream.Repo, pin: str) -> str: return f"{base}/commit/{pin}" +def version_basis_url(profile: dict, basis: dict, mode: str = "") -> str: + """Where a reader checks a version: the file stating it at the pin, or + the tag it counts from. "" when neither can be linked. + + The Version cell printed `libretro/handy.h` as bare text beside a + pinned Revision link, so the #define it reads was one search away. + """ + field = "upstream" if basis.get("repo") == "upstream" else "source" + declared = next(iter(_repo_pins(profile, field, mode)), None) + repo = _browse_repo(declared[0]) if declared else None + if repo is None: + return "" + kind, path, tag = basis.get("kind"), str(basis.get("path") or ""), str(basis.get("tag") or "") + if kind == "file" and path: + pin = next((p for r, p in _forge_sources(profile, mode) + if (r.host, r.slug) == (repo.host, repo.slug)), "") + return _source_permalink(repo, pin, path, None, None) if pin and tree_holds( + repo, pin, path) else "" + if kind == "tag" and tag and repo.family in ("github", "gitlab"): + tree = "/-/tree/" if repo.family == "gitlab" else "/tree/" + return f"https://{repo.host}/{repo.owner}/{repo.name}{tree}{urllib.parse.quote(tag)}" + return "" + + def revision_markdown(profile: dict) -> str: """Each repository the profile reads, linked at the commit it was read at. diff --git a/scripts/sitestarlight.py b/scripts/sitestarlight.py index bf532b9f5..f4ffbf027 100644 --- a/scripts/sitestarlight.py +++ b/scripts/sitestarlight.py @@ -244,10 +244,21 @@ def _tooltip_line(line: str, pattern: re.Pattern, pending: dict[str, str]) -> st return "".join(out) +_NAME_BEFORE = re.compile(r"(?:/|\b[A-Z][\w'-]*[ \t]+)$") + + +def _in_proper_name(term: str, before: str) -> bool: + """A capitalised term after a capitalised word or a slash is part of a + name: Free Unix Spectrum Emulator, Windows Terminal, Documents/Dolphin + Emulator. An acronym (BIOS, ZIP) is the term wherever it stands.""" + first = term.split()[0] + return first[:1].isupper() and not first.isupper() and bool(_NAME_BEFORE.search(before)) + + def _tooltip_text(text: str, pattern: re.Pattern, pending: dict[str, str]) -> str: def swap(match: re.Match) -> str: term = match.group(1) - if term not in pending: + if term not in pending or _in_proper_name(term, text[:match.start()]): return term definition = pending.pop(term) return f'{term}' diff --git a/scripts/validate_pr.py b/scripts/validate_pr.py index 9d99637df..cbc6fa1bd 100644 --- a/scripts/validate_pr.py +++ b/scripts/validate_pr.py @@ -21,6 +21,7 @@ import argparse import json import os import re +import shutil import subprocess import sys from pathlib import Path @@ -36,6 +37,7 @@ from common import ( ) from hashing import parse_hash_list from nativemode import digest_algorithm +from dedup import _in_nodedup_dir from validation import frontend_digest_matches, inner_rom_check try: @@ -79,6 +81,13 @@ class ValidationResult: self.md5 = "" self.crc32 = "" self.size = 0 + # "held" (a copy of a file the collection has) or "unknown" (no list + # or profile declares it): no check fails, and neither is a file + # the pull request adds to the collection. + self.outcome = "" + # Where --place copies the file: one folder and one declared name, or + # the .variants/ slot of a name the collection already holds. + self.destination = "" def add_check(self, passed: bool, message: str) -> None: self.checks.append(("PASS" if passed else "FAIL", message)) @@ -93,8 +102,12 @@ class ValidationResult: def passed(self) -> bool: return all(s != "FAIL" for s, _ in self.checks) + @property + def status(self) -> str: + return "FAIL" if not self.passed else "REVIEW" if self.outcome else "PASS" + def to_markdown(self) -> str: - status = "PASS" if self.passed else "FAIL" + status = self.status lines = [f"### {status} `{self.filename}`"] lines.append("") lines.append(f"- **Path**: `{self.filepath}`") @@ -147,7 +160,7 @@ def load_platform_hashes(platforms_dir: str, db: dict | None = None) -> dict: under, read from the other files of that system it holds. """ known = {"sha1": set(), "md5": set(), "sha256": set(), "crc32": set(), - "names": set(), "folders": {}, "sizes": {}, "entries": {}} + "names": set(), "folders": {}, "sizes": {}, "entries": {}, "named": {}} systems_of: dict[str, set[str]] = {} folders_of: dict[str, dict[str, int]] = {} files = (db or {}).get("files", {}) @@ -194,6 +207,9 @@ def load_platform_hashes(platforms_dir: str, db: dict | None = None) -> dict: ) for value in (*sha1s, *md5s): systems_of.setdefault(value, set()).add(system_id) + for value in (*sha1s, *md5s, *sha256s): + if "name" in file_entry: + known["named"].setdefault(value, set()).add(file_entry["name"]) _note_folder(folders_of, system_id, files, by_md5, sha1s, md5s) known["folders"] = _system_homes(systems_of, folders_of) @@ -251,6 +267,7 @@ def load_profile_hashes( """ known: dict[str, dict] = { "sha1": {}, "sha256": {}, "md5": {}, "crc32": {}, "sizes": {}, "folders": {}, + "named": {}, } systems_of: dict[str, set[str]] = {} folders_of: dict[str, dict[str, int]] = {} @@ -274,6 +291,8 @@ def load_profile_hashes( for algo, values in hashes.items(): for value in values: known[algo].setdefault(value, set()).add(emulator) + if isinstance(entry.get("name"), str): + known["named"].setdefault(value, set()).add(entry["name"]) # The entry's own system, or the profile's when it emulates one. system = entry.get("system") or ( declared_systems[0] if len(declared_systems) == 1 else None @@ -374,9 +393,15 @@ def validate_file( # PR that regenerated database.json was told each new file # duplicated itself. result.add_check(True, "Already in the database at this path") + elif _in_nodedup_dir(relative): + result.add_info( + f"Identical to `{existing['path']}`, kept: this tree is read by path" + ) else: + result.outcome = "held" result.add_warning( - f"Duplicate: identical file already exists at `{existing['path']}`" + f"Already held at `{existing['path']}`: nothing to add, " + "leave this copy out of the pull request" ) else: result.add_check(True, "Not a duplicate in database") @@ -421,8 +446,11 @@ def validate_file( "Filename matches a known requirement but hash differs - may be a variant" ) else: + result.outcome = "unknown" result.add_warning( - "File not referenced in any platform config - needs manual review" + "Not recognised: no platform list or emulator profile declares this " + "file. Say in the pull request which emulator reads it, for which " + "system, and where the dump comes from" ) normalized = os.path.normpath(relative) @@ -452,11 +480,27 @@ def validate_file( result.add_check(True, f"Correct placement: bios/{parts[1]}/{parts[2]}/") else: result.add_warning("File should be in bios/Manufacturer/Console/ structure") + elif result.outcome == "held": + pass elif expected_folders: # The contributor asks before forking: say where it goes. + names = { + name + for table in (platform_hashes, profile_hashes) + for value in (result.sha1, result.md5, hashes.get("sha256", "")) + for name in table.get("named", {}).get(value, ()) + } + name = next(iter(names)) if len(names) == 1 else "" + if len(expected_folders) == 1 and name and "/" not in name: + folder = expected_folders[0] + result.destination = ( + f"{folder}.variants/{name}.{result.sha1[:8]}" + if os.path.exists(folder + name) else folder + name + ) result.add_warning( "File is not under bios/ yet. Its system's files are in " + " or ".join(f"`{f}`" for f in expected_folders) + + (f". --place copies it to `{result.destination}`" if result.destination else "") ) elif _variant_home(db, result.filename, result.sha1): result.add_warning( @@ -485,9 +529,11 @@ def validate_file( if (name_known and not (sha1_known or md5_known or other_known or accepted_by) and not by_profile and size_fits): home = _variant_home(db, result.filename) + slot = f"{home}.variants/{result.filename}.{result.sha1[:8]}" + if home and not normalized.startswith("bios" + os.sep): + result.destination = slot result.add_info( - "This may be a valid variant. If accepted, it will be placed in " - f"`{home}.variants/{result.filename}.{result.sha1[:8]}`" + f"This may be a valid variant. If accepted, it will be placed in `{slot}`" ) return result @@ -558,30 +604,35 @@ def _repo_key(url: str) -> str: def _project_main_line() -> str | None: - """The project's main line, fetched when no remote of this clone names it. + """The project's main line, fetched when this clone does not hold it. A fork's clone has origin alone. Once the commit is pushed to the fork's main, origin/main is HEAD and the diff against it was empty, so --changed - found nothing and exited 0. None leaves the upstream/ and origin/ lines - in charge: a remote of that name names the project, or the clone has no - remote at all and only its uncommitted files can be read. + found nothing and exited 0. A remote added as CONTRIBUTING says and never + fetched did the same: upstream/main did not exist and origin/main won. + None leaves the upstream/ and origin/ lines in charge when the clone has + no remote at all and only its uncommitted files can be read. """ remotes = _git_lines("remote") or [] if not remotes: return None project = {_repo_key(url) for url in PROJECT_URLS} + source = PROJECT_URLS[0] for remote in remotes: urls = _git_lines("remote", "get-url", "--all", remote) or [] if any(_repo_key(url) in project for url in urls): - return None if remote in ("upstream", "origin") else f"{remote}/main" + if _git_lines("rev-parse", "--verify", "--quiet", f"{remote}/main"): + return f"{remote}/main" + source = remote + break fetched = subprocess.run( - ["git", "fetch", "--quiet", "--no-tags", PROJECT_URLS[0], "main"], + ["git", "fetch", "--quiet", "--no-tags", source, "main"], capture_output=True, text=True, check=False, ) if fetched.returncode != 0: raise RuntimeError( - f"no remote of this clone is {PROJECT_URLS[0]}, and fetching its " - f"main failed: {fetched.stderr.strip()}" + f"this clone does not hold the main of {PROJECT_URLS[0]}, and " + f"fetching its main from {source} failed: {fetched.stderr.strip()}" ) return "FETCH_HEAD" @@ -675,6 +726,28 @@ def _files_to_validate( return files +def place(results: list[ValidationResult]) -> list[str]: + """Copy each placeable file to its destination, never over a file. + + The report named the folder of each file and left 40 copies from a card + to be made by hand, each .variants/ name retyped. The source is copied, + not moved: it is often a card or another tree the contributor keeps. + """ + placed = [] + for r in results: + if not (r.passed and r.destination and not r.outcome): + continue + target = Path(r.destination) + if target.exists(): + print(f"not placed: {target} exists", file=sys.stderr) + continue + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(r.filepath, target) + placed.append(r.destination) + print(f"placed {r.filepath} at {r.destination}", file=sys.stderr) + return placed + + def _load_database_if_any(path: str) -> dict | None: """The database, optional only where the default is meant. @@ -709,6 +782,11 @@ def main() -> None: "--markdown", action="store_true", help="Output as markdown (for PR comments)" ) output.add_argument("--json", action="store_true", help="Output as JSON") + parser.add_argument( + "--place", action="store_true", + help="copy each recognised file from outside bios/ to the folder and name " + "the report gives, or to .variants/ beside the name it varies", + ) args = parser.parse_args() # The database, platforms and profiles are read from the repository @@ -745,6 +823,8 @@ def main() -> None: { "file": r.filepath, "passed": r.passed, + "outcome": r.outcome or "new", + "destination": r.destination, "sha1": r.sha1, "md5": r.md5, "size": r.size, @@ -754,7 +834,15 @@ def main() -> None: print(json.dumps(output, indent=2)) elif args.markdown: lines = ["## BIOS Validation Report", ""] - status = "all checks passed" if all_passed else "some checks failed" + held = sum(r.outcome == "held" for r in results) + unknown = sum(r.outcome == "unknown" for r in results) + review = ", ".join(part for part in ( + f"{held} already held" if held else "", + f"{unknown} not recognised" if unknown else "", + ) if part) + status = ("some checks failed" if not all_passed + else f"no check failed, to review: {review}" if review + else "all checks passed") lines.append(f"**Status**: {status}") lines.append("") @@ -765,8 +853,7 @@ def main() -> None: print("\n".join(lines)) else: for r in results: - status = "PASS" if r.passed else "FAIL" - print(f"\n[{status}] {r.filepath}") + print(f"\n[{r.status}] {r.filepath}") if r.sha1: print(f" SHA1: {r.sha1}") print(f" MD5: {r.md5}") @@ -783,6 +870,9 @@ def main() -> None: ) print(f" [{marker}] {m}") + if args.place: + place(results) + if not all_passed: sys.exit(1) diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index ed9cfbf35..4af9f54a4 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -6781,7 +6781,8 @@ class SkipPacksHelpSaysWhatRuns(unittest.TestCase): ) text = " ".join(proc.stdout.split()) self.assertNotIn("Only regenerate DB and verify", text) - self.assertIn("Steps 7 and 8 still rewrite README.md and the site pages in website/", text) + self.assertIn("step 4e still rewrites install/games/, and steps 7 to 8b rewrite README.md " + "and the site unless --skip-docs is given", text) self.assertNotIn("docs/", text) def test_every_step_it_names_is_one_it_skips(self): diff --git a/tests/test_contributing_text.py b/tests/test_contributing_text.py index 09392caea..66ced6b53 100644 --- a/tests/test_contributing_text.py +++ b/tests/test_contributing_text.py @@ -78,6 +78,36 @@ class BeforeOpeningAPullRequest(unittest.TestCase): self.assertIn("profile_sync.py --added --base upstream/main --strict", block) self.assertNotIn("--changed --strict", block) + def test_it_names_the_site_build_and_the_packages_ci_runs(self): + """A wiki-only change passed every listed check and failed CI on + validate_site.py, which the list did not name.""" + text = generate_readme.generate_contributing() + section = text.split("## Before opening a PR", 1)[1].split("\n## ", 1)[0] + workflow = (REPO_ROOT / ".github" / "workflows" / "validate.yml").read_text(encoding="utf-8") + for command in ("npm ci --prefix website", "npm run build --prefix website", + "validate_site.py --site-dir website/dist", + "vermin --no-tips -t=3.8- --violations install.py"): + with self.subTest(command=command): + self.assertIn(command, workflow) + self.assertIn(command, section) + installed = re.search(r'^\s*run: pip install (pyyaml "jsonschema.*markdown.*)$', + workflow, re.M).group(1) + pip_line = next(line for line in section.splitlines() if line.startswith("pip install")) + for package in (re.split(r"[\[=]", token.strip('"'))[0] for token in installed.split()): + with self.subTest(package=package): + self.assertRegex(pip_line, rf"\b{package}\b") + + def test_a_file_too_large_for_git_has_a_route(self): + """The guide sent any file over 25 MB through a clone, which GitHub + refuses past 100 MB, and only a maintainer can write to the release.""" + import validate_pr + + text = generate_readme.generate_contributing() + section = text.split("## What happens next", 1)[1].split("\n## ", 1)[0] + self.assertEqual(validate_pr.MAX_FILE_SIZE, 100 * 1024 * 1024) + self.assertIn("100 MB", section) + self.assertIn(f"]({validate_pr.LARGE_FILE_ISSUE_URL})", section) + def test_it_installs_what_the_checks_need_and_no_site_package(self): """The site is built by Node from website/package-lock.json: a pip line naming the old theme installed packages nothing reads.""" diff --git a/tests/test_deterministic_zip.py b/tests/test_deterministic_zip.py index d5f094812..359f508c2 100644 --- a/tests/test_deterministic_zip.py +++ b/tests/test_deterministic_zip.py @@ -252,6 +252,21 @@ class TestPackDeterminism(unittest.TestCase): names = {i.filename.rsplit("/", 1)[-1] for i in zf.infolist()} self.assertIn("romset.zip", names) + def test_a_romset_is_the_collection_bytes_and_the_wiki_says_so(self): + """The pack copies each ZIP byte for byte since fed03c3db, and the + wiki still told a third party to rebuild romsets to compare.""" + pack = self._build("out1") + with zipfile.ZipFile(pack) as zf: + member = next(n for n in zf.namelist() if n.endswith("romset.zip")) + self.assertEqual(zf.read(member), (self.bios / "romset.zip").read_bytes()) + root = Path(__file__).resolve().parent.parent / "wiki" + for page in ("architecture.md", "tools.md"): + with self.subTest(page=page): + text = " ".join((root / page).read_text(encoding="utf-8").split()) + self.assertNotIn("rebuilt deterministically", text) + self.assertNotIn("rebuilds MAME", text) + self.assertNotIn("Rebuild MAME BIOS ZIPs", text) + def test_every_member_carries_the_fixed_epoch(self): """A member's date must come from the build's inputs, not its clock. diff --git a/tests/test_doc_pages.py b/tests/test_doc_pages.py index 0935b2bcb..bb99c68cb 100644 --- a/tests/test_doc_pages.py +++ b/tests/test_doc_pages.py @@ -1799,5 +1799,91 @@ class ReportExcerptIsTheRun(unittest.TestCase): self.assertIn(line, printed) +class ClaimRuleNamesTheSizeProof(unittest.TestCase): + """architecture.md said a file found by its name claims nothing, while + slots.py has counted a name-found copy at the declared size against a + platform file of another size since 2b535525e.""" + + def test_the_page_states_what_slots_counts(self): + import slots + + self.assertIn(slots.SIZE_EXACT, slots.PROVEN_STATUSES) + page = " ".join((ROOT / "wiki" / "architecture.md").read_text(encoding="utf-8").split()) + section = page.split("### When both layers claim one destination", 1)[1].split("###", 1)[0] + self.assertIn(f"(`{slots.SIZE_EXACT}`)", section) + self.assertIn("Two files of equal size are revisions", section) + + +class CrossReferenceSettlesBySlotAndSize(unittest.TestCase): + """reading-reports.md said the cross-reference checks whether a profile's + files "appear in the platform config", a by-name rule, while verify + settles a file only at its slot or at a size the core accepts.""" + + def test_the_page_states_the_rule_verify_reads(self): + from validation import declaration_settles + + rom1 = {"name": "ROM1.BIN", "size": 32768, "validation": ["size"]} + self.assertFalse(declaration_settles(rom1, "ROM1.BIN", {"ROM1.BIN": [(4096, "rom1.bin")]}, + dest="dosbox/ROM1.BIN")) + self.assertTrue(declaration_settles(rom1, "ROM1.BIN", {"ROM1.BIN": [(32768, "rom1.bin")]}, + dest="dosbox/ROM1.BIN")) + page = " ".join((ROOT / "wiki" / "reading-reports.md").read_text(encoding="utf-8").split()) + section = page.split("### Cross-reference section", 1)[1].split("###", 1)[0] + self.assertIn("declaration_settles()", section) + self.assertIn("A name alone is not enough", section) + self.assertNotIn("appear in the platform config", section) + + +class CommittedLargeFilesAreTheOnesNamed(unittest.TestCase): + """release-process.md named tbblue.mmc among the committed files over + 50 MB two weeks after it left bios/, and not uqm-remix-disc1.uqm.""" + + def test_the_list_is_the_tree(self): + import subprocess + + listed = subprocess.run(["git", "ls-files", "-z", "bios"], cwd=ROOT, capture_output=True, + check=True).stdout.decode("utf-8").split("\0") + large = sorted(Path(path).name for path in listed + if path and (ROOT / path).is_file() + and (ROOT / path).stat().st_size > 50_000_000) + page = " ".join((ROOT / "wiki" / "release-process.md").read_text(encoding="utf-8").split()) + said = re.search(r"Size does not decide it: (\w+) committed files are over 50 MB \(([^)]*)\)", + page) + self.assertIsNotNone(said) + self.assertEqual(sorted(re.findall(r"`([^`]+)`", said.group(2))), large) + self.assertEqual(said.group(1), {3: "three", 4: "four", 5: "five"}.get(len(large))) + + +class SkipFlagsNameTheSteps(unittest.TestCase): + """--skip-packs said "add --skip-docs to stop after verify" while 4d and + 4e still ran, and the flag table left 8b out of --skip-docs.""" + + STEP = r"\b\d[a-z]?\d?\b" + + def test_the_flag_row_the_help_and_the_usage_agree_with_the_step_table(self): + page = (ROOT / "wiki" / "tools.md").read_text(encoding="utf-8") + table = page.split("Pipeline steps:", 1)[1].split("Pipeline flags:", 1)[0] + source = (ROOT / "scripts" / "pipeline.py").read_text(encoding="utf-8") + helps = {} + for node in ast.walk(ast.parse(source)): + if (isinstance(node, ast.Call) and getattr(node.func, "attr", "") == "add_argument" + and node.args and isinstance(node.args[0], ast.Constant)): + helps.update({node.args[0].value: ast.literal_eval(kw.value) + for kw in node.keywords if kw.arg == "help"}) + for flag in ("--skip-packs", "--skip-docs"): + steps = set() + for row in table.splitlines(): + cells = [c.strip() for c in row.split("|")] + if len(cells) > 3 and cells[3].startswith(f"`{flag}`"): + steps.add(cells[1]) + self.assertTrue(steps) + row = re.search(rf"^\| `{flag}` \| skip steps ([^.|]*)", page, re.M).group(1) + usage = re.search(rf"pipeline\.py {flag} +# skip steps (.*)$", source, re.M).group(1) + said = helps[flag].split(":", 1)[0] + for text in (row, usage, said): + with self.subTest(flag=flag, text=text): + self.assertEqual(set(re.findall(self.STEP, text)), steps) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_file_consumers.py b/tests/test_file_consumers.py index 50790d2f0..1d382916f 100644 --- a/tests/test_file_consumers.py +++ b/tests/test_file_consumers.py @@ -17,7 +17,7 @@ from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO_ROOT / "scripts")) -from cross_reference import FileConsumers # noqa: E402 +from cross_reference import FileConsumers, entry_target # noqa: E402 from generate_site import generate_system_page # noqa: E402 _TMP = tempfile.TemporaryDirectory() @@ -246,7 +246,6 @@ class EveryReaderIsTheEmulatorPackFile(unittest.TestCase): build_zip_contents_index, load_data_dir_registry, load_emulator_profiles, resolve_local_file, ) - from validation import with_archive_members db_path = REPO_ROOT / "database.json" if not db_path.exists(): @@ -275,9 +274,12 @@ class EveryReaderIsTheEmulatorPackFile(unittest.TestCase): if members: from_trees |= {(sha1, name) for sha1 in members.values()} continue + # A ROM read inside an archive is the archive, as the + # emulator pack resolves it (archive_entry). + target, dest = entry_target(entry, files) path, status = resolve_local_file( - {**with_archive_members(entry, files), "source_profile": name}, - db, zips, dest_hint=entry.get("path") or "", + {**target, "source_profile": name}, + db, zips, dest_hint=dest, data_dir_registry=registry, ) if path and status not in ("not_found", "hash_mismatch"): diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 92b51c817..fc511761f 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -5974,6 +5974,13 @@ class TestBranchProfiles(unittest.TestCase): ["draft", "new"], ) + def test_an_unfetched_base_says_to_fetch(self): + """`--base upstream/main` with upstream added and never fetched + stopped on git's "Not a valid object name" and no next step.""" + with self.assertRaises(SystemExit) as raised: + profile_sync.branch_profiles("emulators", "upstream/main", added_only=True) + self.assertIn("git fetch upstream", str(raised.exception)) + def test_the_flag_selects_those_profiles(self): args = profile_sync.build_parser().parse_args(["--changed", "--base", "main"]) profiles = {"kept": {}, "edited": {}, "new": {}, "draft": {}} diff --git a/tests/test_prose_style.py b/tests/test_prose_style.py index 490b63b83..650e57ecd 100644 --- a/tests/test_prose_style.py +++ b/tests/test_prose_style.py @@ -308,6 +308,22 @@ class ProfilesStateFactsAboutOtherProjects(unittest.TestCase): ] self.assertEqual(offenders, [], "\n".join(offenders[:40])) + # A project's state is a dated fact or its own words, never a verdict: + # "Upstream project is dead" (yabause), "abandoned fork" (gearboy). + PROJECT_VERDICTS = re.compile( + r"\b(?:(?:project|upstream)(?:\s+project)?\s+(?:is\s+)?dead|" + r"abandoned\s+(?:fork|since)|project\s+abandoned|never-completed)\b", + re.IGNORECASE, + ) + + def test_no_verdict_on_a_project(self): + offenders = [ + f"{path.relative_to(ROOT)}: {found.group(0)}" + for path in sorted((ROOT / "emulators").glob("*.yml")) + for found in self.PROJECT_VERDICTS.finditer(path.read_text(encoding="utf-8")) + ] + self.assertEqual(offenders, [], "\n".join(offenders[:40])) + def test_no_grade_on_another_projects_file(self): offenders = [ f"{path.relative_to(ROOT)}: {line.strip()[:100]}" diff --git a/tests/test_refresh_failures.py b/tests/test_refresh_failures.py index 2225ccfe5..11e2bfc27 100644 --- a/tests/test_refresh_failures.py +++ b/tests/test_refresh_failures.py @@ -130,7 +130,7 @@ class AForkPushedToItsOwnMain(unittest.TestCase): # A project that cannot be reached is an error, never "nothing". missing = self._changed(clone, str(Path(tmp, "absent"))) self.assertNotEqual(missing.returncode, 0) - self.assertIn("fetching its main failed", missing.stderr) + self.assertIn("fetching its main from", missing.stderr) class RefreshFailuresAreFailures(unittest.TestCase): diff --git a/tests/test_site_components.py b/tests/test_site_components.py index 4bd277d60..17fe94afb 100644 --- a/tests/test_site_components.py +++ b/tests/test_site_components.py @@ -334,6 +334,22 @@ class NoLevelBadgeOnATabRoot(unittest.TestCase): self.assertNotIn('class="rb-level ', _title_through_heading(_built(page))) +class OneTabIsNoTabBar(unittest.TestCase): + """142 tab bars held one tab, most of them "On the device" right under + the heading "On the device".""" + + def test_a_single_item_stands_alone(self): + import sitemarkup + import siteguides + + self.assertEqual(sitemarkup.tabs([("Linux", ["run it"])]), ["**Linux**", "", "run it", ""]) + self.assertEqual(sitemarkup.tabs([("", ["run it"])]), ["run it", ""]) + self.assertIn('', sitemarkup.tabs([("A", ["a"]), ("B", ["b"])])) + device = "\n".join(siteguides.terminal_tabs(("device",), {})) + self.assertNotIn("", 1)[0]) + def test_no_build_is_a_skip_not_a_pass(self): + """Every missing page was a `continue`: with no build the test ran no + assertion and still reported ok.""" + from unittest import mock + + module = sys.modules[__name__] + case = WideTablesStackAndFiltersAreBuilt("test_every_wide_table_stacks") + result = unittest.TestResult() + with mock.patch.object(module, "DIST", REPO_ROOT / "tmp" / "no-such-dist"): + case.run(result) + self.assertEqual(len(result.skipped), 1) + def test_long_tables_get_their_labels_at_runtime(self): """Labels on every cell of a long stacked table were 122 KB of emulators/mame and took it over the 600,000-byte page budget.""" diff --git a/tests/test_site_emulator_page.py b/tests/test_site_emulator_page.py index ba592d3e3..be1165ee1 100644 --- a/tests/test_site_emulator_page.py +++ b/tests/test_site_emulator_page.py @@ -68,6 +68,38 @@ class WhoShipsTheBuildAndWhy(unittest.TestCase): "(https://example.org/news) (version 13.0 announced there, " "found only as rebuilt copies) |", page) + def test_the_version_links_its_proof(self): + """`0.97, stated in libretro/handy.h` was bare text beside a pinned + Revision link: the #define it reads was one search away.""" + import generate_site as gs + import sitesources + import yaml + + handy = yaml.safe_load((REPO_ROOT / "emulators" / "handy.yml").read_text(encoding="utf-8")) + path = handy["version_basis"]["path"] + self.assertIn(path, sitesources.cited_paths(handy)) + self.assertIn(f"stated in [`{path}`](https://github.com/libretro/libretro-handy/blob/" + f"{handy['source_commit']}/{path})", gs.version_markdown(handy)) + tagged = {"source": "https://github.com/owner/emu", "source_commit": "a" * 40, + "core_version": "1.2-3-gaaaaaaa", + "version_basis": {"kind": "tag", "tag": "1.2", "distance": 3}} + self.assertEqual(gs.version_markdown(tagged), "1.2-3-gaaaaaaa, 3 commits after tag " + "[`1.2`](https://github.com/owner/emu/tree/1.2)") + # A file no recorded tree confirms stays plain: no link beats a 404. + unread = {**tagged, "version_basis": {"kind": "file", "path": "src/none.h"}} + self.assertEqual(gs.version_markdown(unread), "1.2-3-gaaaaaaa, stated in `src/none.h`") + + def test_the_upstream_pin_says_why_it_is_held(self): + """mupen64plus showed its core revision and not that AmberELEC + builds that one: read as a profile behind HEAD.""" + page = _page("mupen64plus", { + "type": "libretro", + "upstream_frozen": "AmberELEC builds the core at this revision.", + "files": [], + }) + self.assertIn("| Upstream revision held | AmberELEC builds the core at this " + "revision. |", page) + class ACardKeepsItsAddress(unittest.TestCase): """emulators/raze/#file-duke3d-grp stopped pointing at its card once a diff --git a/tests/test_site_emulators.py b/tests/test_site_emulators.py index 70d1d9782..79e871b40 100644 --- a/tests/test_site_emulators.py +++ b/tests/test_site_emulators.py @@ -254,11 +254,16 @@ class WhatTheAppShipsIsNoFileToGet(unittest.TestCase): self.assertIn(">comes with the app", badges) self.assertNotIn(">required", badges) self.assertNotIn(">missing", badges) - # Installed by one build only: the other still reads it from the folder. + # Narrowed to one build, the entry is that build's alone, as verify + # and the pack read it: EightyOne installs its ROMs, and the libretro + # core, which embeds them, reads no entry. Nothing to fetch. dual = " ".join(gs._file_badges({"name": "a", "bundled": True, "mode": "standalone", "required": True}, False)) - self.assertIn(">required", dual) + self.assertNotIn(">required", dual) + self.assertNotIn(">missing", dual) self.assertIn("installed with the standalone emulator", dual) + rom = next(f for f in _profile("81")["files"] if f["name"] == "jupiterace.rom") + self.assertTrue(gs.comes_with_app(rom)) def test_counts_leave_them_out(self): shipped = sum(1 for f in self.profile["files"] if gs.comes_with_app(f)) @@ -399,7 +404,9 @@ class AnAppThatReadsAnyBios(unittest.TestCase): install = self.page[self.page.index("Install this emulator's files"):] install = install[:install.index("")] self.assertRegex(install, r"With [^:]+: the installer finds") - self.assertEqual(set(re.findall(r'', install)), {"On the device"}) + # One place to run it, so no tab bar (one tab offers no choice). + self.assertNotIn(" 1) - self.assertEqual(line, generate_site.OTHER_VERSION) + self.assertEqual(line, generate_site.other_version("retrobat")) + + def test_another_version_is_sent_to_the_check_commands(self): + """A ZIP reader read "Run `--check` after copying." with no command.""" + line = generate_site.other_version("emudeck") + self.assertNotIn("Run `--check`", line) + self.assertIn("](../platforms/emudeck.md#how-to-check)", line) + page = _page("platforms/emudeck.md") + self.assertIn("{ #how-to-check }", page) def test_the_v_prefix_is_dropped(self): self.assertEqual(generate_site.setup_version("retroarch", {}), @@ -925,7 +933,9 @@ class ARouteWithoutANetwork(unittest.TestCase): self.assertEqual(siteguides.GUIDES["retropie"].card_run, ("linux",)) text = _plain_page("platforms/retropie.md") card = text.split("No network?", 1)[1].split("## On the device", 1)[0] - self.assertEqual(_tab_labels(card), {"Linux"}) + # One system, so no tab bar: its label leads the command. + self.assertEqual(_tab_labels(card), set()) + self.assertIn("**Linux**", card) def test_no_zip_is_promised_for_an_unmarked_setup(self): for name in SETUPS: diff --git a/tests/test_site_reference.py b/tests/test_site_reference.py index e32f4610d..5375a7972 100644 --- a/tests/test_site_reference.py +++ b/tests/test_site_reference.py @@ -422,7 +422,11 @@ class BuiltBatoceraPage(unittest.TestCase): # PlayStation's list is long enough to sit on a page of its own. pages = [main] + [p.read_text(encoding="utf-8") for p in (DIST / "platforms").glob("batocera-sony-playstation*/index.html")] - html = "".join(pages) + # Read as a browser shows it: a page with no tab bar is plain + # Markdown, which keeps the entity a name's underscore is written as. + import html as _html + + html = _html.unescape("".join(pages)) for text in ("Served by:", "scph1001_v20.bin", "https://github.com/stenzek/duckstation/blob/"): with self.subTest(text=text): self.assertTrue(text in html) @@ -696,6 +700,23 @@ class ManifestsArePublished(unittest.TestCase): self.assertLess(page.index("## Stable JSON endpoints"), page.index("The DAT is Logiqx XML")) +class TheLookupRecipeSaysWhatASha1Misses(unittest.TestCase): + """The page said a SHA1 lookup was enough, "a setup that lists a file + by MD5 or CRC32 is filed under the SHA1 of the collection file it names + too". A record whose value no collection file answers has no SHA1: 702 + platform MD5s were out of the recipe's reach.""" + + def test_the_page_and_the_records_agree(self): + record = {"layer": "platform", "algorithm": "md5", "value": "0" * 32, + "name": "dmg_boot.bin", "owner": "recalbox"} + self.assertEqual(generate_site._held_under_every_hash([record], {}, {}), []) + self.assertNotIn("sha1", record) + page = " ".join(generate_site.generate_data_page([]).split()) + self.assertNotIn("none is needed", page) + self.assertIn("A list naming a file the collection lacks is filed under its MD5 " + "or CRC32 alone", page) + + class AnIdLeadsToItsData(unittest.TestCase): """8.6: a system by its id, a core by the name the buildbot gives it.""" diff --git a/tests/test_site_render.py b/tests/test_site_render.py index 2ad8e6788..35b27dbc8 100644 --- a/tests/test_site_render.py +++ b/tests/test_site_render.py @@ -427,5 +427,81 @@ class TheSearchSkipsLinkLists(unittest.TestCase): self.assertIn(short, text) +class OneDefinitionPerName(unittest.TestCase): + """generate_site defined _REGION_WORDS twice: the second table replaced + the first at import, and _region_word, written beside the first, never + read it.""" + + def test_no_module_name_is_bound_twice(self): + import ast + + for path in sorted((REPO_ROOT / "scripts").glob("*site*.py")): + seen: dict[str, int] = {} + for node in ast.parse(path.read_text(encoding="utf-8")).body: + if isinstance(node, (ast.FunctionDef, ast.ClassDef)): + names = [node.name] + elif isinstance(node, ast.Assign): + names = [t.id for t in node.targets if isinstance(t, ast.Name)] + elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name): + names = [node.target.id] + else: + continue + for name in names: + self.assertNotIn(name, seen, f"{path.name}:{node.lineno} binds {name} " + f"again, first at line {seen.get(name)}") + seen[name] = node.lineno + + +@unittest.skipUnless((REPO_ROOT / "database.json").is_file(), "database.json is not built") +class AMemberIsReadInItsArchive(unittest.TestCase): + """FBNeo reads sm1.sm1 inside neogeo.zip. Resolved by its crc32, the + entry credited a loose sm1.sm1 FBNeo never opens, and no neogeo.zip card + named FBNeo, while the pack ships it one.""" + + def test_the_archive_is_credited_and_named_on_the_card(self): + import json + + import generate_site as gs + from common import archive_entry, resolve_local_file + from cross_reference import FileConsumers + + db = json.loads((REPO_ROOT / "database.json").read_text(encoding="utf-8")) + profile = yaml.safe_load((REPO_ROOT / "emulators" / "fbneo.yml").read_text(encoding="utf-8")) + files = profile["files"] + consumers = FileConsumers(db) + consumers.add_emulators({"fbneo": profile}) + credited = {consumers.path_of(sha1) for sha1 in consumers.emulators} + shipped, _status = resolve_local_file( + {**archive_entry("neogeo.zip", files), "source_profile": "fbneo"}, db) + self.assertIn(shipped, credited) + self.assertFalse([p for p in credited if p.endswith("/sm1.sm1")]) + sm1 = next(f for f in files if f["name"] == "sm1.sm1") + rows = "\n".join(gs._render_emulator_file(sm1, profile, consumers, files, + lambda _f: True, owner="fbneo")) + self.assertIn("Collection copy: member `sm1.sm1` of", rows) + self.assertIn(shipped, rows) + + +class NoTooltipInsideAName(unittest.TestCase): + """The glossary's capitalised terms matched almost only product names: + Free Unix Spectrum Emulator, Windows Terminal, Documents/Dolphin Emulator.""" + + def test_names_are_left_and_terms_are_marked(self): + import sitestarlight + + terms = {"Emulator": "An app that acts like a console.", + "Terminal": "A window where you type commands.", + "BIOS": "The console's built-in software."} + for line in ("Fuse (Free Unix Spectrum Emulator) reads it.", + "Open Windows Terminal (WSL).", + "Copy it to Documents/Dolphin Emulator/Sys."): + with self.subTest(line=line): + self.assertNotIn("rb-term", sitestarlight.add_tooltips(line, terms)) + marked = sitestarlight.add_tooltips( + "Emulator settings come first. The PlayStation BIOS goes there.", terms) + self.assertIn('title="An app that acts like a console.">Emulator', marked) + self.assertIn('>BIOS', marked) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_site_search.py b/tests/test_site_search.py index ca493590c..051765332 100644 --- a/tests/test_site_search.py +++ b/tests/test_site_search.py @@ -231,6 +231,40 @@ class Descriptions(unittest.TestCase): self.assertTrue(text.startswith("DuckStation emulates Sony PlayStation.")) self.assertNotIn("source-pinned", text) + def test_a_page_whose_files_come_with_the_app_promises_none(self) -> None: + """DaedalusX64's three files come with the app. The result read + "DaedalusX64 BIOS files" over "DaedalusX64 emulates Nintendo 64.".""" + saved, shipped = dict(gs.PAGE_DATA), set(gs.NOTHING_TO_FETCH) + self.addCleanup(lambda: (gs.PAGE_DATA.clear(), gs.PAGE_DATA.update(saved), + gs.NOTHING_TO_FETCH.clear(), gs.NOTHING_TO_FETCH.update(shipped))) + with open(REPO_ROOT / "emulators" / "daedalusx64.yml", encoding="utf-8") as handle: + gs.record_emulator_page("daedalusx64", yaml.safe_load(handle)) + key = Path("emulators/daedalusx64.md") + self.assertEqual(gs._browser_title(key, "DaedalusX64"), "DaedalusX64 emulator") + body = "# DaedalusX64\n\nDaedalusX64 emulates Nintendo 64.\n" + self.assertEqual(gs._page_description(body, key, "DaedalusX64"), + "DaedalusX64 emulates Nintendo 64. Every file it reads comes with the app.") + + def test_an_initial_does_not_end_the_sentence(self) -> None: + """hcl's description was "Hydra Castle Labyrinth: open-source port of E.".""" + body = ("# Hydra Castle Labyrinth\n\n
\nTechnical notes\n\n" + "Open-source port of E. Hashimoto's 2008 freeware Metroidvania. It reads " + "its data folder.\n\n
\n") + text = gs._emulator_description(body, "emulators/hcl.md", "Hydra Castle Labyrinth", "") + self.assertTrue(text.startswith("Hydra Castle Labyrinth: open-source port of E. " + "Hashimoto's 2008 freeware Metroidvania."), text) + self.assertNotIn("It reads", text) + + def test_a_platform_page_says_what_it_gives(self) -> None: + """All 18 ended on the label of a link a snippet cannot follow, + "ROCKNIX website.", and never named the BIOS files or their folder.""" + body = ("# ROCKNIX\n\nA system for handhelds. [ROCKNIX website](https://rocknix.org).\n\n" + "## On the device { #install }\n") + self.assertEqual( + gs._page_description(body, Path("platforms/rocknix.md"), "ROCKNIX"), + "The BIOS files for ROCKNIX, the folder they go in and the command that installs " + "them. A system for handhelds.") + def test_a_console_page_is_described_by_its_start_line(self) -> None: body = "# Sega: Dreamcast\n\nPart of Sega.\n\n**Start with**: `dc_boot.bin`, `dc_flash.bin`.\n" text = gs._page_description(body, Path("systems/sega-dreamcast.md"), "Sega: Dreamcast") diff --git a/tests/test_site_system_names.py b/tests/test_site_system_names.py index da1103b97..bfc883fc4 100644 --- a/tests/test_site_system_names.py +++ b/tests/test_site_system_names.py @@ -116,5 +116,24 @@ class SystemNames(unittest.TestCase): self.assertFalse(names & siterender.program_names(self.profiles)) +class SectionsComeBeforeThePlan(unittest.TestCase): + """plan_emulator_pages names systems. Run before set_console_sections, + it made siterender read ./database.json and ./emulators whatever --db + and --emulators-dir gave, so the parts were cut on another collection.""" + + def test_generate_installs_the_sections_before_planning(self): + import ast + import inspect + + import generate_site + + tree = ast.parse(inspect.getsource(generate_site._generate)) + lines = {} + for node in ast.walk(tree): + if isinstance(node, ast.Call) and isinstance(node.func, ast.Name): + lines.setdefault(node.func.id, node.lineno) + self.assertLess(lines["set_console_sections"], lines["plan_emulator_pages"]) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_validate_pr.py b/tests/test_validate_pr.py index 93575737d..642420795 100644 --- a/tests/test_validate_pr.py +++ b/tests/test_validate_pr.py @@ -139,9 +139,25 @@ class AFileOnlyACoreReadsIsKnown(_Fixture): ) self.assertIn( ("WARN", "File is not under bios/ yet. Its system's files are in " - "`bios/Maker/Console/`"), + "`bios/Maker/Console/`. --place copies it to `bios/Maker/Console/b.bin`"), result.checks, ) + self.assertEqual(result.destination, "bios/Maker/Console/b.bin") + # A file of that name already there: the copy is a variant. + self._bios_file("bios/Maker/Console/b.bin", b"another") + self.assertEqual( + validate_pr.validate_file("Downloads/b.bin", db, EMPTY_HASHES, known).destination, + f"bios/Maker/Console/.variants/b.bin.{result.sha1[:8]}", + ) + # Copied under the declared name, never over a file, and only once. + card = self._bios_file("card/B.BIN", missing) + named = validate_pr.validate_file(card, db, EMPTY_HASHES, known) + self.assertEqual(validate_pr.place([named]), [named.destination]) + self.assertEqual((self.tmp / named.destination).read_bytes(), missing) + self.assertTrue((self.tmp / card).is_file()) + self.assertEqual(validate_pr.place([named]), []) + unknown = validate_pr.validate_file(self._bios_file("card/x.bin"), db, EMPTY_HASHES, known) + self.assertEqual((unknown.outcome, unknown.destination), ("unknown", "")) placed = validate_pr.validate_file( self._bios_file("bios/Other/Place/b.bin", missing), db, EMPTY_HASHES, known, ) @@ -243,20 +259,38 @@ class HashRecognition(_Fixture): self.assertIn(".variants/known.bin.", markdown) def test_an_unreferenced_file_asks_for_review(self): + """Random bytes read "PASS" and "all checks passed", and the warning + named the platform configs alone though the profiles were searched.""" rel = self._bios_file("bios/Sony/PS/mystery.bin") - markdown = validate_pr.validate_file(rel, None, EMPTY_HASHES).to_markdown() - self.assertIn("needs manual review", markdown) + result = validate_pr.validate_file(rel, None, EMPTY_HASHES) + markdown = result.to_markdown() + self.assertEqual(result.outcome, "unknown") + self.assertIn("### REVIEW", markdown) + self.assertIn("no platform list or emulator profile declares this file", markdown) + self.assertIn("which emulator reads it", markdown) def test_a_duplicate_of_a_known_file_is_reported(self): - rel = self._bios_file("bios/Sony/PS/dupe.bin") + """A copy of a held file passed, and was sent to the folder that + already holds it.""" + rel = self._bios_file("elsewhere/dupe.bin") probe = validate_pr.validate_file(rel, None, EMPTY_HASHES) db = {"files": {probe.sha1: {"path": "bios/Sony/PS/original.bin"}}} - markdown = validate_pr.validate_file(rel, db, EMPTY_HASHES).to_markdown() - self.assertIn("Duplicate", markdown) - self.assertIn("original.bin", markdown) + known = {**EMPTY_HASHES, "sha1": {probe.sha1}, + "folders": {probe.sha1: {"bios/Sony/PS/"}}} + result = validate_pr.validate_file(rel, db, known) + markdown = result.to_markdown() + self.assertEqual(result.outcome, "held") + self.assertIn("### REVIEW", markdown) + self.assertIn("Already held at `bios/Sony/PS/original.bin`: nothing to add", markdown) + self.assertNotIn("not under bios/", markdown) # A duplicate is worth saying out loud but must not block the PR. - self.assertTrue(validate_pr.validate_file(rel, db, EMPTY_HASHES).passed) + self.assertTrue(result.passed) + def test_a_copy_in_a_tree_read_by_path_is_kept(self): + rel = self._bios_file("bios/ScummVM/a/dupe.bin") + probe = validate_pr.validate_file(rel, None, EMPTY_HASHES) + db = {"files": {probe.sha1: {"path": "bios/ScummVM/b/dupe.bin"}}} + self.assertNotEqual(validate_pr.validate_file(rel, db, EMPTY_HASHES).outcome, "held") def test_the_file_the_database_holds_is_not_its_own_duplicate(self): """The documented example on a tracked file warned "Duplicate: @@ -334,7 +368,8 @@ class Placement(_Fixture): class Reporting(_Fixture): def test_markdown_carries_every_digest_and_the_verdict(self): rel = self._bios_file("bios/Sega/Saturn/sega.bin") - result = validate_pr.validate_file(rel, None, EMPTY_HASHES) + probe = validate_pr.validate_file(rel, None, EMPTY_HASHES) + result = validate_pr.validate_file(rel, None, {**EMPTY_HASHES, "sha1": {probe.sha1}}) markdown = result.to_markdown() for value in (result.sha1, result.md5, result.crc32): self.assertIn(value, markdown) @@ -344,12 +379,33 @@ class Reporting(_Fixture): rel = self._bios_file("bios/Sega/Saturn/bad.exe") self.assertIn("### FAIL", validate_pr.validate_file(rel, None, EMPTY_HASHES).to_markdown()) + @unittest.skipUnless((REPO_ROOT / "database.json").is_file(), "database.json is not built") + def test_the_status_line_names_what_is_left_to_review(self): + import shutil + import subprocess + + held = REPO_ROOT / "bios" / "Sony" / "PlayStation Portable" / "ppge_atlas.zim" + if not held.is_file(): + self.skipTest(f"{held} is not here") + shutil.copy(held, self.tmp / "ppge_atlas.zim") + (self.tmp / "mystery.bin").write_bytes(os.urandom(4096)) + proc = subprocess.run( + [sys.executable, str(REPO_ROOT / "scripts" / "validate_pr.py"), "--markdown", + str(self.tmp / "mystery.bin"), str(self.tmp / "ppge_atlas.zim")], + capture_output=True, text=True, timeout=300, check=False, + ) + self.assertEqual(proc.returncode, 0, proc.stderr) + self.assertIn("**Status**: no check failed, to review: 1 already held, " + "1 not recognised", proc.stdout) + self.assertNotIn("all checks passed", proc.stdout) + class PlatformHashLoading(_Fixture): def test_missing_directory_yields_empty_sets(self): known = validate_pr.load_platform_hashes(str(self.tmp / "absent")) self.assertEqual(known, {"sha1": set(), "md5": set(), "sha256": set(), "crc32": set(), - "names": set(), "folders": {}, "sizes": {}, "entries": {}}) + "names": set(), "folders": {}, "sizes": {}, "entries": {}, + "named": {}}) def test_declared_hashes_and_names_are_collected(self): platforms = self.tmp / "platforms" diff --git a/tests/test_validate_pr_flow.py b/tests/test_validate_pr_flow.py index e5930f573..0cb7a8667 100644 --- a/tests/test_validate_pr_flow.py +++ b/tests/test_validate_pr_flow.py @@ -13,6 +13,7 @@ import subprocess import sys import tempfile import unittest +from unittest import mock from pathlib import Path import yaml @@ -146,5 +147,45 @@ class RunFromAnyDirectory(unittest.TestCase): self.assertIn("Already in the database at this path", messages) +class AnUnfetchedUpstreamIsFetched(unittest.TestCase): + """CONTRIBUTING has the project added as `upstream` with no fetch. The + remote named the project, so nothing was fetched, upstream/main did not + exist, origin/main was the pushed branch, and --changed found nothing.""" + + def _git(self, cwd: Path, *args: str) -> None: + subprocess.run(["git", *args], cwd=cwd, check=True, capture_output=True, text=True) + + def test_the_branch_file_is_found(self): + with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp: + root = Path(tmp) + project = root / "Abdess" / "retrobios.git" + fork = root / "fork.git" + clone = root / "clone" + for bare in (project, fork): + bare.mkdir(parents=True) + self._git(bare, "init", "-q", "--bare", "-b", "main") + clone.mkdir() + self._git(clone, "init", "-q", "-b", "main") + self._git(clone, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-q", + "--allow-empty", "-m", "base") + self._git(clone, "push", "-q", str(project), "main") + (clone / "bios").mkdir() + (clone / "bios" / "new.bin").write_bytes(b"x") + self._git(clone, "add", "bios/new.bin") + self._git(clone, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-q", + "-m", "add") + self._git(clone, "remote", "add", "origin", str(fork)) + self._git(clone, "push", "-q", "origin", "main") + self._git(clone, "fetch", "-q", "origin") + self._git(clone, "remote", "add", "upstream", str(project)) + cwd = os.getcwd() + os.chdir(clone) + try: + with mock.patch.object(validate_pr, "PROJECT_URLS", (str(project),)): + self.assertEqual(validate_pr.get_changed_files(), ["bios/new.bin"]) + finally: + os.chdir(cwd) + + if __name__ == "__main__": unittest.main() diff --git a/website/src/data/source-paths.json b/website/src/data/source-paths.json index 50e4da500..d591448f7 100644 --- a/website/src/data/source-paths.json +++ b/website/src/data/source-paths.json @@ -1,10 +1,16 @@ { "archive.softwareheritage.org/softwareheritage/revision@dc20a615275f66bee20a4fd851ef0231daca4f14": [], "bitbucket.org/linuxwolf6/splitwolf@f6c00044cdef00a31c66307d8b7cb1fb4ddf8c67": [], + "codeberg.org/iyzsong/anarch-libretro@187050344ef440c4eaf3135304a020093564fa3f": [ + "texts.h" + ], "codeberg.org/iyzsong/eternal-libretro@13adf143aea822ebc1ee23722a80e93a7979e4c9": [ "libretro.c" ], "codeberg.org/iyzsong/gam4980@32c531d3e7159081afd8dee72e8266153821095d": [], + "codeberg.org/iyzsong/uxn-libretro@2037b34ee0bf5f3914c38e0aafd145ece170b0f6": [ + "build.zig.zon" + ], "codeberg.org/schelling/dosbox-pure-unleashed@4a11412248ca4c862751a7d9e6818023795031e9": [ "Makefile", "main.cpp" @@ -22,6 +28,9 @@ "src/gui/midi_sc55.h", "src/gui/mt32emu.h" ], + "codeberg.org/virtualxt/virtualxt@a400adb0abf242c2fecbe926618338a00dc557c5": [ + "virtualxt_libretro.info" + ], "git.eden-emu.dev/eden-emu/eden@26a03d2473787abf3223b2d0e65b291eee00c1e1": [ "src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt", "src/common/fs/path_util.cpp", @@ -52,7 +61,8 @@ "Src/OSD/libretro/LibretroWrapper.cpp", "Src/OSD/libretro/libretro.cpp", "Src/OSD/libretro/libretroCrosshair.cpp", - "Src/Sound/MPEG/MpegAudio.cpp" + "Src/Sound/MPEG/MpegAudio.cpp", + "Src/Version.h" ], "git.libretro.com/libretro/cemu-libretro@14f56b569b3668db01a4972225993abd3e8f2096": [ ".gitlab-ci.yml", @@ -89,6 +99,7 @@ "libretro-radio.c" ], "git.libretro.com/libretro/rust-dos@152d9bca7820f0d9a5d1ca820a2d20855698a211": [ + "Cargo.toml", "crates/gus/build.rs", "crates/gus/src/builtin.rs", "crates/mt32/src/lib.rs", @@ -105,6 +116,7 @@ "src/verite/mod.rs" ], "git.libretro.com/libretro/wqxemu@caf0998989446e3d6659f67c1b8aabd708a0379d": [ + "Cargo.toml", "crates/wqxemu-core/src/machine.rs", "crates/wqxemu-core/src/machines/cc800.rs", "crates/wqxemu-core/src/machines/mod.rs", @@ -138,7 +150,8 @@ "src/xenia/ui/audio_helper.cc", "src/xenia/ui/d3d12/d3d12_provider.cc", "src/xenia/ui/imgui_drawer.cc", - "src/xenia/ui/vulkan/vulkan_instance.cc" + "src/xenia/ui/vulkan/vulkan_instance.cc", + "upstream.version" ], "git.ngram.ca/mirrors/Ryujinx@7d158acc3b5826a08941d6e8d50d3a3897021bcd": [ "src/Ryujinx.Common/Configuration/AppDataManager.cs", @@ -190,6 +203,7 @@ ], "github.com/7800-devtools/a7800@7a2afdc1ea08fc331b16b750d8c1f02d4ef62fc8": [ "hash/a7800.xml", + "makefile", "src/devices/bus/a7800/hiscore.cpp", "src/devices/bus/a7800/xm.cpp", "src/emu/crsshair.cpp", @@ -282,6 +296,7 @@ "font/fontv98.c", "io/pcidev.c", "keystat.c", + "np2ver.h", "pccore.c", "sdl/libretro/libretro.c", "sound/fmgen/fmgen_opna.cpp", @@ -322,6 +337,7 @@ "src/util.cpp" ], "github.com/AloysHF/BBKEmu@6641863f6ec59a74635753766e3f9d34adf92b05": [ + "Cargo.toml", "crates/bbkemu-core/src/emulator.rs", "crates/bbkemu-core/src/memory.rs", "crates/bbkemu-core/src/model.rs", @@ -366,6 +382,7 @@ "crates/dingooemu/src/main.rs" ], "github.com/AloysHF/Native32Emu@059fa33c658caa4dbbad140ebf2af1197f9dd778": [ + "Cargo.toml", "crates/native32emu-core/src/archive_loader.rs", "crates/native32emu-core/src/content_loader.rs", "crates/native32emu-core/src/emulator.rs", @@ -377,10 +394,12 @@ "crates/native32emu-libretro/src/libretro/constants.rs" ], "github.com/AloysHF/PlaydiaEmu@b1b990e7f28d6baa58d5fcde9faa155990ee8f3f": [ + "Cargo.toml", "crates/playdiaemu-core/src/content.rs", "crates/playdiaemu-libretro/playdiaemu_libretro.info" ], "github.com/AloysHF/SPMP8000Emu@65a93528ee33767bb9cf8faef516c8d1ad43fd13": [ + "Cargo.toml", "crates/spmp8000emu-core/assets/README.md", "crates/spmp8000emu-core/src/api/emu_fs.rs", "crates/spmp8000emu-core/src/api/mod.rs", @@ -391,6 +410,7 @@ "crates/spmp8000emu-libretro/src/libretro/constants.rs" ], "github.com/AloysHF/WQXEmu@caf0998989446e3d6659f67c1b8aabd708a0379d": [ + "Cargo.toml", "crates/wqxemu-core/src/machine.rs", "crates/wqxemu-core/src/machines/cc800.rs", "crates/wqxemu-core/src/machines/mod.rs", @@ -413,7 +433,9 @@ "plastic_tui/src/ui.rs", "plastic_ui/src/main.rs" ], - "github.com/AppleWin/AppleWin@1f3cadb88d1158c139d2e3a7b864d91836856948": [], + "github.com/AppleWin/AppleWin@1f3cadb88d1158c139d2e3a7b864d91836856948": [ + "resource/version.h" + ], "github.com/AppleWin/AppleWin@3e8054b4627624398e4589f7f27b3d40a6b9718e": [ "resource/resource.h", "source/CardManager.cpp", @@ -481,6 +503,7 @@ "libretro/libretro.c" ], "github.com/BlitterStudio/amiberry@49dbd1f027ef2afc12862b30c0c31ac8efa66221": [ + "CMakeLists.txt", "android/app/build.gradle", "libretro/libretro.cpp", "src/a2091.cpp", @@ -521,7 +544,9 @@ "source/bus-main-m68k.c", "source/bus-sub-m68k.c" ], - "github.com/Clownacy/clownmdemu-libretro@d43c2708b0a31c285ce16724b6c4a2e92af07346": [], + "github.com/Clownacy/clownmdemu-libretro@d43c2708b0a31c285ce16724b6c4a2e92af07346": [ + "source/libretro-interface.c" + ], "github.com/ColinPitrat/caprice32@8c778bf072a06fb7c23b9cccd179041db7f79a52": [ "cap32.cfg", "rom/MF2.rom", @@ -588,7 +613,8 @@ "engine/source/gamelib/packfile.c", "engine/source/gamelib/packfile.h", "engine/source/gamelib/translation.c", - "engine/source/utils.c" + "engine/source/utils.c", + "engine/version.sh" ], "github.com/DaedalusX64/daedalus@f17e9ed86f3806fadeb69abd29c9526ab2d4bd1b": [ "Source/CMakeLists.txt", @@ -657,6 +683,7 @@ "Src/Version.h" ], "github.com/DrGlaucous/doukutsu-rs-nm@51e64c2e9615bcd476034c69b58927877fe73494": [ + "Cargo.toml", "drsretroarch/src/core.rs", "src/data/vanilla.rs", "src/game/shared_game_state.rs" @@ -669,6 +696,7 @@ "src/emu/CMakeLists.txt", "src/emu/android/app/src/main/cpp/src/native-lib.cpp", "src/emu/android/app/src/main/java/com/github/eka2l1/emu/Emulator.java", + "src/emu/common/include/common/configure.h.in", "src/emu/common/src/path.cpp", "src/emu/common/src/types.cpp", "src/emu/config/include/config/options.inl", @@ -729,6 +757,7 @@ "src/emu/CMakeLists.txt", "src/emu/android/app/src/main/cpp/src/native-lib.cpp", "src/emu/android/app/src/main/java/com/github/eka2l1/emu/Emulator.java", + "src/emu/common/include/common/configure.h.in", "src/emu/common/src/path.cpp", "src/emu/common/src/types.cpp", "src/emu/config/include/config/options.inl", @@ -783,6 +812,7 @@ "src/scripts/pottery_startup_fix.lua" ], "github.com/EasyRPG/Player@096c004def987f3545198304b1b7a8352f284a50": [ + "CMakeLists.txt", "builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java", "builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java", "src/decoder_fluidsynth.cpp", @@ -811,11 +841,13 @@ ], "github.com/FCare/Kronos@d0384bc33aad06bfa9cf5cd308c4bd7ae6cfc814": [ "yabause/src/ctrl/src/stv.c", + "yabause/src/libretro/Makefile.common", "yabause/src/libretro/libretro.c", "yabause/src/sys/memory/src/cs0.c", "yabause/src/utils/src/db.c" ], "github.com/FWGS/xash3d-fwgs@27092af50d912432887faa43a1cccddfae569b2d": [ + "engine/common/com_strings.h", "filesystem/gameinfo.c", "filesystem/pak.c", "filesystem/searchpath.c" @@ -829,6 +861,7 @@ ], "github.com/FrodeSolheim/fs-uae@f362278ccd4c60991caac3b4d240d4a3f751bea2": [ "cfgfile.cpp", + "configure.ac", "od-fs/fsuae/fsuae-init.c", "od-fs/fsuae/fsuae-messages.cpp", "od-fs/python/app/app_init.py", @@ -871,9 +904,13 @@ "codemp/qcommon/qcommon.h" ], "github.com/Jakz/retro8@ecee6594c5b953966ee0515f3edac4032c7ed40f": [ + "src/libretro/libretro.cpp", "src/vm/gfx.cpp", "src/vm/lua_bridge.cpp" ], + "github.com/JesseTG/McSoftServe@fbdd09c7dfc11e35d900e51086f63e7f853e6c01": [ + "CMakeLists.txt" + ], "github.com/JesseTG/melonds-ds@f394adbacb5722ee97c1b37c8064da9a25818310": [ "src/libretro/config/config.cpp", "src/libretro/config/config.hpp", @@ -881,7 +918,11 @@ "src/libretro/config/constants.cpp", "src/libretro/config/constants.hpp" ], + "github.com/JesseTG/rom-cleaner@f75e2b58733de180f28a73286c6a82505be7bbb2": [ + "libretro.cpp" + ], "github.com/JetSetIlly/Gopher2600@295b9e1c08203453677ea4184e284c0664f5949a": [ + "Makefile", "cartridgeloader/loader.go", "debugger/debugger.go", "gopher2600.go", @@ -907,12 +948,14 @@ ], "github.com/KaosOverride/CapriceRPI@7d86ebdc6a5e645220c6f30cf407ac57c3256054": [ "src/cap32.cpp", + "src/cap32.h", "src/roms/builtin_roms.h" ], "github.com/LIJI32/SameBoy@9bf7e0b4dffc20941e4dfadffc6899ae19463844": [ "Core/gb.c", "Core/gb.h", - "libretro/libretro.c" + "libretro/libretro.c", + "version.mk" ], "github.com/LLeny/holani-retro@d0d79b4928e3d32695294142784c033ce7aeda9b": [ "src/lib.rs" @@ -922,6 +965,7 @@ "src/rom.rs" ], "github.com/LekKit/RVVM@343f6784c8922446083c5d5bd111d7c1eb8590eb": [ + "CMakeLists.txt", "src/bindings/libretro/libretro.c", "src/core/rvvm.c", "src/main.c" @@ -989,6 +1033,7 @@ "tools/installer/TRX_Installer/InstallMappings.cs" ], "github.com/NagyD/SDLPoP@3c5add5fb7f83d4ceb542823ab66d00146c4271b": [ + "src/config.h", "src/seg009.c" ], "github.com/Novum/vkQuake@a6cfba6904e0743dae4235f9dad9711bb5bd27f1": [ @@ -1217,15 +1262,18 @@ "rpcs3/Emu/Cell/lv2/sys_prx.cpp", "rpcs3/Emu/System.cpp", "rpcs3/Loader/PUP.cpp", + "rpcs3/rpcs3_version.cpp", "rpcs3/rpcs3qt/main_window.cpp" ], "github.com/RSDKModding/RSDKv3-Decompilation@2a7fd8b70f1b33553e0d6c67491d07e8290b80c1": [ + "CMakeLists.txt", "RSDKv3/Reader.cpp", "RSDKv3/RetroEngine.cpp", "RSDKv3/RetroEngine.hpp", "RSDKv3/Userdata.cpp" ], "github.com/RSDKModding/RSDKv4-Decompilation@a7f5195e21fdad7b75e4587e249013feeea9e6f3": [ + "CMakeLists.txt", "RSDKv4/ModAPI.cpp", "RSDKv4/Reader.cpp", "RSDKv4/Reader.hpp", @@ -1326,11 +1374,13 @@ "C64.emu/src/vice/vicii/vicii-resources.c", "C64.emu/src/vice/viciisc/vicii-resources.c", "C64.emu/src/vice/video/video-resources.c", + "EmuFramework/CMakeLists.txt", "EmuFramework/include/emuframework/AppMeta.hh", "EmuFramework/include/emuframework/AssetManager.hh", "EmuFramework/include/emuframework/DataPathSelectView.hh", "EmuFramework/include/emuframework/EmuSystem.hh", "EmuFramework/include/shared/mednafen/mednafen-config.h", + "EmuFramework/metadata/conf.mk", "EmuFramework/src/AppMeta.cc", "EmuFramework/src/AssetManager.cc", "EmuFramework/src/EmuApp.cc", @@ -1603,7 +1653,8 @@ "Source/RMG-Input/main.cpp", "Source/RMG/UserInterface/Dialog/SettingsDialog.cpp", "Source/RMG/UserInterface/MainWindow.cpp", - "Source/RMG/main.cpp" + "Source/RMG/main.cpp", + "VERSION" ], "github.com/STJr/SRB2@639b58c6d718452ef343a0bc927d043bed9e40d6": [ "src/CMakeLists.txt", @@ -1611,10 +1662,12 @@ "src/d_main.c", "src/doomdef.h", "src/sdl/i_system.c", + "src/version.h", "src/w_wad.c" ], "github.com/SourMesen/Mesen-S@89199f298a95345a893d814525b6668567a6d490": [ "Core/BaseCartridge.cpp", + "Core/EmuSettings.cpp", "Core/FirmwareHelper.h", "Core/Gameboy.cpp", "Core/GbBootRom.h", @@ -1649,6 +1702,7 @@ ], "github.com/Spuds0588/mgba-splitscreen@f1d5975d77285f77affbfe64b7050ebd5c4b6a77": [ "CMakeLists.txt", + "mgba-splitscreen/package.json", "src/gba/gba.c", "src/platform/libretro-splitscreen/instances.c", "src/platform/libretro-splitscreen/mgba_splitscreen_libretro.info" @@ -1661,7 +1715,8 @@ "src/emulator.c" ], "github.com/SquirrelJME/SquirrelJME@24d0e97d4297d0bce26a9651f5bce5d326ea690a": [ - "nanocoat/src/boot.c" + "nanocoat/src/boot.c", + "squirreljme-version" ], "github.com/StillJC/ArcadeDuck@80cdb6da6b51d687192a6346efe4499b10fe4faf": [ "data/resources/arcadedb.yaml", @@ -1735,7 +1790,8 @@ ], "github.com/TASEmulators/desmume@e06d11f6df8c2e35d570412c45f5a63e5e7c031d": [ "desmume/src/NDSSystem.cpp", - "desmume/src/firmware.cpp" + "desmume/src/firmware.cpp", + "desmume/src/version.cpp" ], "github.com/TASEmulators/desmume@fc504ee0bc9ac3434cafcaae7b129c025499a609": [ "desmume/src/NDSSystem.cpp", @@ -1755,6 +1811,7 @@ ], "github.com/TerryCavanagh/VVVVVV@40e1ec3bbf2a990a5c600564fe312c2121178a50": [ "desktop_version/src/FileSystemUtils.cpp", + "desktop_version/src/ReleaseVersion.h", "desktop_version/src/main.cpp" ], "github.com/TheForceEngine/TheForceEngine@cc25205cc94c36a396f6f50295772f6ec83069a2": [ @@ -1777,7 +1834,8 @@ "src/main/level_file.cpp", "src/main/menu_main.cpp", "src/main/translate.cpp", - "src/sound.cpp" + "src/sound.cpp", + "version.cmake" ], "github.com/ThisBoringWorld/gam4980@eeaa531b55e7127ab4b5e0bdc5ceba686df59c6a": [ "src/libretro.c" @@ -1851,6 +1909,7 @@ "SDL/keyboard.c", "SDL/prop.c", "SDL/tool/create_cgrom.c", + "version.txt", "win32api/dosio.c", "x68k/ioc.c", "x68k/midi_fluid.c", @@ -1877,6 +1936,7 @@ "wadsrc_lights/CMakeLists.txt", "wadsrc_widepix/CMakeLists.txt" ], + "github.com/Uzebox/uzebox@eb02e5f69212983e778a2ca87b198f22071303b8": [], "github.com/VICE-Team/svn-mirror@46b1bc4393251f838ce3dadd241c1f567d9371d1": [], "github.com/VICE-Team/svn-mirror@81546e60c222fe4aef750b890b0dbe3a5ac280d8": [ "vice/configure.ac", @@ -1942,6 +2002,7 @@ "vice/src/video/video-color.c" ], "github.com/Vita3K/Vita3K@6ec8e6ce31f3d6099b14eb9dd0320c09ef6386b6": [ + "vita3k/CMakeLists.txt", "vita3k/app/src/app.cpp", "vita3k/module/src/load_module.cpp", "vita3k/packages/src/pup.cpp", @@ -1961,6 +2022,7 @@ "README.md", "cmake/Symbian.cmake", "src/emu/CMakeLists.txt", + "src/emu/common/include/common/configure.h.in", "src/emu/common/src/path.cpp", "src/emu/common/src/types.cpp", "src/emu/config/include/config/options.inl", @@ -2137,6 +2199,9 @@ "vm/vm.c", "vm/vm_console.c" ], + "github.com/aduros/wasm4@9d6c962785cfe3719d0245fd279ecbe98a4dbb63": [ + "cli/package.json" + ], "github.com/adventuregamestudio/ags@6802bad8dc3189462003d4a169fc591f64cfb9b6": [ "Common/ac/gamesetupstructbase.h", "Common/ac/spritefile.cpp", @@ -2225,6 +2290,7 @@ "src/game/gconfig.cc" ], "github.com/alexbatalov/fallout2-ce@e97087b9582f37075db347a89898887320753f8b": [ + "CMakeLists.txt", "src/game.cc", "src/game_config.cc", "src/interface.cc", @@ -2310,6 +2376,9 @@ "src/ui_text.c", "src/version.c" ], + "github.com/amberwhitehead/pzretro@6d859b47092f585a7ec05804c1d51a1676a06531": [ + "src/core.cpp" + ], "github.com/andkrau/NuanceResurrection@daf3ca642756a559ecce91e10da0e73cbad180ff": [ "CMakeLists.txt", "src/Bios.h", @@ -2488,6 +2557,7 @@ ], "github.com/atari800/atari800@98cc2dc749867c9c7317a0271ba062a6c797f886": [], "github.com/audetto/AppleWin@0a89db3d593a74d04590b902fb7206723591f445": [ + "resource/version.h", "source/frontends/common2/gnuframe.cpp" ], "github.com/aureyoboss/ArcadeFlashWeb@5de66e0cd69b48721a094887a28d042f71ddad08": [ @@ -2508,6 +2578,7 @@ "src/tools/mkpfi.c" ], "github.com/autch/piemu@23208626e16e53b73fd21a2fbd0c429338625dc4": [ + "app.h", "piemu.c", "tools/mkpfi.c" ], @@ -2648,6 +2719,7 @@ "bsnes/target-libretro/libretro.cpp" ], "github.com/bvschaik/julius@34d1ecd54befb845c0139b371fa8a0438210dac1": [ + "CMakeLists.txt", "src/building/model.c", "src/core/image.c", "src/core/lang.c", @@ -2659,10 +2731,12 @@ ], "github.com/cebix/macemu@96e512bd6376e78a2869f16dcc8a9028bce5ee72": [ "BasiliskII/src/Unix/main_unix.cpp", + "BasiliskII/src/include/version.h", "BasiliskII/src/main.cpp", "BasiliskII/src/rom_patches.cpp", "SheepShaver/src/Unix/main_unix.cpp", "SheepShaver/src/Windows/main_windows.cpp", + "SheepShaver/src/include/version.h", "SheepShaver/src/main.cpp", "SheepShaver/src/rom_patches.cpp" ], @@ -2762,6 +2836,7 @@ "src/video/screen.c" ], "github.com/chocolate-doom/chocolate-doom@72d9e9318c4ccc61dab29bb9cc30bc8a9a93266d": [ + "configure.ac", "src/d_iwad.c", "src/d_iwad.h", "src/doom/d_main.c", @@ -2775,9 +2850,11 @@ ], "github.com/christianhaitian/bennugd-monolithic@60ee3389efcf9b402d66035e87f33d17d70cbd83": [ "README.md", - "core/bgdi/src/main.c" + "core/bgdi/src/main.c", + "projects/cmake/include/common_defs.cmake" ], "github.com/christianhaitian/ti99sim@e3608541943b5258afe45b74e3f78e723df1fcbd": [ + "doc/CHANGES.html", "src/core/device-support.cpp", "src/core/ti994a-gk.cpp", "src/core/ti994a.cpp", @@ -2845,6 +2922,7 @@ "fs.cpp", "game.cpp", "main.cpp", + "main_libretro.cpp", "resource.cpp" ], "github.com/deathkiller/jazz2-native@ff9ddb7be9c84bdcf71b22d9380da06135edeb62": [ @@ -2865,6 +2943,7 @@ "cmake/ncine_options.cmake" ], "github.com/derik-dot-digital/D.Smile@d4bbc3be27537def21513991647f14ea9a9c2d15": [ + "app/build.gradle.kts", "app/src/main/cpp/core/vsmile.cpp", "app/src/main/cpp/jni_bridge.cpp", "app/src/main/java/com/dsmile/emulator/ui/EmuActivity.kt", @@ -2883,18 +2962,25 @@ "Source/diablo.cpp", "Source/engine/assets.cpp", "Source/init.cpp", - "Source/options.cpp" + "Source/options.cpp", + "VERSION" + ], + "github.com/diekleinekuh/BennuGD_libretro@5fba764ac709545d4b0d3b28deff2ec0c7d1a91b": [ + "core/common/bgd_version.c" ], "github.com/digarok/gsplus-legacy-archive@480572054518112647c8fae5d7ea7046a6d6ecfb": [ + "CMakeLists.txt", "src/config.c", "src/imagewriter.cpp", "src/printer.cpp", "src/sim65816.c" ], "github.com/digarok/gsplus@41e71031debbde98e66ea5c5c67498e9b4726a6d": [ + "gsplus/src/CMakeLists.txt", "gsplus/src/config.c" ], "github.com/dividebysandwich/rust-dos@152d9bca7820f0d9a5d1ca820a2d20855698a211": [ + "Cargo.toml", "crates/gus/build.rs", "crates/gus/src/builtin.rs", "crates/mt32/src/lib.rs", @@ -3020,6 +3106,7 @@ "Source/Core/DolphinQt/MenuBar.cpp" ], "github.com/dosbox-staging/dosbox-staging@fa1856c057902be3e08856675d6cc1e7ffe43db6": [ + "CMakeLists.txt", "src/audio/disk_noise.cpp", "src/dos/CMakeLists.txt", "src/dos/dos_code_page.cpp", @@ -3043,6 +3130,7 @@ "src/shell/autoexec.cpp" ], "github.com/doublegate/RustyNES@3d02b99dc84fb389c03fc6b1f986ffa658dda0dd": [ + "Cargo.toml", "crates/rustynes-core/src/bus.rs", "crates/rustynes-core/src/nes.rs", "crates/rustynes-gamedb/src/lib.rs", @@ -3050,6 +3138,7 @@ "crates/rustynes-mappers/src/fds.rs" ], "github.com/doukutsu-rs/doukutsu-rs@d05995b2ce88e583491fcecfbd5883735d3527ea": [ + "Cargo.toml", "src/data/vanilla.rs", "src/game/shared_game_state.rs" ], @@ -3186,6 +3275,7 @@ "github.com/emuplace/sudachi.emuplace.app@c7431bd420d66c54df277ba0a0b71de42cf0a5bf": [], "github.com/etlegacy/etlegacy@add16a92f4b10c2955136e3ab7868bfc9bff8fad": [ "README.md", + "VERSION.txt", "cmake/ETLBuildMod.cmake", "cmake/ETLVersion.cmake", "src/qcommon/files.c", @@ -3193,6 +3283,7 @@ "src/sys/sys_main.c" ], "github.com/fabiangreffrath/taradino@2b4ea3c6c18caf60959efee1f2534c2122607716": [ + "CMakeLists.txt", "rott/rt_datadir.c", "rott/rt_main.c", "rott/rt_main.h", @@ -3201,7 +3292,8 @@ "github.com/finalburnneo/FBNeo@697e385c8285bfc528343b96d948ee3a4f9e24c5": [ "src/burn/burn.cpp", "src/burn/drv/neogeo/d_neogeo.cpp", - "src/burn/hiscore.cpp" + "src/burn/hiscore.cpp", + "src/burn/version.h" ], "github.com/finalburnneo/FBNeo@c110542721d8674b6ff48e691ac032c040fecbba": [ "src/burn/burn.cpp", @@ -3230,6 +3322,7 @@ "src/burn/drv/taito/d_taitof2.cpp", "src/burn/hiscore.cpp", "src/burn/snd/samples.cpp", + "src/burn/version.h", "src/burner/conc.cpp" ], "github.com/flyinghead/flycast@e88b0ced2243de137bcccac0072f0c1dd00d41bd": [ @@ -3280,12 +3373,14 @@ "app/src/main/java/paulscode/android/mupen64plusae/persistent/AppData.java", "app/src/main/java/paulscode/android/mupen64plusae/persistent/GlobalPrefs.java", "app/src/main/java/paulscode/android/mupen64plusae/util/RomHeader.java", + "build_common/version_common.gradle", "mupen64plus-core/upstream/src/main/main.c", "settings.gradle" ], "github.com/garglk/garglk@9e06211c20bee35026744424a57f50375cdc29db": [ "CMakeLists.txt", "INSTALL.md", + "VERSION", "garglk/CMakeLists.txt", "garglk/config.cpp", "garglk/draw.cpp", @@ -3357,6 +3452,7 @@ "idTech4Amm/src/main/java/com/n0n3m4/DIII4A/launcher/SetupControlsThemeFunc.java" ], "github.com/gopher64/gopher64@87385e77432cd278ac8588c7ed3d5014fc0d6bab": [ + "Cargo.toml", "build.rs", "parallel-rdp/interface.cpp", "src/cheats.rs", @@ -3498,7 +3594,8 @@ "src/xenia/ui/audio_helper.cc", "src/xenia/ui/d3d12/d3d12_provider.cc", "src/xenia/ui/imgui_drawer.cc", - "src/xenia/ui/vulkan/vulkan_instance.cc" + "src/xenia/ui/vulkan/vulkan_instance.cc", + "upstream.version" ], "github.com/hatari/hatari@23bc3060ebf0da348cc842e358c102ba88002b56": [], "github.com/hatari/hatari@c1a5c88bc06e56778b6b7f8483becfd58a7da2b4": [ @@ -3562,6 +3659,7 @@ "src/org/recompile/mobile/PlatformFont.java" ], "github.com/hex007/freej2me@eb4ac87e9a0b212ba701691eb67ee4892b2b1173": [ + "META-INF/freej2me-build.version", "src/libretro/freej2me_libretro.c" ], "github.com/hrydgard/ppsspp@43a69b1c84dfe29e82b46bd3797a1106cbd65c5b": [ @@ -3594,6 +3692,9 @@ "dirksimple_libretro.c", "dirksimple_sdl.c" ], + "github.com/icculus/mojozork@ff7e00742a00acec8e175ddefb97520fb270df2d": [ + "mojozork-libretro.c" + ], "github.com/ihhub/fheroes2@35ca4c915be00098547d5050412a325dd55cfd7f": [ "src/fheroes2/agg/agg.cpp", "src/fheroes2/audio/audio_manager.cpp", @@ -3685,6 +3786,7 @@ "ui/libretro.c" ], "github.com/ioquake/ioq3@87a99d18206e878a20289f05ac3c16005dcae5c3": [ + "cmake/identity.cmake", "code/qcommon/files.c", "code/qcommon/q_shared.h", "code/qcommon/qcommon.h" @@ -3692,6 +3794,7 @@ "github.com/iortcw/iortcw@438e7d413b5f7277187c35b032eb0ef9093ae778": [ "MP/code/qcommon/files.c", "MP/code/qcommon/q_shared.h", + "SP/Makefile", "SP/code/qcommon/files.c", "SP/code/qcommon/qcommon.h" ], @@ -3705,6 +3808,7 @@ "pico/pico_int.h", "pico/sms.c", "platform/common/emu.c", + "platform/common/version.h", "platform/libretro/libretro.c" ], "github.com/ivanizag/izapple2@4e773baf9f1f3fb692e3f3444eb5f42fddb205ef": [ @@ -3720,7 +3824,9 @@ "github.com/iyzsong/onscripter-libretro@ec4ff1842d498faaa844d570becf561d088deea0": [ "libretro.cpp" ], - "github.com/iyzsong/syobonaction-libretro@792c40cb590bcc7286cf720b5fb85807082b534e": [], + "github.com/iyzsong/syobonaction-libretro@792c40cb590bcc7286cf720b5fb85807082b534e": [ + "libretro.cpp" + ], "github.com/izzy2lost/Super3@c410f82f771b33094b8c1cdbf96ef7c74ec9cff1": [ "Manual.txt", "README.md", @@ -3775,6 +3881,7 @@ "github.com/joamag/boytacean@4d806d0770f117cc8e6ac0fe75b95aa9dc39fc4f": [ "frontends/libretro/src/core.rs", "frontends/sdl/src/main.rs", + "setup.py", "src/data.rs", "src/gb.rs" ], @@ -3834,6 +3941,7 @@ "src/spi.cpp" ], "github.com/jsgroth/jgenesis@0a00c47ba7a16bd6e77ef7e0b6c8fab2fc603cba": [ + "Cargo.toml", "backend/gb-core/src/memory.rs", "backend/gba-core/src/memory.rs", "backend/genesis-components/src/cartridge.rs", @@ -3861,6 +3969,7 @@ "frontend/jgenesis-native-driver/src/mainloop/smsgg.rs" ], "github.com/jtothebell/fake-08@814991a2571ad3970e386cef48f3b148aa1c27b9": [ + "Makefile", "platform/libretro/libretro.cpp", "source/p8GlobalLuaFunctions.h", "source/vm.h" @@ -3869,7 +3978,8 @@ "sc2/build/win32_install/packages.nsh", "sc2/build/win32_install/uqm-installer.nsi", "sc2/src/options.c", - "sc2/src/uqm/setup.c" + "sc2/src/uqm/setup.c", + "sc2/src/uqmversion.h" ], "github.com/kondrak/vkQuake2@6763f207229f97cffabb6fc2da72017a794b139b": [ "client/snd_miniaudio.c", @@ -3881,6 +3991,7 @@ "github.com/kurtjd/jaxe@c767afd785e01a15bcd575a2d93b737add82b675": [ "include/chip8.h", "src/chip8.c", + "src/libretro.c", "src/main.c" ], "github.com/lemon32767/ccleste@261d96f15af430b8111abc7a5250229246654f52": [ @@ -3888,6 +3999,7 @@ "sdl12main.c" ], "github.com/lethal-guitar/RigelEngine@f05996f9b3ad3b3ea5bb818e49e7977636746343": [ + "CMakeLists.txt", "README.md", "cmake/rigel_pack.cmake", "src/assets/cmp_file_package.cpp", @@ -3916,6 +4028,7 @@ "src/burn/burn.cpp", "src/burn/drv/neogeo/d_neogeo.cpp", "src/burn/hiscore.cpp", + "src/burn/version.h", "src/burner/libretro/libretro.cpp" ], "github.com/libretro/FBNeo@da14132bab6683980b1ad7e4d0d6fd47bf06dce7": [ @@ -3945,9 +4058,13 @@ "src/burn/drv/taito/d_taitof2.cpp", "src/burn/hiscore.cpp", "src/burn/snd/samples.cpp", + "src/burn/version.h", "src/burner/conc.cpp", "src/burner/libretro/libretro.cpp" ], + "github.com/libretro/FFmpeg@4920879d2f09a78cdf855403c349457cee1c31da": [ + "RELEASE" + ], "github.com/libretro/FreeChaF@76c7a84f1f7e80f3e6f2bba96fe100cb24e99124": [ "src/channelf_hle.c", "src/libretro.c", @@ -3978,12 +4095,14 @@ ], "github.com/libretro/Mesen-S@9e4fdeb9b336470bc96beb8765b2e79c86a2da1e": [ "Core/BaseCartridge.cpp", + "Core/EmuSettings.cpp", "Core/FirmwareHelper.h", "Core/Gameboy.cpp", "Core/GbBootRom.h", "Core/NecDsp.cpp" ], "github.com/libretro/Mesen@0102910c39ad1a62bc3f784466f3f67ca9eae335": [ + "Core/EmulationSettings.cpp", "Core/FdsLoader.cpp", "Core/StudyBoxLoader.cpp", "Libretro/libretro.cpp" @@ -4030,6 +4149,7 @@ "font/fontv98.c", "io/pcidev.c", "keystat.c", + "np2ver.h", "pccore.c", "sdl/libretro/libretro.c", "sound/fmgen/fmgen_opna.cpp", @@ -4062,9 +4182,13 @@ "source/PokeMini.c", "source/PokeMini.h" ], + "github.com/libretro/QuickNES_Core@26bb785c9deddb66a17717b21bb4e328f03ade32": [ + "libretro/libretro.cpp" + ], "github.com/libretro/RACE@c7810dd7f172827bfa2004813bc000b13786636b": [ "flash.c", "koyote_bin.h", + "libretro/libretro.c", "main.c", "ngpBios.c", "race-memory.c", @@ -4081,10 +4205,14 @@ "src/resource_aba.cpp", "src/staticres.cpp" ], + "github.com/libretro/RetroArch@5078a39b624e31b359e1527ea7fbfddfc7de96cb": [ + "version.all" + ], "github.com/libretro/SameBoy@aa158a889a48b538a0302873704a34577c8eb67d": [ "Core/gb.c", "Core/gb.h", - "libretro/libretro.c" + "libretro/libretro.c", + "version.mk" ], "github.com/libretro/TIC-80@14fd99edc29962778bc88d81773288ffc6e8aa53": [ ".gitmodules" @@ -4093,14 +4221,21 @@ "libretro.c", "memory.c" ], + "github.com/libretro/ThePowderToy@bb2d9f6623d2ccf25a0021045af9591c8a0bbaff": [ + "src/Config.h" + ], "github.com/libretro/a5200@40c6f2f1ad4a3145b328d5baaf010fae6c7e752b": [ "emu/memory.c", "libretro/libretro.c" ], "github.com/libretro/arduous@831b83e67ad46e8c86b8165649ff81bc6fc6c157": [ + "CMakeLists.txt", "include/arduous/arduous.h", "src/arduous/arduous.cpp" ], + "github.com/libretro/beetle-bsnes-libretro@5f05e4c785e936c928ac468e129c55b6f08592cb": [ + "libretro.cpp" + ], "github.com/libretro/beetle-finalarcade2019@0581797db6fdffd826086b053ced4b6b29bb6678": [ "src/burn/burn.cpp", "src/burn/drv/coleco/d_coleco.cpp", @@ -4130,6 +4265,9 @@ "mednafen/lynx/cart.cpp", "mednafen/lynx/rom.cpp" ], + "github.com/libretro/beetle-ngp-libretro@a50d5ac288a81f2104ddf43195a4efdd15c72227": [ + "libretro.c" + ], "github.com/libretro/beetle-pce-fast-libretro@4eee99c451f85ebe66d1591de88b892015b23059": [ "libretro.c" ], @@ -4143,7 +4281,8 @@ "mednafen/settings.c" ], "github.com/libretro/beetle-psx-libretro@7fea57a541abb39b8c5035b6261c3e70962d221b": [ - "libretro.c" + "libretro.c", + "libretro_options.h" ], "github.com/libretro/beetle-saturn-libretro@30f4c12072d265c9e553dcb29386059114945dea": [ "disc.c", @@ -4167,6 +4306,9 @@ "github.com/libretro/beetle-vb-libretro@d5ae54c8e23b573a3b34e888b2d48a588027e92c": [ "libretro.cpp" ], + "github.com/libretro/beetle-wswan-libretro@4b01295838ea89e3f1355bbe4cb5cf98aa6108cd": [ + "libretro.c" + ], "github.com/libretro/bk-emulator@92042a753289cedf05e7e6bd8e75849f83e48966": [ "boot.c", "libretro.c", @@ -4185,10 +4327,12 @@ "paths.c", "romdb.c", "segacd.c", - "systems.cfg" + "systems.cfg", + "version.inc" ], "github.com/libretro/blueMSX-libretro@e3086eb5d36d77fa11704cf53dc176686e70127d": [ "Src/Board/Machine.c", + "Src/BuildInfo/version.h", "Src/Memory/MegaromCartridge.c", "libretro.c", "libretro_core_options.h", @@ -4212,9 +4356,14 @@ "github.com/libretro/bsnes-jg@4aef191fef72be539ccc07ada21c6e0a65360eb7": [ "libretro/libretro.cpp", "src/cartridge.cpp", - "src/coprocessor/icd.cpp" + "src/coprocessor/icd.cpp", + "version.h" + ], + "github.com/libretro/bsnes-libretro-cplusplus98@baec09aa4796b7575aba1c1ae7335f493c2216af": [ + "snes/libretro/libretro.cpp" ], "github.com/libretro/bsnes-libretro@05439f96121d2b9d7ad7a5fc1f29d7eebdcc8c43": [ + "bsnes/emulator/emulator.hpp", "bsnes/heuristics/super-famicom.cpp", "bsnes/sfc/cartridge/load.cpp", "bsnes/sfc/coprocessor/armdsp/armdsp.hpp", @@ -4224,16 +4373,19 @@ ], "github.com/libretro/bsnes-mercury@79d7f9de218b6ffa65a80bbdc5828532bc239232": [ "ananke/heuristics/super-famicom.hpp", + "emulator/emulator.hpp", "sfc/cartridge/markup.cpp", "target-libretro/libretro.cpp" ], "github.com/libretro/bsnes2014@3c1394e042ee444c8248e1b9210e14ea55e836e9": [ "ananke/heuristics/super-famicom.hpp", + "emulator/emulator.hpp", "target-libretro/libretro.cpp" ], "github.com/libretro/cannonball@0d83575d920ea4c0a280847a19d7802f673e6a68": [ "src/main/engine/omusic.c", "src/main/frontend/config.c", + "src/main/frontend/menu.c", "src/main/libretro/main.c", "src/main/romloader.c" ], @@ -4269,6 +4421,7 @@ ], "github.com/libretro/crawl-ref@cc2530f377a3a5d686d4b6a360d88b01de0fbc59": [ "crawl-ref/libretro/libretro.cc", + "crawl-ref/source/build.h", "crawl-ref/source/database.cc", "crawl-ref/source/files.cc", "crawl-ref/source/fontwrapper-ft.cc", @@ -4276,17 +4429,20 @@ "crawl-ref/source/tiletex.cc" ], "github.com/libretro/daphne@6f1695dd1f376060666eec0a416ff56bb6c9cccc": [ + "daphne/daphne-1.0-src/daphne.cpp", "daphne/daphne-1.0-src/game/game.cpp", "daphne/libretro/libretro.cpp" ], "github.com/libretro/desmume2015@422b688009ccb9f2917c086a8fe79c8cecb030ae": [ "desmume/src/NDSSystem.cpp", - "desmume/src/NDSSystem.h" + "desmume/src/NDSSystem.h", + "desmume/src/version.c" ], "github.com/libretro/desmume@95b4d798731caa809125b6c3c11d17cc332ff6ef": [ "desmume/src/NDSSystem.cpp", "desmume/src/firmware.cpp", - "desmume/src/frontend/libretro/libretro.cpp" + "desmume/src/frontend/libretro/libretro.cpp", + "desmume/src/version.cpp" ], "github.com/libretro/dolphin@3aac3f052f733f8df2f1616843a4d2e14002ed11": [ "Source/Android/app/build.gradle.kts", @@ -4320,6 +4476,7 @@ "libretro/deps/munt/mt32emu/src/ROMInfo.cpp", "libretro/src/libretro.cpp", "libretro/src/midi_bassmidi.cpp", + "libretro/svn", "src/gui/midi_mt32.cpp" ], "github.com/libretro/dosbox-libretro@a563bb5e9753f207d74e9d8c417d793b784fa4be": [ @@ -4329,6 +4486,7 @@ "libretro/src/libretro.cpp", "libretro/src/libretro_core_options.cpp", "libretro/src/midi_bassmidi.cpp", + "libretro/svn", "src/gui/midi.cpp", "src/gui/midi_mt32.cpp", "src/gui/midi_sc55.h" @@ -4340,12 +4498,14 @@ "src/gui/mt32emu.h" ], "github.com/libretro/dosbox-svn@46a3164fc0f1e808ae56143d81698b00fb6b4781": [ + "include/config.h", "libretro/Makefile.common", "libretro/Makefile.libretro", "libretro/libretro.cpp", "src/misc/cross.cpp" ], "github.com/libretro/easyrpg-libretro@a3fbb7c24fd5a39fd95388d09135256a6b19f1e4": [ + "CMakeLists.txt", "builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java", "builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java", "src/decoder_fluidsynth.cpp", @@ -4354,11 +4514,13 @@ ], "github.com/libretro/ecwolf@0cccd9a85d7ffa33fb695691a8a5db246606518c": [ "src/libretro/libretro.cpp", + "src/libretro/versiondefs.h", "src/libretro/wl_main.cpp", "src/version.h", "src/wl_iwad.cpp" ], "github.com/libretro/emux@3e558cb15157fcb1944846a845547d369605c963": [ + "configure.ac", "controllers/mapper/gb_mapper.c", "controllers/mapper/gb_mapper.h", "controllers/mapper/sms_mapper.c", @@ -4376,16 +4538,20 @@ "github.com/libretro/fbalpha2012@0ce31536bef3162fe7e69ff5f555334ec4913cef": [ "svn-current/trunk/src/burn/burn.cpp", "svn-current/trunk/src/burn/hiscore.cpp", - "svn-current/trunk/src/burn/snd/samples.cpp" + "svn-current/trunk/src/burn/snd/samples.cpp", + "svn-current/trunk/src/burn/version.h" ], "github.com/libretro/fbalpha2012_cps1@5542c1848ef81e92db311193b01dc349bc29d7cc": [ - "src/burn/hiscore.cpp" + "src/burn/hiscore.cpp", + "src/burner/libretro/libretro.cpp" ], "github.com/libretro/fbalpha2012_cps2@e3f3ae46f120228b74d41e2b2862f1fbf1d7eb5b": [ - "src/burn/hiscore.c" + "src/burn/hiscore.c", + "src/burner/libretro/libretro.cpp" ], "github.com/libretro/fbalpha2012_cps3@57f8015c2e0cdebcf42d3add2c314bb7448f9421": [ - "svn-current/trunk/src/burn/hiscore.cpp" + "svn-current/trunk/src/burn/hiscore.cpp", + "svn-current/trunk/src/burn/version.h" ], "github.com/libretro/fbalpha2012_neogeo@e092097037d494c50d17c5144a2758d68538061b": [ "src/burn/drv/neogeo/d_neogeo.cpp", @@ -4393,10 +4559,12 @@ ], "github.com/libretro/fixGB@c7865087b8c8126b8bc60fddb75b20efad992458": [ "libretro/libretro.c", + "main.c", "mem.c" ], "github.com/libretro/fixNES@72af6c6fd9d3b6c23d334ba7385b6a09225f2276": [ "libretro/libretro.c", + "main.c", "mapper.c" ], "github.com/libretro/fmsx-libretro@4de11755ce4f196ac1c8a7bb20bb4eccbc87a7d4": [ @@ -4424,9 +4592,11 @@ "github.com/libretro/frodo-libretro@891d20102a8894dd4fce73fb8bd2f623483fd05f": [ "Src/C64.cpp", "Src/C64.h", + "Src/Version.h", "Src/main.cpp" ], "github.com/libretro/fuse-libretro@e997e2bc32c888348f862f69f2c53babfedf7791": [ + "fuse/configure.ac", "fuse/machine.c", "fuse/machines/pentagon.c", "fuse/machines/pentagon512.c", @@ -4467,15 +4637,20 @@ "src/geo.c", "src/geo_cd.c" ], + "github.com/libretro/gong@69ef0b69dc542af4c1cf2efcc451dd6c378281b6": [ + "gong.c" + ], "github.com/libretro/gpsp@5819380c2ffb0900219d700a382ee68c464ebb99": [ "bios/source/core.s", "gba_memory.c", + "gpsp_config.h", "libretro/libretro.c", "main.h" ], "github.com/libretro/gw-libretro@91d599b951e7bfe7e040347f58667cba20074adc": [ "gwrom/gwrom.h", - "src/libretro.c" + "src/libretro.c", + "src/libretro_version.c" ], "github.com/libretro/hatari@25f488b1667ed064a25c30eb3f06b40bc877c495": [ ".gitlab-ci-libretro.yml", @@ -4535,6 +4710,9 @@ "src/tos.c", "src/vdi.c" ], + "github.com/libretro/hbmame-libretro@840eeef5ee0fd75dab62c90bf25789b898f3dba3": [ + "makefile" + ], "github.com/libretro/ishiiruka@f228bde5fbf4861ba749a229803addcd6238663f": [ "Source/Core/Common/CommonPaths.h", "Source/Core/Common/SysConf.cpp", @@ -4557,11 +4735,22 @@ "src/jcv_coleco.c", "src/jcv_crvision.c", "src/jcv_db.c", - "src/jollycv.c" + "src/jollycv.c", + "version.h" + ], + "github.com/libretro/jumpnbump-libretro@2cc840155ce4bf42b2e9e0ef3854b32d534e607e": [ + "libretro.c" + ], + "github.com/libretro/libretro-2048@39333f7b13dc4daea7c151d9c38d22b961246343": [ + "libretro.c" + ], + "github.com/libretro/libretro-3dengine@efa2bf6f32f930721fe9ae2361d7ec858077c483": [ + "libretro.cpp" ], "github.com/libretro/libretro-atari800@4e7fbc73765c1a9670c7506616046ad1d4ccda51": [ "atari800/src/atari.c", - "atari800/src/sysrom.c" + "atari800/src/sysrom.c", + "libretro/config.h" ], "github.com/libretro/libretro-cap32@8e61dd69d948cea16c3bad734f7b9d0c2a018cc8": [ "cap32/cap32.c", @@ -4572,10 +4761,15 @@ "cap32/rom/amsdos.h", "cap32/rom/cpm.h", "cap32/slots.c", - "libretro/libretro-core.c" + "libretro/libretro-core.c", + "libretro/libretro-core.h" + ], + "github.com/libretro/libretro-chailove@524ef951abbd07464830ee12a3f6f5e2aba9efd3": [ + "src/ChaiLove.h" ], "github.com/libretro/libretro-crocods@a9c63b29443715ae2add392010fca4eae7f93e67": [ "crocods-core/cpc6128.bin.c", + "crocods-core/crocods.h", "crocods-core/multiface.c", "crocods-core/platform.c", "crocods-core/romdisc.bin.c", @@ -4594,11 +4788,16 @@ ], "github.com/libretro/libretro-fsuae@089bb69207fe8fa5eec0006d1d759fb6426f4027": [ "Makefile.libretro", + "configure.ac", "libretro/libretro.c", "sources/src/fs-uae/config.c", "sources/src/fs-uae/paths.c" ], + "github.com/libretro/libretro-gme@1562f6207a066e9807243c89648d1cb44e411971": [ + "deps/game-music-emu/gme/gme.h" + ], "github.com/libretro/libretro-handy@bc55d462f0b2d6b073ea93dc552ebd73cec60fd1": [ + "libretro/handy.h", "libretro/libretro.cpp", "lynx/rom.cpp", "lynx/rom.h", @@ -4606,7 +4805,8 @@ ], "github.com/libretro/libretro-lutro@6224157a615b18507bc0b117a3398c7a324cd3e5": [ "filesystem.c", - "libretro.c" + "libretro.c", + "lutro.h" ], "github.com/libretro/libretro-meowPC98@5fdbb2187f6b14ca7bf668dd52b36c820b989995": [ "bios/bios.c", @@ -4620,19 +4820,24 @@ "libretro/libretro.c", "np2tool/GETBIOS.ASM", "sound/rhythmc.c", - "sound/soundrom.c" + "sound/soundrom.c", + "x11/configure.ac" ], "github.com/libretro/libretro-minivmac@babdf7b53d361a7225858b68a8a64efe8b06f5e5": [ "libretro/libretro-core.c", "minivmac/cfg/CNFGRAPI.h", "minivmac/src/OSGLUERETRO.c" ], + "github.com/libretro/libretro-mpv@99f7fe4fd43d68da45006301829eaf37c7edbcc2": [ + "version.h" + ], "github.com/libretro/libretro-o2em@679d6fec04963f6e70a7ec217e3d0ebb1fe472fc": [ "libretro.c", "libretro_core_options.h", "src/voice.c" ], "github.com/libretro/libretro-pcem@e07923e902dcedcf65a502fb3f14e75b9ded5271": [ + "configure.ac", "src/mem.c", "src/ne2000.c", "src/nvr.c", @@ -4665,24 +4870,46 @@ "libretro/libretro.c", "src/d_main.c" ], + "github.com/libretro/libretro-remotejoy@745b97ca8d2d825d0bfde4bb321d1bf7b49fd91d": [ + "libretro/libretro.c" + ], "github.com/libretro/libretro-samples@7c2ad112d7a5176de2191e473369668da8946b16": [ + "audio/audio_callback/libretro-test.c", + "audio/audio_no_callback/libretro-test.c", + "audio/audio_playback_wav/libretro-test.c", + "input/button_test/libretro.c", "tests/cruzes/cruzes.c", + "video/opengl/libretro_test_gl_compute_shaders/app/boxes.cpp", "video/opengl/libretro_test_gl_fixedfunction/libretro_gl_ff_test.c", "video/software/rendering/libretro-test.c", - "video/software/rendering_direct_to_vram/libretro-test.c" + "video/software/rendering_direct_to_vram/libretro-test.c", + "video/vulkan/vk_async_compute/libretro-test.c" + ], + "github.com/libretro/libretro-samples@bce193bc1b8c9a3da43b2ead0158a69e28b37ed8": [ + "tests/test/libretro-test.c", + "video/opengl/libretro_test_gl_shaders/libretro_gl_test.c", + "video/vulkan/vk_rendering/libretro-test.c" + ], + "github.com/libretro/libretro-simcoupe@c28046241ac6a4d79e55326b6e354dc02f92fa34": [ + "SimCoupe/Base/GUIDlg.cpp" ], "github.com/libretro/libretro-uae@151828f14d43bb2b0651327ee19a23b36a7081b2": [ "libretro/libretro-core.c", "libretro/libretro-core.h", + "retrodep/sysconfig.h", "sources/src/caps/caps.c", "sources/src/driveclick.c" ], "github.com/libretro/libretro-uae@7387937066b1b693816c3b4e2b7a0c601d41363d": [ "libretro/libretro-core.c", "libretro/libretro-core.h", + "retrodep/sysconfig.h", "sources/src/caps/caps.c", "sources/src/driveclick.c" ], + "github.com/libretro/libretro-uzem@d991ee94547c8294abc1c4cb73d63116aa58b5bc": [ + "uzem.h" + ], "github.com/libretro/libretro-vecx@dccff5402a16d1ea7c112095a8e42632bf149506": [ "bios/system.h", "libretro.c", @@ -4719,7 +4946,8 @@ "src/drivers/stv.c", "src/drivers/suprnova.c", "src/drivers/zn.c", - "src/hiscore.c" + "src/hiscore.c", + "src/mame2003/mame2003.c" ], "github.com/libretro/mame2003-plus-libretro@993403d0332946b8aa6abab56fbd9864498790a1": [ "src/artwork.c", @@ -4746,7 +4974,8 @@ "src/cheat.c", "src/cpuexec.c", "src/datafile.c", - "src/hiscore.c" + "src/hiscore.c", + "src/libretro/libretro.c" ], "github.com/libretro/mame2010-libretro@dff8aadd1c3f38215af3955746d6e19abe0ddcea": [ "src/emu/cheat.c", @@ -4787,7 +5016,8 @@ "src/mame/drivers/triforce.c", "src/mame/drivers/twinkle.c", "src/mame/drivers/viper.c", - "src/mame/drivers/zn.c" + "src/mame/drivers/zn.c", + "src/osd/retro/retroos.c" ], "github.com/libretro/mame2015-libretro@7adbf440d5f554097f58f8c6ebb673adc4320a31": [ "src/emu/fileio.c", @@ -4844,7 +5074,8 @@ "src/mame/drivers/twinkle.c", "src/mame/drivers/viper.c", "src/mame/drivers/zn.c", - "src/osd/retro/retromain.c" + "src/osd/retro/retromain.c", + "src/version.c" ], "github.com/libretro/mame2015-libretro@c8fe6c7399f24466808b67933b2b96feea188e0b": [ "src/emu/hiscore.c", @@ -4899,7 +5130,8 @@ "src/mame/drivers/triforce.c", "src/mame/drivers/twinkle.c", "src/mame/drivers/viper.c", - "src/mame/drivers/zn.c" + "src/mame/drivers/zn.c", + "src/version.c" ], "github.com/libretro/mame2016-libretro@ae07c2f88ff2482ba9f50ffc8c9e7e6fbfe97d0a": [ "src/emu/romload.cpp", @@ -4953,9 +5185,11 @@ "src/mame/drivers/triforce.cpp", "src/mame/drivers/twinkle.cpp", "src/mame/drivers/viper.cpp", - "src/mame/drivers/zn.cpp" + "src/mame/drivers/zn.cpp", + "src/version.cpp" ], "github.com/libretro/mame@5f5a07748ce5c239c4118463ce8e6473743d4859": [ + "makefile", "plugins/boot.lua", "src/devices/bus/a1bus/a1cassette.cpp", "src/devices/bus/a1bus/a1cffa.cpp", @@ -5944,6 +6178,7 @@ "src/osd/sdl3/sdlopts.cpp" ], "github.com/libretro/mame@dc1a96537089d6d4ecd01d9886b21061471c70c0": [ + "makefile", "src/mame/acorn/aristmk5.cpp", "src/mame/amiga/alg.cpp", "src/mame/amiga/arsystems.cpp", @@ -6004,6 +6239,7 @@ "src/mame/taito/taitotz.cpp" ], "github.com/libretro/mame@fba50741006fa3e27e1897f589d2048a505b8fd6": [ + "makefile", "src/mame/acorn/aristmk5.cpp", "src/mame/amiga/alg.cpp", "src/mame/amiga/arsystems.cpp", @@ -6066,6 +6302,7 @@ "src/mame/taito/taitotz.cpp" ], "github.com/libretro/melonDS@66b5d2634cd0a79030562811e6e05f5532f800ba": [ + "Makefile.common", "src/DSi.cpp", "src/DSi.h", "src/DSi_NAND.cpp", @@ -6079,18 +6316,24 @@ ], "github.com/libretro/meteor-libretro@9ea6c358eae6acd1a346647d96b832ccaffad0aa": [ "ameteor/source/bios.cpp", - "ameteor/source/cpu.cpp" + "ameteor/source/cpu.cpp", + "libretro/libretro.cpp" ], "github.com/libretro/mgba@7a12d6d4b9acb14c0ae62c9166b6a2f3d08007f6": [ "src/gb/core.c", "src/gb/gb.c", "src/gba/gba.c", "src/gba/hle-bios.c", - "src/platform/libretro/libretro.c" + "src/platform/libretro/libretro.c", + "version.cmake" + ], + "github.com/libretro/mrboom-libretro@40ac32020b540c4bb418c7d798a990932a65da04": [ + "common.hpp" ], "github.com/libretro/mupen64plus-libretro-nx@15bada85906593376e8fd697f1b0b3af3a20f512": [ "GLideN64/src/TextDrawer.cpp", - "mupen64plus-core/src/main/main.c" + "mupen64plus-core/src/main/main.c", + "mupen64plus-core/src/main/version.h" ], "github.com/libretro/mupen64plus-libretro@ab8134ac90a567581df6de4fc427dd67bfad1b17": [ "GLideN64/src/Config.cpp", @@ -6123,6 +6366,7 @@ "github.com/libretro/neocd_libretro@b1e04c738cb48a1dae0574b8877f6a116d270ca1": [ "src/bios.cpp", "src/bios.h", + "src/libretro.cpp", "src/libretro_bios.cpp", "src/memory.cpp", "src/path.cpp" @@ -6143,6 +6387,7 @@ ], "github.com/libretro/opera-libretro@370d9a25c030bb9de4aade371b31045691c40a95": [ "libopera/opera_bios.c", + "libretro.c", "libretro_core_options.c", "opera_boot.h", "opera_lr_opts.c" @@ -6163,11 +6408,15 @@ "mupen64plus-core/src/device/pif/bootrom_hle.c" ], "github.com/libretro/parallel-ps2@8c2b679622d602fe656ceba646b2f1f3a981c717": [ + "libretro/main.cpp", "pcsx2/CDVD/CDVD.cpp", "pcsx2/GameDatabase.cpp", "pcsx2/VMManager.cpp", "pcsx2/ps2/BiosTools.cpp" ], + "github.com/libretro/pascal-pong-libretro@2a0cfd44652c0b215862de3ef56916d1384ad8a0": [ + "pong.pas" + ], "github.com/libretro/pcsx1-libretro@35dabb85ff3af6825820f54090d344945f65d24e": [ "frontend/libretro.c" ], @@ -6185,6 +6434,10 @@ "pico/pico_int.h", "pico/sms.c", "platform/common/emu.c", + "platform/common/version.h", + "platform/libretro/libretro.c" + ], + "github.com/libretro/potator@227c5f6f3ce74d32e9002ce24c1420288559a860": [ "platform/libretro/libretro.c" ], "github.com/libretro/prosystem-libretro@8a88014287c7a01cd568067e5a557d0a2b2a051f": [ @@ -6196,20 +6449,31 @@ "libretro.c" ], "github.com/libretro/quasi88-libretro@7884be5277776a5d8c4fdc4e574e9b5b589945c9": [ - "src/LIBRETRO/libretro.c" + "src/LIBRETRO/libretro.c", + "src/version.h" + ], + "github.com/libretro/redbook@43544bb988f37a8f3df5af5d2c6f89dd255b8235": [ + "libretro.c" ], "github.com/libretro/redream-next@d826ece7f987c6fed336246e7272e2ded6cefa69": [ "src/guest/rom/boot.c", - "src/guest/rom/flash.c" + "src/guest/rom/flash.c", + "src/host/retro_host.c" ], "github.com/libretro/retro8@ddc06a142398ee9755894b3f0bb17c8dc428151d": [ + "src/libretro/libretro.cpp", "src/vm/gfx.cpp", "src/vm/lua_bridge.cpp" ], + "github.com/libretro/retroluxury@fdcd94fe4099735edcbea638760da3f2297bc520": [ + "test/libretro.c" + ], "github.com/libretro/rustation-libretro@3e328e3967e312e1f239aeeecd479a7b4bfda850": [ + "Cargo.toml", "src/lib.rs" ], "github.com/libretro/same_cdi@ff9bb99a50d518aef6d2add1c49b7c44447e3e78": [ + "gen/version.cpp", "src/mame/drivers/cdi.cpp", "src/osd/libretro/libretro-internal/retro_init.cpp" ], @@ -6218,6 +6482,7 @@ "backends/platform/libretro/include/libretro-defs.h", "backends/platform/libretro/scripts/bundle_datafiles.sh", "backends/platform/libretro/src/libretro-os-utils.cpp", + "base/internal_version.h", "dists/engine-data/engine_data.mk", "dists/engine-data/engine_data_big.mk", "dists/engine-data/engine_data_core.mk", @@ -6225,49 +6490,65 @@ ], "github.com/libretro/smsplus-gx@3844b46caa926b6494987b97da63092818c4ddef": [ "source/ports/libretro/smsplus_libretro.c", - "source/ports/sdl/smsplus.c" + "source/ports/sdl/smsplus.c", + "source/system.h" ], "github.com/libretro/snes9x2002@6ffbf9ef4f0063e1f1b78a40d10c50fc52f2524c": [ + "libretro/libretro.c", "src/globals.c" ], "github.com/libretro/snes9x2005@a79dfe9047e7fec58808aefe48ad2bf499c7af11": [ + "libretro.c", "source/apu_blargg.c", "source/dsp1emu.c", "source/globals.c" ], "github.com/libretro/snes9x2010@fe690dd321fa5a46b5234a2bde089d2518c62b0e": [ + "libretro/libretro.c", "src/apu.c", "src/bsx.c", "src/dsp.c" ], "github.com/libretro/snes9x@fae2fea08f74180759ef540ee94259213f503480": [ "libretro/libretro.cpp", - "memmap.cpp" + "memmap.cpp", + "snes9x.h" ], "github.com/libretro/stella2014-libretro@7d1361e407e63f29e52892655069e5fb4096e691": [ + "stella/src/common/Version.hxx", "stella/src/emucore/CartAR.cxx", "stella/src/emucore/KidVid.cxx", "stella/src/emucore/PropsSet.cxx" ], "github.com/libretro/stella2023@ba52c43b9eda950eb0c0eec69cda9b17dee8c39b": [ + "src/common/Version.hxx", "src/emucore/CartAR.cxx", "src/emucore/KidVid.cxx" ], "github.com/libretro/supafaust@642d1d1b6684aa7e306a02a89885f3f5456a5157": [ "libretro.cpp", + "mednafen/config.h", "mednafen/snes_faust/cart.cpp", "mednafen/snes_faust/cart/cx4.cpp", "mednafen/snes_faust/cart/dsp1.cpp", "mednafen/snes_faust/cart/dsp2.cpp" ], + "github.com/libretro/superbroswar-libretro@7f94e03163e1641ecd2a5703eb9ca7e1312b5e8d": [ + "libretro/libretro.cpp" + ], "github.com/libretro/swanstation@631f0fb31b809e0f889d7c551fe695788e39c1d3": [ "src/core/bios.cpp", "src/core/bios.h", "src/core/host_interface.cpp", "src/core/system.cpp", - "src/libretro/libretro_core_options.h" + "src/libretro/libretro_core_options.h", + "src/libretro/libretro_host_interface.cpp" + ], + "github.com/libretro/tgbdual-libretro@0392c9c469e653205e471114c7949c07c83bfce9": [ + "libretro/libretro.cpp" ], "github.com/libretro/tyrquake@67db338ff4d27fdbcead61d4e54938d758ae62cb": [ + "Makefile", "common/bgmusic.c", "common/common.c", "common/host.c", @@ -6278,22 +6559,28 @@ "libretro/core/libretro-core.cpp", "src/memory.cpp" ], + "github.com/libretro/uw8-libretro@1a64f4bde9662cb3dc04214d2b9f8ead19c85f0a": [ + "uw8.c" + ], "github.com/libretro/vba-next@788192f215ad0a1413f1625b40ebba3423fa0ade": [ "build/Makefile.common", "libretro/libretro.c", "src/gba.c" ], "github.com/libretro/vbam-libretro@115defb3a318258ab84746d45258a1aec19d0b4b": [ + "CHANGELOG.md", "src/gb/GB.cpp", "src/gba/GBA.cpp", "src/libretro/libretro.cpp" ], "github.com/libretro/vemulator-libretro@27a062f6ae532e5028e4fb54f523cc689e78146a": [ + "main.cpp", "vmu.cpp" ], "github.com/libretro/vice-libretro@6cbc44add8b0a03791b343712dc63dc1edad2675": [ "libretro/libretro-core.c", "retrodep/ui.c", + "vice/configure", "vice/data/C64DTV/basic-901226-01.bin", "vice/data/C64DTV/chargen-901225-01.bin", "vice/data/C64DTV/kernal-901227-03.bin", @@ -6323,19 +6610,29 @@ ], "github.com/libretro/vitaquake2@4b333fe18e8816974f04432af01f87654ae16e21": [ "libretro/libretro.c", - "qcommon/files.c" + "qcommon/files.c", + "qcommon/qcommon.h" + ], + "github.com/libretro/vitaquake3@61374f650f42c4f25c215ea1f8db992af162ada6": [ + "Makefile" + ], + "github.com/libretro/vitavoyager@58b70dd7ea891c2e8ddc37d03a5d58116b0ea118": [ + "Makefile" ], "github.com/libretro/xmil-libretro@3106aa54ffb244cca4019a6e95dacaa698781bc3": [ "font/fontdata.c", "font/fontx1.c", - "io/memio.c" + "io/memio.c", + "xmilver.h" ], "github.com/libretro/xrick-libretro@fcfde3623a04b4e986548e06d46630fcd0bd1e18": [ + "include/config.h", "libretro/core/libretro-core.c" ], "github.com/libretro/yabause@09ed8e5b2e97e7a848ea2514545c34c7b809e399": [ "yabause/CMakeLists.txt", "yabause/src/config.h.in", + "yabause/src/libretro/Makefile.common", "yabause/src/libretro/libretro.c", "yabause/src/memory.c", "yabause/src/memory.h", @@ -6344,6 +6641,7 @@ ], "github.com/libretro/yabause@3791ffb2221fda61d70aec5cca376418caf05669": [ "yabause/src/ctrl/src/stv.c", + "yabause/src/libretro/Makefile.common", "yabause/src/libretro/libretro.c", "yabause/src/sys/memory/src/cs0.c", "yabause/src/utils/src/db.c" @@ -6355,6 +6653,7 @@ "yabause/src/memory.h" ], "github.com/lightspark/lightspark@6f07fd65cb95b88ff73e905bf78a1a8b1ddf6049": [ + "CMakeLists.txt", "README.md", "src/backends/cachedsurface.cpp", "src/backends/config.cpp", @@ -6398,7 +6697,8 @@ "src/lindbergh/hook.c", "src/lindbergh/lindbergh.c", "src/lindbergh/sdlInput.c", - "src/lindbergh/shaderPatches.c" + "src/lindbergh/shaderPatches.c", + "src/lindbergh/version.h" ], "github.com/lindbergh-loader/linuxloader@9aa6e3e45ccfbbc60eb0f975aa9d3d158a13706c": [ "libs/win32/ll-deps/cg2.dll", @@ -6491,7 +6791,8 @@ "src/mame/drivers/triforce.c", "src/mame/drivers/twinkle.c", "src/mame/drivers/viper.c", - "src/mame/drivers/zn.c" + "src/mame/drivers/zn.c", + "src/version.c" ], "github.com/mamedev/mame@0fdce58484202265b0794a77652bb5e3da9c56af": [ "src/emu/romload.c", @@ -6501,6 +6802,7 @@ "src/mame/drivers/zn.c" ], "github.com/mamedev/mame@2e0aa823508663c4a27164be43467d1ef740cdcd": [ + "makefile", "src/mame/acorn/aristmk5.cpp", "src/mame/amiga/alg.cpp", "src/mame/amiga/arsystems.cpp", @@ -6562,6 +6864,7 @@ ], "github.com/mamedev/mame@39eb9e44c394df9aec8186f2d2ef7517ace232e1": [ "hash/a7800.xml", + "makefile", "src/devices/bus/a7800/hiscore.cpp", "src/emu/crsshair.cpp", "src/emu/fileio.cpp", @@ -6620,7 +6923,8 @@ "src/mame/drivers/triforce.cpp", "src/mame/drivers/twinkle.cpp", "src/mame/drivers/viper.cpp", - "src/mame/drivers/zn.cpp" + "src/mame/drivers/zn.cpp", + "src/version.cpp" ], "github.com/mamedev/mame@6ae44e46f91aa2bd74d9895e6bfd345baebd3d22": [ "src/emu/machine.c", @@ -6674,7 +6978,8 @@ "src/mame/drivers/triforce.c", "src/mame/drivers/twinkle.c", "src/mame/drivers/viper.c", - "src/mame/drivers/zn.c" + "src/mame/drivers/zn.c", + "src/version.c" ], "github.com/mamedev/mame@c285eb9bcd3505c14a338e29f251ba00776cc75c": [ "src/emu/romload.c", @@ -6709,9 +7014,11 @@ "src/mame/drivers/triforce.c", "src/mame/drivers/twinkle.c", "src/mame/drivers/viper.c", - "src/mame/drivers/zn.c" + "src/mame/drivers/zn.c", + "src/version.c" ], "github.com/mamedev/mame@eb05584a2b8d98d5af5587244c9cb7dcccf660fd": [ + "makefile", "src/mame/acorn/aristmk5.cpp", "src/mame/amiga/alg.cpp", "src/mame/amiga/arsystems.cpp", @@ -7824,7 +8131,8 @@ "src/mame/drivers/triforce.c", "src/mame/drivers/twinkle.c", "src/mame/drivers/viper.c", - "src/mame/drivers/zn.c" + "src/mame/drivers/zn.c", + "src/version.c" ], "github.com/melonDS-emu/melonDS@7d8dea42e221b13521270c423b61add41a741b97": [ "src/DSi.cpp", @@ -7867,7 +8175,8 @@ "src/gb/gb.c", "src/gba/gba.c", "src/gba/hle-bios.c", - "src/platform/libretro/libretro.c" + "src/platform/libretro/libretro.c", + "version.cmake" ], "github.com/mgba-emu/mgba@afd6f14eaf8bd35214ed3fb9dc69a92bfc3877a9": [ "CMakeLists.txt", @@ -7883,6 +8192,9 @@ "src/OSGLUSDL.c", "src/OSGLUXWN.c" ], + "github.com/minkcv/vm@11f63848a592644722ea1240b4b96785db9b009f": [ + "machine/libretro.c" + ], "github.com/mirror/daphne-emu@cd214c6046365047704f8a0563928970b30444e6": [ "src/game/astron.cpp", "src/game/badlands.cpp", @@ -7910,14 +8222,19 @@ "src/video/video.cpp", "src/video/video.h" ], + "github.com/mittonk/PD777@331af5f53cc0ffbab90f915681c8dfe3a4cda0a2": [ + "source/libretro/libretro.cpp" + ], "github.com/mittonk/dice-libretro@4b4596b8dd2252b9d08bd6aaa02afda4be834db3": [ "chips/rom.cpp", "libretro.cpp" ], "github.com/mkxp-z/mkxp-z@37a04d14413454551a7b6aadbb4e93e67168accb": [ + "meson.build", "src/display/font.cpp" ], "github.com/mmatyas/supermariowar@ed60ed45e296ff88a8883cbc51f5bf5aed880636": [ + "CMakeLists.txt", "src/common/global.cpp", "src/common/path.cpp", "src/smw/GSSplashScreen.cpp", @@ -7950,6 +8267,7 @@ "app/src/main/java/paulscode/android/mupen64plusae/persistent/GlobalPrefs.java", "app/src/main/java/paulscode/android/mupen64plusae/task/ExtractAssetsOrCleanupTask.java", "app/src/main/java/paulscode/android/mupen64plusae/util/RomHeader.java", + "build_common/version_common.gradle", "mupen64plus-core/mupen64plus-core.mk", "mupen64plus-core/upstream/src/api/frontend.c", "mupen64plus-core/upstream/src/device/gb/gb_cart.c", @@ -7979,7 +8297,8 @@ "github.com/mupen64plus/mupen64plus-ui-console@c8ac4862a019d7885b24927d9b4db5dd3e42a528": [ "projects/unix/Makefile", "src/cheat.c", - "src/main.c" + "src/main.c", + "src/version.h" ], "github.com/nachoverdon/Dolphin-MMJR2@4d0fdb8eea616320e967408cf5547c72a95196f3": [ "CMakeLists.txt", @@ -8073,6 +8392,7 @@ ], "github.com/nesbox/TIC-80@ae1195a9d0fbda243bf0ebd861d6876d072a979e": [ ".gitmodules", + "cmake/version.cmake", "include/tic80_config.h", "src/system/libretro/tic80_libretro.c" ], @@ -8108,9 +8428,11 @@ "github.com/netux79/zc210-libretro@b6426da40b074245fda097fd345fa7c9cdbf152a": [ "libretro.cpp", "zc/zc_sys.cpp", + "zc/zdefs.h", "zc/zelda.cpp" ], "github.com/nikita36078/J2ME-Loader@76383ebf348b5d477256fc884dfc55f053b45a86": [ + "app/build.gradle", "app/src/main/cpp/Android.mk", "app/src/main/cpp/m3g/Android.mk", "app/src/main/cpp/micro3d/Android.mk", @@ -8191,6 +8513,7 @@ "src/memory/Rom.cc" ], "github.com/openmoh/openmohaa@a2f340195975f4f042e28a60b62561dd9a0b2700": [ + "cmake/identity.cmake", "code/qcommon/common.c", "code/qcommon/files.cpp", "code/qcommon/q_shared.h" @@ -8214,7 +8537,8 @@ "src/tyrian2.c" ], "github.com/p2000t/M2000@da0b8cbef2007c907f5db2166847c3209643865d": [ - "src/P2000.c" + "src/P2000.c", + "src/P2000.h" ], "github.com/panda3ds-emu/panda3ds@3787358bdae41d5532ccd00c8998a527ae949949": [ "src/core/crypto/aes_engine.cpp", @@ -8235,6 +8559,7 @@ "src/game/pak.c" ], "github.com/pete-gordon/oricutron@002279fce9fa756d1d63cdc40ae97939eb7de7ed": [ + "Makefile", "ReadMe.txt", "gui.c", "gui.h", @@ -8279,10 +8604,12 @@ "Source/Project64-core/RomList/RomList.cpp", "Source/Project64-core/Settings.cpp", "Source/Project64-core/Settings/SettingType/SettingsType-RomDatabase.cpp", + "Source/Project64-core/Version.h.in", "Source/Project64/UserInterface/Settings/SettingsPage-DiskDrive.cpp", "Source/Script/package_zip.cmd" ], "github.com/proninyaroslav/nesoid@b2ff53fefa56a668aad4a579af85adc672d7a791": [ + "app/build.gradle", "app/jni/common/emuengine.h", "app/jni/neslib/Android.mk", "app/jni/neslib/cart.c", @@ -8349,6 +8676,7 @@ "src/core/mappers/waveFile.cpp", "src/core/palette.c", "src/core/unif.c", + "src/core/version.h", "src/gui/dipswitch.cpp", "src/gui/nes20db.cpp", "src/gui/qt.cpp", @@ -8437,7 +8765,8 @@ "src/mame/drivers/triforce.c", "src/mame/drivers/twinkle.c", "src/mame/drivers/viper.c", - "src/mame/drivers/zn.c" + "src/mame/drivers/zn.c", + "src/version.c" ], "github.com/r-type/sdlvice-libretro@baaaeed97ce1c0a07ed78e3e155cfbd6f508b472": [ "src/c64/c64-resources.c", @@ -8448,7 +8777,8 @@ "src/drive/ieee/ieee-resources.c", "src/drive/ieee/ieee.c", "src/printerdrv/drv-mps803.c", - "src/printerdrv/drv-nl10.c" + "src/printerdrv/drv-nl10.c", + "src/version.h" ], "github.com/rafaelvcaetano/melonDS-android-lib@431ab4bd0003c4356e25fce89640ae1004579b9b": [ "CMakeLists.txt", @@ -8473,7 +8803,8 @@ "app/src/main/java/me/magnum/melonds/impl/SharedPreferencesSettingsRepository.kt", "app/src/main/java/me/magnum/melonds/impl/emulator/AndroidEmulatorManager.kt", "app/src/main/res/values/strings.xml", - "app/src/main/res/xml/pref_custom_firmware.xml" + "app/src/main/res/xml/pref_custom_firmware.xml", + "buildSrc/src/main/kotlin/AppConfig.kt" ], "github.com/raspberrypi/quake3@8d89a2a3c1707bf0f75b2ea26645b872e97c0b95": [ "Makefile", @@ -8492,6 +8823,7 @@ "github.com/recalbox/pifba@419faa7c3967560f6fa149f2f78b5aedca5b123a": [ "burn/neogeo/d_neogeo.cpp", "burn/pgm/d_pgm.cpp", + "burn/version.h", "rpi/bzip.cpp", "rpi/main.cpp" ], @@ -8567,6 +8899,7 @@ "thirdparty/SDL2/src/joystick/SDL_gamecontroller.c" ], "github.com/ruffle-rs/ruffle@fe6cfc545405b888ebb478ce1c211d63147ca49b": [ + "Cargo.toml", "core/Cargo.toml", "core/src/avm1/globals.rs", "core/src/avm2/error_messages.rs", @@ -8593,6 +8926,9 @@ "video/external/src/decoder/openh264.rs", "web/Cargo.toml" ], + "github.com/rz5/nes@68dfadea5889da5da7c7e171823c9b3ab24fc892": [ + "nes.c" + ], "github.com/sarah-walker-pcem/pcem@10739b9618e388a5bf8c21e752f12b3952fa83e1": [ "src/mem.c", "src/nvr.c", @@ -8700,6 +9036,9 @@ "app/src/main/java/com/sbro/emucorex/ui/cheats/CheatManagerScreen.kt", "app/src/main/java/com/sbro/emucorex/ui/gamedb/GameDbCatalogRepository.kt" ], + "github.com/schellingb/vidtest_libretro@edfd32747e5eeb5ce82a5cee686ee1b32f1cc52e": [ + "vidtest.c" + ], "github.com/scummvm/scummvm@21955df19b580dad2807ec55f573b5f348690a81": [ "Makefile.common", "audio/softsynth/fluidsynth.cpp", @@ -8714,6 +9053,7 @@ "backends/platform/sdl/win32/win32.cpp", "backends/vkeybd/virtual-keyboard.cpp", "base/commandLine.cpp", + "base/internal_version.h", "base/main.cpp", "common/engine_data.cpp", "common/str-enc.cpp", @@ -8774,6 +9114,7 @@ "backends/platform/libretro/include/libretro-defs.h", "backends/platform/libretro/scripts/bundle_datafiles.sh", "backends/platform/libretro/src/libretro-os-utils.cpp", + "base/internal_version.h", "dists/engine-data/engine_data.mk", "dists/engine-data/engine_data_big.mk", "dists/engine-data/engine_data_core.mk", @@ -8846,6 +9187,7 @@ "ending.c", "font.c", "global.c", + "libretro/libretro.c", "main.c", "midi.c", "midi_timidity.c", @@ -8917,7 +9259,8 @@ "src/mame/drivers/viper.c", "src/mame/drivers/zn.c", "src/osd/droid-ios/osd-droid.c", - "src/osd/droid-ios/osdmain.c" + "src/osd/droid-ios/osdmain.c", + "src/version.c" ], "github.com/sh95014/AppleWin@51893cab7e8a3d1298734848f4fd616f2e1831fb": [ "Mariani.xcodeproj/project.pbxproj", @@ -8952,6 +9295,7 @@ "source/linux/linuxframe.cpp" ], "github.com/shadps4-emu/shadPS4@3eecea94ddaca360b19f4b800523b609a8b2ffcc": [ + "CMakeLists.txt", "src/core/libraries/sysmodule/sysmodule_internal.cpp", "src/core/linker.cpp" ], @@ -9001,6 +9345,7 @@ "x64/src/main/emulator/EmulatorPlatform.java" ], "github.com/shinyquagsire23/OpenJKDF2@798f930402525a23b94671d85b97b00390342fa0": [ + "cmake_modules/version.cmake", "src/Dw/dwInits.cpp", "src/Main/InstallHelper.c", "src/Main/Main.c", @@ -9010,6 +9355,7 @@ "src/main.c" ], "github.com/simias/rustation@1daf4faaade3c0fd1e9deace8e309e91303f1b46": [ + "Cargo.toml", "src/bios/db.rs", "src/cdrom/disc/mod.rs", "src/lib.rs" @@ -9068,7 +9414,8 @@ "github.com/snes9xgit/snes9x@85591435763dede7ef111aab8070cdf2d5a7da49": [ "bsx.cpp", "libretro/libretro.cpp", - "memmap.cpp" + "memmap.cpp", + "snes9x.h" ], "github.com/snes9xgit/snes9x@b33f2afb33c61d675aaf0319bd3b8cc8d6924d49": [ "bsx.cpp", @@ -9104,6 +9451,7 @@ "Src/SprowCoPro.cpp", "Src/SprowCoPro.h", "Src/Tube.cpp", + "Src/Version.h", "Src/Video.cpp", "Src/Z80Support.cpp", "UserData/Roms_Torch.cfg" @@ -9125,6 +9473,7 @@ "src/os/windows/OSystemWINDOWS.cxx" ], "github.com/stella-emu/stella@408dd6a30cc6fd91decc602403281c7dcfbc1b82": [ + "src/common/Version.hxx", "src/emucore/CartAR.cxx", "src/emucore/KidVid.cxx" ], @@ -9144,6 +9493,7 @@ "src/core/system.cpp" ], "github.com/suijingfeng/vkQuake3@650bd625eb725446279c6176804b554e2e1c98f3": [ + "Makefile", "code/qcommon/files.c", "code/qcommon/q_shared.h", "code/qcommon/qcommon.h", @@ -9158,6 +9508,7 @@ "github.com/sydarn/yabause@a40dace1ae0af3ebd45848549fdf396f40e3930f": [ "yabause/CMakeLists.txt", "yabause/src/config.h.in", + "yabause/src/libretro/Makefile.common", "yabause/src/libretro/libretro.c", "yabause/src/memory.c", "yabause/src/memory.h", @@ -9179,6 +9530,7 @@ ], "github.com/timoinutilis/lowres-nx@63d5988f74e1eea2ecd6ea0445a8ce260b0a9822": [ "core/boot_intro.c", + "core/core.h", "libretro/libretro_main.c" ], "github.com/tom-seddon/b2@e1c9b84a9460909d4dc2030ad1d4e050012b1910": [ @@ -9189,7 +9541,9 @@ "src/b2/roms.cpp" ], "github.com/tonioni/WinUAE@15110cf6606d1188800ea7ad86bf1cbd9c688741": [ + "CMakeLists.txt", "cfgfile.cpp", + "include/winuae_version.h", "memory.cpp", "od-win32/win32.cpp", "od-win32/win32gui.cpp", @@ -9224,6 +9578,7 @@ ], "github.com/trngaje/advanced_drastic@eaf41ab9bc8adcc15c010bf9517bc94c38e7bffd": [ "README.md", + "drastic", "launch.sh" ], "github.com/trzy/Supermodel@0eef09ba2b74e48fd5cfd388ee69a3743fd5f618": [ @@ -9269,6 +9624,7 @@ "Src/Version.h" ], "github.com/usineur/hode@59f3c466923c6b8d49e29176ff697a91165e7efc": [ + "CMakeLists.txt", "README.txt", "fs_posix.cpp", "main.cpp", @@ -9285,6 +9641,7 @@ "libretro.cpp" ], "github.com/visualboyadvance-m/visualboyadvance-m@46cc9f6bda114737e99304a4bd8427c038e63084": [ + "CHANGELOG.md", "src/core/gba/gba.cpp", "src/libretro/libretro.cpp" ], @@ -9325,6 +9682,7 @@ "src/core/VPApp.cpp", "src/core/player.cpp", "src/core/stdafx.h", + "src/core/vpversion.h", "src/parts/bumper.cpp", "src/parts/decal.cpp", "src/parts/kicker.cpp", @@ -9344,6 +9702,7 @@ ], "github.com/warmenhoven/Emir@8174cbbeea4070654841cdd4604361b3eee8fdd1": [ ".github/workflows/build.yaml", + "CMakeLists.txt", "apps/ymir-libretro/src/libretro.cpp", "apps/ymir-sdl3/CMakeLists.txt", "apps/ymir-sdl3/src/app/app.cpp", @@ -9358,6 +9717,9 @@ "libs/ymir-core/include/ymir/sys/memory_defs.hpp", "libs/ymir-core/src/ymir/db/ipl_db.cpp" ], + "github.com/warmenhoven/amiarcadia@8ca27c925833fe0021475155512c7f467924f647": [ + "DroidArcadia/app/src/main/cpp/da.h" + ], "github.com/warmenhoven/tia@6ae5bf998fe7a7842294089db17fc86fd73db1f0": [ "Makefile", "src/cart.c", @@ -9421,6 +9783,7 @@ "Source/Core/DolphinQt/MenuBar.cpp" ], "github.com/white-axe/mkxp-z@650cb0888a07d0b5044e131160ddfa53feaf595b": [ + "meson.build", "src/core.cpp", "src/display/font.cpp" ], @@ -9487,6 +9850,7 @@ "code/sys/sys_win32.c" ], "github.com/wzcwzc/emuscv@6d6e4594d79862dd5c2ff7f725c109e4266f0b03": [ + "Makefile.libretro", "src/vm/scv/memory.cpp", "src/vm/scv/memory.h" ], @@ -9618,6 +9982,7 @@ ], "github.com/ymir-emu/Ymir@8174cbbeea4070654841cdd4604361b3eee8fdd1": [ ".github/workflows/build.yaml", + "CMakeLists.txt", "apps/ymir-libretro/src/libretro.cpp", "apps/ymir-sdl3/CMakeLists.txt", "apps/ymir-sdl3/src/app/app.cpp", @@ -9634,6 +9999,7 @@ ], "github.com/ymir-emu/Ymir@fe439e98abc555c243a8a197850c71777ff664f4": [ ".github/workflows/build.yaml", + "CMakeLists.txt", "apps/ymir-sdl3/CMakeLists.txt", "apps/ymir-sdl3/src/app/app.cpp", "apps/ymir-sdl3/src/app/profile.cpp", @@ -9648,6 +10014,7 @@ "libs/ymir-core/src/ymir/db/ipl_db.cpp" ], "github.com/yonkuma/piemu@0785214f13746bdfd709b1909f007e213b470ba1": [ + "app.h", "piemu.c", "tools/mkpfi.c" ], @@ -9657,6 +10024,7 @@ ], "github.com/yuzu-emu-mirror/yuzu@15e6e48bef0216480661444a8d8b348c1cca47bb": [], "github.com/zelurker/raine@8e25d5d8907ae459181610912b0030c955683015": [ + "makefile", "source/emudx.c", "source/emumain.c", "source/files.c", @@ -9683,6 +10051,7 @@ "src/libretro/core.h" ], "gitlab.com/DavidGriffith/frotz@042d7bcadc1e2a8090cf737c841d8b7fc14b6eff": [ + "Makefile", "src/sdl/sf_deffont.c", "src/sdl/sf_fonts.c", "src/sdl/sf_resource.c" @@ -9723,9 +10092,11 @@ "src/fileio/CExeFile.cpp", "src/fileio/CPatcher.cpp", "src/fileio/CTileLoader.cpp", - "src/fileio/KeenFiles.h" + "src/fileio/KeenFiles.h", + "version.h" ], "gitlab.com/MaaaX-EPOCH84/libretro-emucv@6a6e1b15c5d5afefbc3fefdd8b52f3f2c8c0a0eb": [ + "CMakeLists.txt", "src/common.cpp", "src/core.cpp", "src/emu.cpp", @@ -9755,6 +10126,7 @@ "gitlab.com/jengun/sdltrs@205ddfd78fd00b32b3f730453175dbd70782ce79": [ "CMakeLists.txt", "README.md", + "configure.ac", "src/trs_cassette.c", "src/trs_chars.c", "src/trs_clones.c", @@ -9775,7 +10147,8 @@ ], "gitlab.com/jgemu/bsnes@53e6a7c2ec4b4220adc2f8dcb3786489bd04fe0e": [ "src/cartridge.cpp", - "src/coprocessor/icd.cpp" + "src/coprocessor/icd.cpp", + "version.h" ], "gitlab.com/jgemu/geolith@05ddef568212e7225ceec22f3ecb20b5a9b640e6": [ "jg.c", @@ -9787,7 +10160,8 @@ "src/jcv_coleco.c", "src/jcv_crvision.c", "src/jcv_db.c", - "src/jollycv.c" + "src/jollycv.c", + "version.h" ], "gitlab.com/jgemu/nestopia@b3adbeb42482e979ab99e16bcd9a22480f13479f": [], "gitlab.com/parallel-launcher/parallel-launcher@8dc2a83770c57bc00e1b1fae0aed71f2120cc875": [ @@ -9795,6 +10169,7 @@ "src/core/rom-finder.cpp", "src/core/settings.hpp", "src/core/updates.cpp", + "src/core/version.cpp", "src/main.cpp", "src/rhdc/core/sync.cpp", "src/ui/main-window.cpp", @@ -9841,6 +10216,7 @@ "source/Video.cpp" ], "gitlab.com/solarus-games/solarus@49d1d4686f67ec81c86c90ca30279a4ce06b1729": [ + "CMakeLists.txt", "cmake/AddDefines.cmake", "cmake/AddInstallDirs.cmake", "cmake/AddInstallTargets.cmake", @@ -9851,6 +10227,7 @@ ], "gitlab.com/solarus-games/solarus@86fa2031e1303aaaf9924a2f03dfdb7b7d1cbf4b": [ ".gitlab-ci.yml", + "CMakeLists.txt", "cmake/AddDefines.cmake", "cmake/AddInstallDirs.cmake", "cmake/AddInstallTargets.cmake", diff --git a/wiki/architecture.md b/wiki/architecture.md index bd69cd264..69889c947 100644 --- a/wiki/architecture.md +++ b/wiki/architecture.md @@ -180,7 +180,11 @@ A platform and an emulator profile can each name a different file for the same place in the BIOS folder: RetroBat puts its own `ATARIOSB.ROM` at `bios/ATARIOSB.ROM`, where atari800 reads another dump. A claim counts only when it rests on content or on the path. A file found by its name alone claims -nothing. The pack then answers to whoever asked for it: +nothing, unless its copy has the exact size the profile declares and the +platform's file has another size (`size_exact`): RetroDECK's `sgb2.boot.rom` is +the Super Game Boy 2 SNES program ROM where supersnes9x reads a 256-byte boot ROM. +Two files of equal size are revisions, not a conflict. The pack then answers to +whoever asked for it: - A **platform pack** must leave the frontend's own check green, since a user reading red concludes the pack is broken. Where the frontend only looks for @@ -280,17 +284,16 @@ being intact: see [verifying a release](release-process.md#verifying-a-release). Three things make that hold. Generated members (`README.txt`, `manifest.json`) carry a fixed date rather than the wall clock, and the manifest's `generated` -field is read from `database.json` instead of the build time. MAME and FBNeo -romsets are rebuilt deterministically from their ROMs, so a pack does not -inherit whatever metadata the source archive happened to carry. And every -member is written with the same fixed date: `ZipFile.write` copies the source -file's mtime, which is the checkout time for a file from the collection and -the wall clock for one the build just staged in `tmp/`. +field is read from `database.json` instead of the build time. Each ZIP of the +collection, MAME and FBNeo romsets included, is copied byte for byte, so it +hashes to the SHA1 the install list and the site publish, and git fixes those +bytes. And every member is written with the same fixed date: `ZipFile.write` +copies the source file's mtime, which is the checkout time for a file from the +collection and the wall clock for one the build just staged in `tmp/`. `tests/test_deterministic_zip.py` builds the same fixture twice and compares the bytes, for both the platform and the emulator pack paths. Its fixture -holds a romset, because without one the comparison never reaches the rebuild -path and passes while the real packs still move. +holds a ZIP, so the comparison covers the archive copy as well as loose files. ## Collection rules @@ -383,7 +386,8 @@ resolution. deploy identity. No workflow builds or publishes a release. - `safe_extract_zip()` prevents zip-slip path traversal attacks -- `deterministic_zip` rebuilds MAME ZIPs so same ROMs always produce the same hash +- a pack copies each ZIP of the collection byte for byte, so every member + hashes to the SHA1 the install list publishes - `crypto_verify.py` and `sect233r1.py` verify 3DS RSA-2048 signatures and AES-128-CBC integrity - ZIP inner ROM verification via `checkInsideZip()` replicates Batocera's behavior - `md5_composite()` replicates Recalbox's composite ZIP hash diff --git a/wiki/reading-reports.md b/wiki/reading-reports.md index 0b22210ae..15f9503e2 100644 --- a/wiki/reading-reports.md +++ b/wiki/reading-reports.md @@ -140,8 +140,13 @@ are still included in packs automatically. A required one the collection lacks is listed under the counts, and `--verbose` lists the optional ones too. The cross-reference uses `resolve_platform_cores()` to determine which emulator -profiles are relevant for each platform, then checks whether each profile's files -appear in the platform config. +profiles are relevant for each platform, then checks whether the platform +already declares each profile's files (`declaration_settles()`). A name alone +is not enough. The declaration must fill the core's own slot or have a size the +core accepts: Galaksija's 4 KiB `ROM1.BIN` does not stand for DOSBox's 32 KiB +SC-55 one. An alias counts only at the path where the core reads it, so +RetroDECK's root `MSX.ROM` is not CLK's `MSX/MSX.ROM`. An archive counts by its +name. ### Filtering output diff --git a/wiki/release-process.md b/wiki/release-process.md index 9e7746532..250ad6ad7 100644 --- a/wiki/release-process.md +++ b/wiki/release-process.md @@ -166,9 +166,9 @@ PR based on changed paths: ## Large files management Files too large for git are stored as assets on a permanent GitHub release -named `large-files`. Size does not decide it: four committed files are over -50 MB (`history.xml`, `default-GM.sf3`, `5.avi`, `tbblue.mmc`) and are served -from the repository like any other. +named `large-files`. Size does not decide it: four committed files are +over 50 MB (`history.xml`, `default-GM.sf3`, `5.avi`, `uqm-remix-disc1.uqm`) +and are served from the repository like any other. Examples: PS3UPDAT.PUP, PSVUPDAT.PUP, PSP2UPDAT.PUP, the DSi NAND images, maclc3.zip, Firmware.19.0.0.zip (Switch), the QEMU EDK2 firmware, the ScummVM diff --git a/wiki/tools.md b/wiki/tools.md index 5e4359e12..be4a332b7 100644 --- a/wiki/tools.md +++ b/wiki/tools.md @@ -15,7 +15,7 @@ Run everything in sequence: ```bash python scripts/pipeline.py --offline # DB + verify + packs + manifests + integrity + readme + site -python scripts/pipeline.py --offline --skip-packs # DB + verify + readme + site +python scripts/pipeline.py --offline --skip-packs # no packs or manifests python scripts/pipeline.py --offline --skip-docs # skip readme + site generation python scripts/pipeline.py --offline --target switch # filter by hardware target python scripts/pipeline.py --offline --with-truth # include truth generation + diff @@ -63,8 +63,8 @@ Pipeline flags: | Flag | Effect | |------|--------| | `--offline` | skip every step that needs the network (2, 2a, 2a2, 2b, 2b2), and pack only the release assets already in `.cache/large/` | -| `--skip-packs` | skip steps 4, 4b, 4c, 6 and the consistency check. README and site are still rewritten unless `--skip-docs` is given too | -| `--skip-docs` | skip steps 7 and 8 | +| `--skip-packs` | skip steps 4, 4b, 4c, 5 and 6. Steps 4d and 4e still run, and README and site are still rewritten unless `--skip-docs` is given too | +| `--skip-docs` | skip steps 7, 8 and 8b | | `--include-archived` | include archived platforms in verify, packs, truth and export | | `--target TARGET` | filter verify, packs and truth by hardware target | | `--source {platform,truth,full}` | pack file source, passed through to `generate_pack.py`. Refused with `--skip-packs` | @@ -1334,7 +1334,7 @@ before a release (wiki/release-process.md). Its lines stay in | `validate_site.py` | Validate rendered metadata, heading order, image alternatives, JSON-LD, local resources, links and fragments, and the page size and search index budgets | | `romset_recipes.py` | Identify which emulator version an arcade archive matches, and rebuild a pinned archive from ROMs already held | | `scraper/romset_dat_importer.py` | Fetch and import per-set recipes from MAME `-listxml` or FBNeo DATs into `recipes/`. `--fetch` alone takes the newest MAME release, and FBNeo DATs are refreshed by git blob sha. `--fetch ` reads FBNeo at that tag, branch or commit | -| `deterministic_zip.py` | Rebuild MAME BIOS ZIPs deterministically (same ROMs = same hash) | +| `deterministic_zip.py` | The fixed date pack members carry. Its archive rebuild is no longer called by the pack, which copies each ZIP byte for byte | | `torrentzip.py` | Build TorrentZip archives for MAME/FBNeo ROM sets (archive bytes depend only on contents) | | `crypto_verify.py` | 3DS RSA signature and AES crypto verification | | `sect233r1.py` | Pure Python ECDSA verification on sect233r1 curve (3DS OTP cert) | @@ -1373,7 +1373,7 @@ Further options, by script: | `romset_recipes.py` | `--dat-recipes PATH`, a snapshot or a directory of them (default `recipes/`) | | `split_pack.py` | `--max-size 1900M`, the ceiling per part (K, M, G, default 2G) | | `torrentzip.py` | `--check ZIP` reports conformance, `--rebuild ZIP -o OUT` normalizes an archive | -| `validate_pr.py` | `--changed` takes the BIOS files the branch changes, a folder named checks every file under it, `--markdown`, `--json` | +| `validate_pr.py` | `--changed` takes the BIOS files the branch changes, a folder named checks every file under it, `--place` copies each recognised file from outside `bios/` to its folder and declared name (or its `.variants/` slot), `--markdown`, `--json` | | `validate_site.py` | `--site-dir` (rendered site, default `website/dist`), `--base` (path the site is served under), `--max-errors N` | | `scraper/coreinfo_scraper.py` | `--compare-db database.json` lists the `.info` firmware the database lacks | | `scraper/mame_hash_scraper.py` | `--release mameNNNN --profile FILE --source-commit SHA --core NAME --built-by PLATFORM` writes the profile of a MAME build a platform compiles. `--devices --listxml FILE` documents in `mame.yml` every device set with dumped ROMs that the release's `-listxml` names (`isdevice="yes"`), with `--dry-run` to preview |