diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index 67130fe8f..cdc72f0eb 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -38,6 +38,7 @@ on: - "scripts/packreadme.py" - "scripts/packresolve.py" - "scripts/packverify.py" + - "scripts/pr_checks.py" - "scripts/provenance_report.py" - "scripts/refresh_data_dirs.py" - "scripts/region.py" @@ -46,6 +47,7 @@ on: - "scripts/safeparse.py" - "scripts/sect233r1.py" - "scripts/sitebootstrap.py" + - "scripts/sitecollection.py" - "scripts/sitediagrams.py" - "scripts/siteentries.py" - "scripts/siteguides.py" @@ -89,6 +91,8 @@ on: # generated folders are gitignored and never trigger anything. - "website/**" - ".github/workflows/deploy-site.yml" + # The contributing guide quotes the pip lines of its test job. + - ".github/workflows/validate.yml" workflow_dispatch: permissions: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 965d1a80a..ff2a2baec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,18 +72,33 @@ 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 pyte hypothesis vermin setuptools -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 --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 +pip install pyyaml "jsonschema[format-nongpl]==4.26.0" "markdown==3.10.2" "pyte==0.8.2" "hypothesis==6.170.8" "vermin==1.9.2" "setuptools==80.9.0" +python scripts/pr_checks.py ``` +`pr_checks.py` runs these, in CI's order, carries on past a failure as CI +does, and names at the end each check that failed or could not run: + +```bash +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 --strict # each profile the branch adds +vermin --no-tips -t=3.8- --violations install.py # install.py stays on Python 3.8 +python scripts/export_native.py --all --fetch --output-dir tmp/native # files the exporter tests compare with +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 # the site's packages, Node 24 +npm run build --prefix website # the built site +python -m unittest discover -s tests -t . # the suite +python3.8 -m unittest tests.test_install_tui # the full screen on the installer's floor +python scripts/validate_site.py --site-dir website/dist # links and page budgets +``` + +The versions are the ones CI pins. Three tools come from the system, not from +that line, and the tests that need them skip without them: `shellcheck` for the +handheld launchers, `bwrap` (bubblewrap) for the probe sandbox, and a Python 3.8 +with pyte (`python3.8 -m pip install "pyte==0.8.2"`) for the full screen. CI has all three. + `profile_sync.py` compares the branch with this repository's `main`, and fetches it in a fork's clone. A profile you only edit is not held to `--strict`: its older refs are reviewed by hand. diff --git a/README.md b/README.md index e554dfb12..aa2a0781c 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ Checked by is the test your platform runs on its own, replicated here from its s ## Build your own pack -Clone the repo and generate packs for any platform, emulator, or system: +Packs are built from a clone, and the clone carries the collection: its checkout alone is over 7.7 GB. `git clone --depth 1 https://github.com/Abdess/retrobios.git` leaves the history out. Files kept as release assets are downloaded when a pack needs them. Then generate packs for any platform, emulator, or system: ```bash # The pack of a platform, as released diff --git a/emulators/ardens.yml b/emulators/ardens.yml index 3fb3eb951..4471e18b5 100644 --- a/emulators/ardens.yml +++ b/emulators/ardens.yml @@ -67,6 +67,6 @@ platform_details: size: 1536 note: | Empty flashcart header written to the first 1536 bytes of the - W25Q128 flash image during initialization. This is not a BIOS - file -- it is the default flash content the Arduboy FX expects - at the start of external storage. + W25Q128 flash image during initialization. It is the default flash + content the Arduboy FX expects at the start of external storage, + not a BIOS file. diff --git a/emulators/atari800.yml b/emulators/atari800.yml index cc18f11af..85a78c4c4 100644 --- a/emulators/atari800.yml +++ b/emulators/atari800.yml @@ -16,7 +16,7 @@ cores: [atari800] systems: [atari-400-800, atari-5200, xegs] # Atari800 emulates the Atari 8-bit family (400/800/XL/XE) and the 5200 console. -# All BIOS files are optional -- the core ships built-in Altirra OS replacements +# All BIOS files are optional. The core ships built-in Altirra OS replacements # (altirraos_800, altirraos_xl, altirra_5200_os, altirra_basic) compiled into the # binary. If real ROM files are found in the system directory they take priority. # @@ -121,9 +121,8 @@ files: note: > Atari 400/800 OS Rev B (NTSC). CRC32 0x0e86d61d matches sysrom.c SYSROM_B_NTSC. Preferred over Rev A for 400/800 mode. - Note: .info lists md5 a3e8d617c95d08031fe1b20d541434b2 which is a - different dump (crc32 3e28a1fe) -- both in repo, the CRC32-matched - version is the one sysrom.c auto-detects. + The .info lists md5 a3e8d617c95d08031fe1b20d541434b2, a different + dump (crc32 3e28a1fe). sysrom.c detects the CRC32-matched one. source_ref: "source atari800/src/sysrom.c:91, source atari800/src/sysrom.c:286" # -- Atari XL/XE OS -- @@ -165,7 +164,7 @@ files: # -- XEGS Missile Command built-in game -- # The XEGS had Missile Command in ROM (8 KB), CRC32: 0xbdca01fb - # No filename auto-match -- must be set via config or detected by CRC. + # No filename auto-match. It must be set via config or detected by CRC. - name: "XEGAME.ROM" path: "XEGAME.ROM" diff --git a/emulators/cruzes.yml b/emulators/cruzes.yml index eea7783c2..2c70bae54 100644 --- a/emulators/cruzes.yml +++ b/emulators/cruzes.yml @@ -17,7 +17,7 @@ systems: notes: | Unfinished Picross puzzle game, part of the libretro-samples repository. - No separate upstream -- written directly for the libretro API. + The libretro API is its only target, with no separate upstream. supports_no_game true (tests/cruzes/cruzes.c:772), valid_extensions "" (tests/cruzes/cruzes.c:749). Puzzle grid hardcoded in retro_init (tests/cruzes/cruzes.c:706-717) via load_challenge. diff --git a/emulators/emux_chip8.yml b/emulators/emux_chip8.yml index 34027feb2..1ef84b7fa 100644 --- a/emulators/emux_chip8.yml +++ b/emulators/emux_chip8.yml @@ -51,4 +51,3 @@ platform_details: note: | No external BIOS. The 80-byte character font is compiled into the binary as char_mem[] and memcpy'd into RAM[0x000] on every reset. - This is standard CHIP-8 behavior -- all interpreters embed the font. diff --git a/emulators/fake08.yml b/emulators/fake08.yml index a6a62586a..40f4c102c 100644 --- a/emulators/fake08.yml +++ b/emulators/fake08.yml @@ -30,8 +30,8 @@ notes: | binary as string constants (p8GlobalLuaFunctions.h, DefaultCart.h). When no ROM is passed (supports_no_game = true), the core queues its - built-in BIOS cart "__FAKE08-BIOS.p8" (platform/libretro/libretro.cpp:751). This is not - an external file -- it is generated internally by the VM. + built-in BIOS cart "__FAKE08-BIOS.p8" (platform/libretro/libretro.cpp:751). The VM + generates it internally, so no external file is read. The core accepts .p8 (text) and .png (cartridge-as-image) formats (platform/libretro/libretro.cpp:258). Content is loaded either via the libretro data buffer diff --git a/emulators/freechaf.yml b/emulators/freechaf.yml index a45926c2b..527674410 100644 --- a/emulators/freechaf.yml +++ b/emulators/freechaf.yml @@ -35,7 +35,7 @@ notes: | hle_state.psu1_hle and hle_state.psu2_hle (src/channelf_hle.c). The .info file declares all three firmware files as required (opt=false), - but the core does not abort on missing BIOS -- it falls back to HLE. + but the core does not abort on a missing BIOS. It falls back to HLE. files: - name: "sl31253.bin" diff --git a/emulators/galaksija.yml b/emulators/galaksija.yml index 746d1f918..12fe25ba0 100644 --- a/emulators/galaksija.yml +++ b/emulators/galaksija.yml @@ -20,7 +20,7 @@ systems: # Z80-based at 3.072 MHz, PAL 50fps, character-mode display (32x16 chars, 256x208 px). # # The core embeds all three firmware files compressed in rom.c (LoadCHRGENBIN, -# LoadROM1BIN, LoadROM2BIN). External files are optional overrides -- if ANY of +# LoadROM1BIN, LoadROM2BIN). External files are optional overrides. If ANY of # CHRGEN.BIN or ROM1.BIN is missing from /galaksija/, the core sets # firmware_ignore=true and loads all ROMs from the embedded copies instead. # @@ -73,7 +73,7 @@ files: note: > Math extension ROM. Mapped at 0x1000-0x1FFF. Adds floating-point math functions (SIN, COS, TG, SQR, LN, EXP, POW, etc). Optional even when - loading from disk -- missing ROM2 only triggers a warning. Embedded in + loading from disk, where a missing ROM2 only triggers a warning. Embedded in rom.c as fallback. source_ref: "source src/rom.h:11, source src/libretro.c:107-110, source src/external/galaksija-emulator/src/init.c:83-92" diff --git a/emulators/mesen.yml b/emulators/mesen.yml index 54eb463e8..2114a0332 100644 --- a/emulators/mesen.yml +++ b/emulators/mesen.yml @@ -42,7 +42,7 @@ notes: | File path construction: retro_load_game() sets FolderUtilities::SetHomeFolder(systemFolder). All BIOS lookups use - CombinePath(GetHomeFolder(), filename) -- flat, no subdirectories. + CombinePath(GetHomeFolder(), filename), flat, with no subdirectories. No functional divergence between upstream and libretro for BIOS loading. diff --git a/emulators/mgba.yml b/emulators/mgba.yml index 30663a7fd..2f8be12e7 100644 --- a/emulators/mgba.yml +++ b/emulators/mgba.yml @@ -30,7 +30,7 @@ notes: | All BIOS files are optional. mGBA ships a built-in HLE BIOS for GBA (src/gba/hle-bios.c) that covers most software. The real BIOS improves accuracy (startup logo, some edge-case SWI behavior). There is no HLE - BIOS for GB/GBC/SGB -- those rely on GBSkipBIOS() if no file is found. + BIOS for GB/GBC/SGB, which rely on GBSkipBIOS() if no file is found. BIOS loading is controlled by two core options: mgba_use_bios (default ON) - whether to look for BIOS files at all diff --git a/emulators/pokemini.yml b/emulators/pokemini.yml index 76efaa28c..12900eb5e 100644 --- a/emulators/pokemini.yml +++ b/emulators/pokemini.yml @@ -21,8 +21,8 @@ notes: | The core has an embedded FreeBIOS (open-source replacement) compiled directly into the binary from freebios/freebios.c. This means the - real BIOS is never strictly required -- games will run with FreeBIOS - alone, though compatibility may differ from the original firmware. + real BIOS is never strictly required. Games run with FreeBIOS alone, + though compatibility may differ from the original firmware. BIOS loading sequence in the libretro port: diff --git a/emulators/prboom-plus.yml b/emulators/prboom-plus.yml index 912ff69bd..d12e1fca9 100644 --- a/emulators/prboom-plus.yml +++ b/emulators/prboom-plus.yml @@ -120,7 +120,7 @@ files: analysis: engine_wad_contents: - source_ref: "data/CMakeLists.txt:24-340 (rdatawad input lists), data/CMakeLists.txt:342-362 (WAD_SRC and WAD_CMDLINE)" + source_ref: "prboom2/data/CMakeLists.txt:24-340 (rdatawad input lists), prboom2/data/CMakeLists.txt:342-362 (WAD_SRC and WAD_CMDLINE)" note: > What rdatawad packs into prboom-plus.wad, in the order the command line builds it. @@ -135,8 +135,8 @@ analysis: - "SINETABL, TANGTABL, TANTOANG, GAMMATBL" - "CHEXDEH, BFGBEX, NERVEBEX" - "GLSHADOW, GLFP, GLVP" - - "-PRBHUD-, read by hu_stuff.c:1144" - - "the M_* OpenGL automap icons listed in gl_map.c:48-68" + - "-PRBHUD-, read by prboom2/src/hu_stuff.c:1144" + - "the M_* OpenGL automap icons listed in prboom2/src/gl_map.c:48-68" sounds: "DSDGSIT, DSDGATK, DSDGACT, DSDGDTH, DSDGPAIN" graphics: "the DIG* status bar digits, STBR*, the BOX* frame pieces, STKEYS6-8, STCFN096, M_BUTT1/2, M_COLORS, M_PALNO, M_PALSEL, M_VBOX, CROSS1-3" flats: "-N0_TEX-, between FF_START and FF_END" @@ -146,7 +146,7 @@ analysis: files: - "shaders/glvp.txt" - "shaders/glfp.txt" - source_ref: "gl_shader.c:144-152 (path built from I_DoomExeDir), gl_shader.c:84-129 (ReadLump)" + source_ref: "prboom2/src/gl_shader.c:144-152 (path built from I_DoomExeDir), prboom2/src/gl_shader.c:84-129 (ReadLump)" note: > Read from the config directory when present. ReadLump falls back to the GLVP and GLFP lumps in the ns_prboom namespace when the file is missing, @@ -160,18 +160,18 @@ analysis: - filename: "prboom-plus.cfg" source_ref: "prboom2/src/m_misc.c:1586-1600 (BOOM_CFG, path from I_DoomExeDir), prboom2/src/m_misc.c:1606 (read)" - filename: "RGB2PAL.dat" - source_ref: "gl_hires.c:938-953 (read), gl_hires.c:962-967 (opened wb and written)" + source_ref: "prboom2/src/gl_hires.c:938-953 (read), prboom2/src/gl_hires.c:962-967 (opened wb and written)" note: > A 24-bit RGB to palette lookup table built from V_GetPlaypal, so its - contents follow whichever IWAD is loaded. gl_hires.c:920-936 prefers + contents follow whichever IWAD is loaded. prboom2/src/gl_hires.c:920-936 prefers an RGB2PAL lump of the right length when a WAD supplies one. - filename: ".cache" - source_ref: "gl_hires.c:1144-1147 (read), gl_hires.c:1179 (gld_HiRes_WriteCache)" + source_ref: "prboom2/src/gl_hires.c:1144-1147 (read), prboom2/src/gl_hires.c:1179 (gld_HiRes_WriteCache)" note: "Keyed on the source image mtime and discarded when it differs." - filename: "levelstat.txt" - source_ref: "e6y.c:968" + source_ref: "prboom2/src/e6y.c:968" - filename: "levelinfo.txt" - source_ref: "prboom2/src/d_main.c:1049-1060, behind GL_DOOM and PRBOOM_DEBUG" + source_ref: "prboom2/src/d_main.c:1049-1060 (behind GL_DOOM and PRBOOM_DEBUG)" - filename: "temp_a.nut, temp_v.nut" source_ref: "prboom2/src/m_misc.c:1065-1066 (cap_tempfile1, cap_tempfile2)" @@ -189,11 +189,11 @@ analysis: - what: "autoload directories, /autoload/doom-all and one per IWAD" source_ref: "prboom2/src/d_main.c:1400-1414 (GetAutoloadBaseDir), prboom2/src/d_main.c:1500-1519 (D_AutoloadIWadDir)" - what: "demos, -playdemo, -fastdemo, -record" - source_ref: "r_demo.c:89-119, g_game.c:2570, g_game.c:3150" + source_ref: "prboom2/src/r_demo.c:89-119, prboom2/src/g_game.c:2570, prboom2/src/g_game.c:3150" - what: "external high resolution texture packs, under textures/, flats/ and patches/" - source_ref: "gl_hires.c:467-631 (hires_paths), gl_hires.c:703-719 (gl_texture_hires_dir or I_DoomExeDir)" + source_ref: "prboom2/src/gl_hires.c:467-631 (hires_paths), prboom2/src/gl_hires.c:703-719 (gl_texture_hires_dir or I_DoomExeDir)" - what: "savegames, DOOMSAVEDIR or the config directory" - source_ref: "prboom2/src/d_main.c:1019-1032, m_menu.c:957" + source_ref: "prboom2/src/d_main.c:1019-1032, prboom2/src/m_menu.c:957" - what: "response files, @name" source_ref: "prboom2/src/d_main.c:1094-1112" @@ -203,6 +203,6 @@ analysis: - what: "ffmpeg, video capture and muxing" source_ref: "prboom2/src/m_misc.c:1062-1064 (cap_soundcommand, cap_videocommand, cap_muxcommand)" - what: "wget then unzip, fetching a WAD a game server asks for" - source_ref: "d_client.c:236-257 (execlp), doc/prboom-plus.6:200-206" + source_ref: "prboom2/src/d_client.c:236-257 (execlp), prboom2/doc/prboom-plus.6:200-206" - what: "getwad_cmdline, run through system() when a needed WAD is missing" - source_ref: "r_demo.c:1641-1704 (D_TryGetWad), prboom2/src/m_misc.c:1029 (default empty, so the path is inert until configured)" + source_ref: "prboom2/src/r_demo.c:1641-1704 (D_TryGetWad), prboom2/src/m_misc.c:1029 (default empty, so the path is inert until configured)" diff --git a/emulators/px68k.yml b/emulators/px68k.yml index 479d774b6..5d73c2efe 100644 --- a/emulators/px68k.yml +++ b/emulators/px68k.yml @@ -23,7 +23,7 @@ systems: # 1. Base path set to /keropi/ via file_setcd(winx68k_dir) at line 969 # 2. IPL ROM: iterates BIOSFILE[] = {iplrom.dat, iplrom30.dat, iplromco.dat, iplromxv.dat} # and opens the first file found. Reads 0x20000 bytes (128 KB) into IPL buffer. -# Returns 0 (failure) if none found -- core will not start. +# Returns 0 (failure) if none found, and the core does not start. # 3. SCSI check (WinX68k_SCSICheck): if IPL contains SCSI vectors at offset 0x30600-0x30c00, # patches a fake SCSI BIOS stub at $fc0000 with "Human68k" signature. # 4. Byte-swaps IPL in 16-bit pairs (big-endian ROM on little-endian host). @@ -32,7 +32,7 @@ systems: # # SRAM (x68k/sram.c:50-73): # sram.dat is 16 KB of battery-backed SRAM, loaded on init and saved on cleanup. -# Not a BIOS -- runtime state. Created automatically if missing. +# Not a BIOS but runtime state. Created automatically if missing. # # Directory layout expected: # /keropi/iplrom.dat (or iplrom30.dat, iplromco.dat, iplromxv.dat) @@ -41,7 +41,7 @@ systems: # /keropi/config (auto-created, core settings) files: - # IPL ROM -- the main system BIOS. The core tries four filenames in order and + # IPL ROM, the main system BIOS. The core tries four filenames in order and # uses the first one found. Only one is needed. iplrom.dat is the original X68000, # iplrom30.dat is X68030, iplromco.dat is X68000 Compact, iplromxv.dat is X68000 XVI. @@ -87,7 +87,7 @@ files: Only used if the three other IPL variants are not found. source_ref: "source libretro.c:985" - # Font ROM -- character generator ROM for text rendering. + # Font ROM, the character generator ROM for text rendering. - name: "cgrom.dat" installed_on: [recalbox] diff --git a/emulators/retrodream.yml b/emulators/retrodream.yml index 0d0c76fba..a99dd0bdf 100644 --- a/emulators/retrodream.yml +++ b/emulators/retrodream.yml @@ -18,7 +18,7 @@ systems: notes: | Fork of the last GPLv3 release of redream (inolen) before it went closed-source. - The core has HLE bootstrap -- when boot.bin is missing, bios_invalid_instr() + The core has an HLE bootstrap. When boot.bin is missing, bios_invalid_instr() traps at PC=0x0 and calls bios_boot(), which loads IP.BIN and 1ST_READ.BIN directly from disc, sets up syscall vectors, and starts execution at 0xac008300. diff --git a/exports/amberelec.json b/exports/amberelec.json index 494490274..f409a92a7 100644 --- a/exports/amberelec.json +++ b/exports/amberelec.json @@ -864,7 +864,7 @@ } } ], - "hashes_corrected": [], + "hashes_updated": [], "hashes_filled": [ "msx/MSXDOS2.ROM md5", "msx/FMPAC.rom md5", @@ -877,10 +877,10 @@ "neogeo/aes.zip md5", "neogeocd/neocdz.zip md5" ], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 107, "removal_candidates": [ diff --git a/exports/batocera.json b/exports/batocera.json index cf6d3eaf0..5429fd3b0 100644 --- a/exports/batocera.json +++ b/exports/batocera.json @@ -213,7 +213,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "nds", "name": "dsi_bios7.bin", "destination": "dsi_bios7.bin", @@ -222,7 +222,7 @@ "new": "322ceeafb77b0cc96a1fcc6271e0e0ae" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "nds", "name": "dsi_bios9.bin", "destination": "dsi_bios9.bin", @@ -231,7 +231,7 @@ "new": "23031f25602a52c2df32c39554b0c55e" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "msx", "name": "MSX.ROM", "destination": "MSX.ROM", @@ -1560,16 +1560,16 @@ } } ], - "hashes_corrected": [ + "hashes_updated": [ "nds/dsi_bios7.bin md5", "nds/dsi_bios9.bin md5", "msx/MSX.ROM md5" ], "hashes_filled": [], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 114, "removal_candidates": [ diff --git a/exports/bizhawk.json b/exports/bizhawk.json index 272ca6115..50029774d 100644 --- a/exports/bizhawk.json +++ b/exports/bizhawk.json @@ -14,12 +14,12 @@ "not_kept": 0 }, "changes": [], - "hashes_corrected": [], + "hashes_updated": [], "hashes_filled": [], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 0, "removal_candidates": [], diff --git a/exports/emudeck.json b/exports/emudeck.json index a970a3962..9b3c88d81 100644 --- a/exports/emudeck.json +++ b/exports/emudeck.json @@ -14,12 +14,12 @@ "not_kept": 0 }, "changes": [], - "hashes_corrected": [], + "hashes_updated": [], "hashes_filled": [], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 107, "removal_candidates": [ diff --git a/exports/emuelec.json b/exports/emuelec.json index 36da198f3..a6c82fbbd 100644 --- a/exports/emuelec.json +++ b/exports/emuelec.json @@ -955,7 +955,7 @@ } } ], - "hashes_corrected": [], + "hashes_updated": [], "hashes_filled": [ "msx/MSXDOS2.ROM md5", "msx/FMPAC.rom md5", @@ -1005,10 +1005,10 @@ "Cannonball/opr-10268.99 md5", "RickDangerous/data.zip md5" ], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 312, "unattributed": [], diff --git a/exports/knulli.json b/exports/knulli.json index 026f24351..f8b9fe592 100644 --- a/exports/knulli.json +++ b/exports/knulli.json @@ -48,7 +48,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "neogeo", "name": "neogeo.zip", "destination": "neogeo.zip", @@ -233,7 +233,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "nds", "name": "dsi_bios7.bin", "destination": "dsi_bios7.bin", @@ -242,7 +242,7 @@ "new": "322ceeafb77b0cc96a1fcc6271e0e0ae" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "nds", "name": "dsi_bios9.bin", "destination": "dsi_bios9.bin", @@ -284,7 +284,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "msx", "name": "MSX.ROM", "destination": "MSX.ROM", @@ -1393,17 +1393,17 @@ } } ], - "hashes_corrected": [ + "hashes_updated": [ "neogeo/neogeo.zip md5", "nds/dsi_bios7.bin md5", "nds/dsi_bios9.bin md5", "msx/MSX.ROM md5" ], "hashes_filled": [], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 114, "removal_candidates": [ diff --git a/exports/lakka.json b/exports/lakka.json index a1e732fa6..f8e8f00e6 100644 --- a/exports/lakka.json +++ b/exports/lakka.json @@ -29,7 +29,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Arcade", "name": "aes.zip", "destination": "aes.zip", @@ -38,7 +38,7 @@ "new": "23c66caa80b93199914fae246a5ea6e84db1be29" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Arcade", "name": "aes.zip", "destination": "aes.zip", @@ -47,7 +47,7 @@ "new": "ad9585c72130c56f04ae26aae87c289d" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Arcade", "name": "aes.zip", "destination": "aes.zip", @@ -56,7 +56,7 @@ "new": "12c66621" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "Arcade", "name": "aes.zip", "destination": "aes.zip", @@ -889,7 +889,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios7.bin", "destination": "dsi_bios7.bin", @@ -898,7 +898,7 @@ "new": "c7c7570bfe51c3c7c5da3b01331b94e7e7cb4f53" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios7.bin", "destination": "dsi_bios7.bin", @@ -907,7 +907,7 @@ "new": "322ceeafb77b0cc96a1fcc6271e0e0ae" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios7.bin", "destination": "dsi_bios7.bin", @@ -916,7 +916,7 @@ "new": "4316cc42" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios9.bin", "destination": "dsi_bios9.bin", @@ -925,7 +925,7 @@ "new": "719b9eef33692406d7170ff526069615759c4dfc" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios9.bin", "destination": "dsi_bios9.bin", @@ -934,7 +934,7 @@ "new": "23031f25602a52c2df32c39554b0c55e" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios9.bin", "destination": "dsi_bios9.bin", @@ -957,7 +957,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "SNK - NeoGeo CD", "name": "neocdz.zip", "destination": "neocdz.zip", @@ -1965,7 +1965,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "access.dat", "destination": "scummvm/extra/access.dat", @@ -1974,7 +1974,7 @@ "new": "775b12b7e787bbaaeb73aa45b1ad3bb383e041ef" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "access.dat", "destination": "scummvm/extra/access.dat", @@ -1983,7 +1983,7 @@ "new": "4f9dd40b455876d5c54b5ebd93aec88c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "access.dat", "destination": "scummvm/extra/access.dat", @@ -1992,7 +1992,7 @@ "new": "f63ddcc5" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "access.dat", "destination": "scummvm/extra/access.dat", @@ -2001,7 +2001,7 @@ "new": 520352 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "cryomni3d.dat", "destination": "scummvm/extra/cryomni3d.dat", @@ -2010,7 +2010,7 @@ "new": "3971847e26be0abad8196caf03e5852a81ddbdea" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "cryomni3d.dat", "destination": "scummvm/extra/cryomni3d.dat", @@ -2019,7 +2019,7 @@ "new": "7224c7a94074addeb69015b1346695d2" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "cryomni3d.dat", "destination": "scummvm/extra/cryomni3d.dat", @@ -2028,7 +2028,7 @@ "new": "5956e0e9" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "cryomni3d.dat", "destination": "scummvm/extra/cryomni3d.dat", @@ -2037,7 +2037,7 @@ "new": 70416 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "drascula.dat", "destination": "scummvm/extra/drascula.dat", @@ -2046,7 +2046,7 @@ "new": "05c97e92439b67aea8f6eb7fc96db023edf62124" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "drascula.dat", "destination": "scummvm/extra/drascula.dat", @@ -2055,7 +2055,7 @@ "new": "b304979ed642a6dcaf26f43f8a37a1de" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "drascula.dat", "destination": "scummvm/extra/drascula.dat", @@ -2064,7 +2064,7 @@ "new": "b36f7b0e" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "drascula.dat", "destination": "scummvm/extra/drascula.dat", @@ -2073,7 +2073,7 @@ "new": 259625 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "fonts.dat", "destination": "scummvm/extra/fonts.dat", @@ -2082,7 +2082,7 @@ "new": "1eaef39122559500fbbc705c9425500311d23e03" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "fonts.dat", "destination": "scummvm/extra/fonts.dat", @@ -2091,7 +2091,7 @@ "new": "6df29d218962e155d0a2114006af45c0" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "fonts.dat", "destination": "scummvm/extra/fonts.dat", @@ -2100,7 +2100,7 @@ "new": "f59313fa" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "fonts.dat", "destination": "scummvm/extra/fonts.dat", @@ -2109,7 +2109,7 @@ "new": 6449829 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "freescape.dat", "destination": "scummvm/extra/freescape.dat", @@ -2118,7 +2118,7 @@ "new": "c6b3d3152e4e2ff4c9bf55e103bb39ed7faca2bd" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "freescape.dat", "destination": "scummvm/extra/freescape.dat", @@ -2127,7 +2127,7 @@ "new": "71697f90fb0c7747616dbb0799ce966b" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "freescape.dat", "destination": "scummvm/extra/freescape.dat", @@ -2136,7 +2136,7 @@ "new": "54f0350e" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "freescape.dat", "destination": "scummvm/extra/freescape.dat", @@ -2145,7 +2145,7 @@ "new": 30124 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "hugo.dat", "destination": "scummvm/extra/hugo.dat", @@ -2154,7 +2154,7 @@ "new": "74a5fa172f5327fedc145cb9eccc8fd050b9cfb9" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "hugo.dat", "destination": "scummvm/extra/hugo.dat", @@ -2163,7 +2163,7 @@ "new": "a865e70f1b0998a500f522dab441c8fc" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "hugo.dat", "destination": "scummvm/extra/hugo.dat", @@ -2172,7 +2172,7 @@ "new": "fc0dd1cb" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "hugo.dat", "destination": "scummvm/extra/hugo.dat", @@ -2181,7 +2181,7 @@ "new": 186866 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "kyra.dat", "destination": "scummvm/extra/kyra.dat", @@ -2190,7 +2190,7 @@ "new": "31b0b9418af2f9dd4e4094aada05d48eb355975b" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "kyra.dat", "destination": "scummvm/extra/kyra.dat", @@ -2199,7 +2199,7 @@ "new": "ebddd7bbd97135a427083db5daf6f9fc" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "kyra.dat", "destination": "scummvm/extra/kyra.dat", @@ -2208,7 +2208,7 @@ "new": "6b7229ce" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "kyra.dat", "destination": "scummvm/extra/kyra.dat", @@ -2217,7 +2217,7 @@ "new": 2028520 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "lure.dat", "destination": "scummvm/extra/lure.dat", @@ -2226,7 +2226,7 @@ "new": "516347ec0ec2a397265d1b2fb053aa3758382e09" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "lure.dat", "destination": "scummvm/extra/lure.dat", @@ -2235,7 +2235,7 @@ "new": "6f90cf73f36453d332d5c9cee311f81c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "lure.dat", "destination": "scummvm/extra/lure.dat", @@ -2244,7 +2244,7 @@ "new": "d616bea5" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "lure.dat", "destination": "scummvm/extra/lure.dat", @@ -2253,7 +2253,7 @@ "new": 768832 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "macventure.dat", "destination": "scummvm/extra/macventure.dat", @@ -2262,7 +2262,7 @@ "new": "e431e718e3c784532422ea2654d5fc6042a95c09" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "macventure.dat", "destination": "scummvm/extra/macventure.dat", @@ -2271,7 +2271,7 @@ "new": "3b1faf40e685b955372e02f54ed70263" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "macventure.dat", "destination": "scummvm/extra/macventure.dat", @@ -2280,7 +2280,7 @@ "new": "24a09690" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "macventure.dat", "destination": "scummvm/extra/macventure.dat", @@ -2289,7 +2289,7 @@ "new": 4146 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "mm.dat", "destination": "scummvm/extra/mm.dat", @@ -2298,7 +2298,7 @@ "new": "0816fe29b57911b28cbf774348f5492062326a85" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "mm.dat", "destination": "scummvm/extra/mm.dat", @@ -2307,7 +2307,7 @@ "new": "01f074a6ac34dd86fcc6d1e8090614f0" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "mm.dat", "destination": "scummvm/extra/mm.dat", @@ -2316,7 +2316,7 @@ "new": "f007671e" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "mm.dat", "destination": "scummvm/extra/mm.dat", @@ -2325,7 +2325,7 @@ "new": 280803 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "nancy.dat", "destination": "scummvm/extra/nancy.dat", @@ -2334,7 +2334,7 @@ "new": "7c8907d33da59afd4e4b62eee67541910e0b78d9" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "nancy.dat", "destination": "scummvm/extra/nancy.dat", @@ -2343,7 +2343,7 @@ "new": "eb7cfdb3f74972beb8f43196e4e44001" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "nancy.dat", "destination": "scummvm/extra/nancy.dat", @@ -2352,7 +2352,7 @@ "new": "d7976af8" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "nancy.dat", "destination": "scummvm/extra/nancy.dat", @@ -2361,7 +2361,7 @@ "new": 255042 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "queen.tbl", "destination": "scummvm/extra/queen.tbl", @@ -2370,7 +2370,7 @@ "new": "7bafa28dfd9b3be17430d467d8787d8204437ba0" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "queen.tbl", "destination": "scummvm/extra/queen.tbl", @@ -2379,7 +2379,7 @@ "new": "93c6d6379eb0290ce93854a6888ec3f4" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "queen.tbl", "destination": "scummvm/extra/queen.tbl", @@ -2388,7 +2388,7 @@ "new": "d713b1ab" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "queen.tbl", "destination": "scummvm/extra/queen.tbl", @@ -2397,7 +2397,7 @@ "new": 1100505 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "supernova.dat", "destination": "scummvm/extra/supernova.dat", @@ -2406,7 +2406,7 @@ "new": "8a571cfc431b5fe794f0262f0e55f72927ab27b4" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "supernova.dat", "destination": "scummvm/extra/supernova.dat", @@ -2415,7 +2415,7 @@ "new": "89664136c503a30894d5c713be5bfe90" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "supernova.dat", "destination": "scummvm/extra/supernova.dat", @@ -2424,7 +2424,7 @@ "new": "9643ad1a" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "supernova.dat", "destination": "scummvm/extra/supernova.dat", @@ -2433,7 +2433,7 @@ "new": 523211 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "teenagent.dat", "destination": "scummvm/extra/teenagent.dat", @@ -2442,7 +2442,7 @@ "new": "0d488164930332bae3aa1ad38cef44abbe3d00a6" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "teenagent.dat", "destination": "scummvm/extra/teenagent.dat", @@ -2451,7 +2451,7 @@ "new": "4594620989a0bfd8b120da2de7ac30de" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "teenagent.dat", "destination": "scummvm/extra/teenagent.dat", @@ -2460,7 +2460,7 @@ "new": "90b9e752" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "teenagent.dat", "destination": "scummvm/extra/teenagent.dat", @@ -2469,7 +2469,7 @@ "new": 403315 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "toon.dat", "destination": "scummvm/extra/toon.dat", @@ -2478,7 +2478,7 @@ "new": "85bb688125d66f2797a41f153ce1fa177027a83f" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "toon.dat", "destination": "scummvm/extra/toon.dat", @@ -2487,7 +2487,7 @@ "new": "2b71a5d9aa19219d46711e8a77451f0f" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "toon.dat", "destination": "scummvm/extra/toon.dat", @@ -2496,7 +2496,7 @@ "new": "7731190d" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "toon.dat", "destination": "scummvm/extra/toon.dat", @@ -2505,7 +2505,7 @@ "new": 20996 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "ultima.dat", "destination": "scummvm/extra/ultima.dat", @@ -2514,7 +2514,7 @@ "new": "e049932518390257f4d49b1691cad229e6060079" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "ultima.dat", "destination": "scummvm/extra/ultima.dat", @@ -2523,7 +2523,7 @@ "new": "e359fb7f14c4cb072b6e9300a23a6d62" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "ultima.dat", "destination": "scummvm/extra/ultima.dat", @@ -2532,7 +2532,7 @@ "new": "c15f9059" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "ultima.dat", "destination": "scummvm/extra/ultima.dat", @@ -2541,7 +2541,7 @@ "new": 15981464 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "wintermute.zip", "destination": "scummvm/extra/wintermute.zip", @@ -2550,7 +2550,7 @@ "new": "5302f543384b21fb55466118a6966e5ef46b85f6" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "wintermute.zip", "destination": "scummvm/extra/wintermute.zip", @@ -2559,7 +2559,7 @@ "new": "9783248899f9cb531f0a3e0a711ab8b3" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "wintermute.zip", "destination": "scummvm/extra/wintermute.zip", @@ -2568,7 +2568,7 @@ "new": "f7dc9bc4" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "wintermute.zip", "destination": "scummvm/extra/wintermute.zip", @@ -2577,7 +2577,7 @@ "new": 4520 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "gui-icons.dat", "destination": "scummvm/theme/gui-icons.dat", @@ -2586,7 +2586,7 @@ "new": "38e74e6994924781487a0d39cc53dc4708a90c1c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "gui-icons.dat", "destination": "scummvm/theme/gui-icons.dat", @@ -2595,7 +2595,7 @@ "new": "9c59e0e9875434456ec1b7eb8e51666f" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "gui-icons.dat", "destination": "scummvm/theme/gui-icons.dat", @@ -2604,7 +2604,7 @@ "new": "0aa0c87d" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "gui-icons.dat", "destination": "scummvm/theme/gui-icons.dat", @@ -2613,7 +2613,7 @@ "new": 3315855 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "residualvm.zip", "destination": "scummvm/theme/residualvm.zip", @@ -2622,7 +2622,7 @@ "new": "589b622f55c9944c4883e4163d4d49508e22e056" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "residualvm.zip", "destination": "scummvm/theme/residualvm.zip", @@ -2631,7 +2631,7 @@ "new": "b2811fa9b209733b61cd49ddb21e1e95" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "residualvm.zip", "destination": "scummvm/theme/residualvm.zip", @@ -2640,7 +2640,7 @@ "new": "93296840" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "residualvm.zip", "destination": "scummvm/theme/residualvm.zip", @@ -2649,7 +2649,7 @@ "new": 94555 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummclassic.zip", "destination": "scummvm/theme/scummclassic.zip", @@ -2658,7 +2658,7 @@ "new": "69836fb392710f2182df07bab8960e8a2d92c1a9" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummclassic.zip", "destination": "scummvm/theme/scummclassic.zip", @@ -2667,7 +2667,7 @@ "new": "8f37bd19dd51be456ac8f005fd5669a8" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummclassic.zip", "destination": "scummvm/theme/scummclassic.zip", @@ -2676,7 +2676,7 @@ "new": "103168ea" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "scummclassic.zip", "destination": "scummvm/theme/scummclassic.zip", @@ -2685,7 +2685,7 @@ "new": 16488 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummmodern.zip", "destination": "scummvm/theme/scummmodern.zip", @@ -2694,7 +2694,7 @@ "new": "aa2dfd12c11c6e6f3baaf7bf199bd6bcd71a520c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummmodern.zip", "destination": "scummvm/theme/scummmodern.zip", @@ -2703,7 +2703,7 @@ "new": "ee71d6cd464c618c632e6fd62b430d2c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummmodern.zip", "destination": "scummvm/theme/scummmodern.zip", @@ -2712,7 +2712,7 @@ "new": "00194297" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "scummmodern.zip", "destination": "scummvm/theme/scummmodern.zip", @@ -2721,7 +2721,7 @@ "new": 68087 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummremastered.zip", "destination": "scummvm/theme/scummremastered.zip", @@ -2730,7 +2730,7 @@ "new": "03e53349eefc39ce25220bfcb1ae4a580704d29d" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummremastered.zip", "destination": "scummvm/theme/scummremastered.zip", @@ -2739,7 +2739,7 @@ "new": "0d990484d0dfc1680b20cfabcae373cd" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummremastered.zip", "destination": "scummvm/theme/scummremastered.zip", @@ -2748,7 +2748,7 @@ "new": "6fa86fae" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "scummremastered.zip", "destination": "scummvm/theme/scummremastered.zip", @@ -2757,7 +2757,7 @@ "new": 93901 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "translations.dat", "destination": "scummvm/theme/translations.dat", @@ -2766,7 +2766,7 @@ "new": "e5b6f6d6473e2d6aa0a250fd9983b573ebf6fbe4" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "translations.dat", "destination": "scummvm/theme/translations.dat", @@ -2775,7 +2775,7 @@ "new": "a206e54bac1af13559ac8b2d9e550577" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "translations.dat", "destination": "scummvm/theme/translations.dat", @@ -2784,7 +2784,7 @@ "new": "14689714" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "translations.dat", "destination": "scummvm/theme/translations.dat", @@ -2793,7 +2793,7 @@ "new": 3693914 } ], - "hashes_corrected": [ + "hashes_updated": [ "Arcade/aes.zip sha1", "Arcade/aes.zip md5", "Arcade/aes.zip crc32", @@ -2875,7 +2875,7 @@ "ScummVM/translations.dat crc32" ], "hashes_filled": [], - "sizes_corrected": [ + "sizes_updated": [ "Arcade/aes.zip", "ScummVM/access.dat", "ScummVM/cryomni3d.dat", @@ -2908,7 +2908,7 @@ "SNK - NeoGeo CD/neocdz.zip sha1", "SNK - NeoGeo CD/neocdz.zip crc32" ], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 97, "removal_candidates": [ diff --git a/exports/misterfpga.json b/exports/misterfpga.json index 6b9bf906c..85cbbd0ac 100644 --- a/exports/misterfpga.json +++ b/exports/misterfpga.json @@ -15,12 +15,12 @@ "not_kept": 0 }, "changes": [], - "hashes_corrected": [], + "hashes_updated": [], "hashes_filled": [], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 0, "removal_candidates": [], diff --git a/exports/recalbox.json b/exports/recalbox.json index 3297c413d..c5d016716 100644 --- a/exports/recalbox.json +++ b/exports/recalbox.json @@ -16,7 +16,7 @@ }, "changes": [ { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "amiga600", "name": "kick34005.A500", "destination": "amiga/bios/kick34005.A500", @@ -25,7 +25,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "amiga600", "name": "kick37175.A500", "destination": "amiga/bios/kick37175.A500", @@ -298,7 +298,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "amiga1200", "name": "kick40068.A4000", "destination": "amiga/bios/kick40068.A4000", @@ -307,7 +307,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "amigacd32", "name": "kick40060.CD32.ext", "destination": "amiga/bios/kick40060.CD32.ext", @@ -316,7 +316,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "macintosh", "name": "MinivMacBootv2.dsk", "destination": "macintosh/MinivMacBootv2.dsk", @@ -325,7 +325,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "atari800", "name": "ATARIBAS.ROM", "destination": "atari800/ATARIBAS.ROM", @@ -334,7 +334,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "atari800", "name": "ATARIOSA.ROM", "destination": "atari800/ATARIOSA.ROM", @@ -343,7 +343,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "atari800", "name": "ATARIOSB.ROM", "destination": "atari800/ATARIOSB.ROM", @@ -352,7 +352,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "atari800", "name": "ATARIXL.ROM", "destination": "atari800/ATARIXL.ROM", @@ -361,7 +361,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "bk", "name": "B11M_BOS.ROM", "destination": "bk/B11M_BOS.ROM", @@ -370,7 +370,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "bk", "name": "B11M_EXT.ROM", "destination": "bk/B11M_EXT.ROM", @@ -379,7 +379,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "bk", "name": "BAS11M_0.ROM", "destination": "bk/BAS11M_0.ROM", @@ -388,7 +388,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "bk", "name": "BAS11M_1.ROM", "destination": "bk/BAS11M_1.ROM", @@ -397,7 +397,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "bk", "name": "DISK_327.ROM", "destination": "bk/DISK_327.ROM", @@ -406,7 +406,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "bk", "name": "FOCAL10.ROM", "destination": "bk/FOCAL10.ROM", @@ -643,7 +643,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "dreamcast", "name": "dc_boot.bin", "destination": "dc/dc_boot.bin", @@ -664,7 +664,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "pc88", "name": "n88_1.rom", "destination": "quasi88/n88_1.rom", @@ -673,7 +673,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "pc88", "name": "n88_2.rom", "destination": "quasi88/n88_2.rom", @@ -682,7 +682,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "pc88", "name": "n88_3.rom", "destination": "quasi88/n88_3.rom", @@ -691,7 +691,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "pc88", "name": "n88n.rom", "destination": "quasi88/n88n.rom", @@ -700,7 +700,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "pc98", "name": "bios.rom", "destination": "np2kai/bios.rom", @@ -709,7 +709,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "pc98", "name": "font.bmp", "destination": "np2kai/font.bmp", @@ -718,7 +718,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "pc98", "name": "sound.rom", "destination": "np2kai/sound.rom", @@ -739,7 +739,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "sufami", "name": "STBIOS.bin", "destination": "sufami/STBIOS.bin", @@ -748,7 +748,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2", "name": "CARTS.SHA", "destination": "msx/CARTS.SHA", @@ -757,7 +757,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2", "name": "CYRILLIC.FNT", "destination": "msx/CYRILLIC.FNT", @@ -766,7 +766,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2", "name": "DISK.ROM", "destination": "msx/DISK.ROM", @@ -775,7 +775,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2", "name": "FMPAC.ROM", "destination": "msx/FMPAC.ROM", @@ -784,7 +784,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2", "name": "ITALIC.FNT", "destination": "msx/ITALIC.FNT", @@ -793,7 +793,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2", "name": "KANJI.ROM", "destination": "msx/KANJI.ROM", @@ -802,7 +802,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2", "name": "MSXDOS2.ROM", "destination": "msx/MSXDOS2.ROM", @@ -811,7 +811,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2", "name": "PAINTER.ROM", "destination": "msx/PAINTER.ROM", @@ -820,7 +820,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2", "name": "RS232.ROM", "destination": "msx/RS232.ROM", @@ -829,7 +829,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "pesadelo.rom", "destination": "Machines/Forte II Games - Pesadelo/pesadelo.rom", @@ -838,7 +838,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "cbios_logo_msx1.rom", "destination": "Machines/MSX - C-BIOS/cbios_logo_msx1.rom", @@ -847,7 +847,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "cbios_main_msx1.rom", "destination": "Machines/MSX - C-BIOS/cbios_main_msx1.rom", @@ -856,7 +856,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "cbios_logo_msx2.rom", "destination": "Machines/MSX2 - C-BIOS/cbios_logo_msx2.rom", @@ -865,7 +865,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "cbios_main_msx2.rom", "destination": "Machines/MSX2 - C-BIOS/cbios_main_msx2.rom", @@ -874,7 +874,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "cbios_sub.rom", "destination": "Machines/MSX2 - C-BIOS/cbios_sub.rom", @@ -883,7 +883,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "cbios_logo_msx2+.rom", "destination": "Machines/MSX2+ - C-BIOS/cbios_logo_msx2+.rom", @@ -892,7 +892,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "cbios_main_msx2+.rom", "destination": "Machines/MSX2+ - C-BIOS/cbios_main_msx2+.rom", @@ -901,7 +901,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "cbios_music.rom", "destination": "Machines/MSX2+ - C-BIOS/cbios_music.rom", @@ -910,7 +910,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "Msx2pe.rom", "destination": "Machines/MSX2+ - European/Msx2pe.rom", @@ -919,7 +919,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "Msx2pexte.rom", "destination": "Machines/MSX2+ - European/Msx2pexte.rom", @@ -928,7 +928,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "ARAB1.ROM", "destination": "Machines/Shared Roms/ARAB1.ROM", @@ -937,7 +937,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "ARABIC.rom", "destination": "Machines/Shared Roms/ARABIC.rom", @@ -946,7 +946,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "BEERIDE.ROM", "destination": "Machines/Shared Roms/BEERIDE.ROM", @@ -955,7 +955,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "FMPAC.rom", "destination": "Machines/Shared Roms/FMPAC.rom", @@ -964,7 +964,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "GCVMX80.ROM", "destination": "Machines/Shared Roms/GCVMX80.ROM", @@ -973,7 +973,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "HANGUL.rom", "destination": "Machines/Shared Roms/HANGUL.rom", @@ -982,7 +982,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "KANJI.rom", "destination": "Machines/Shared Roms/KANJI.rom", @@ -991,7 +991,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MICROSOLDISK.ROM", "destination": "Machines/Shared Roms/MICROSOLDISK.ROM", @@ -1000,7 +1000,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MOONSOUND.rom", "destination": "Machines/Shared Roms/MOONSOUND.rom", @@ -1009,7 +1009,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2AR.ROM", "destination": "Machines/Shared Roms/MSX2AR.ROM", @@ -1018,7 +1018,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2AREXT.ROM", "destination": "Machines/Shared Roms/MSX2AREXT.ROM", @@ -1027,7 +1027,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2BR.rom", "destination": "Machines/Shared Roms/MSX2BR.rom", @@ -1036,7 +1036,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2BREXT.rom", "destination": "Machines/Shared Roms/MSX2BREXT.rom", @@ -1045,7 +1045,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2FR.rom", "destination": "Machines/Shared Roms/MSX2FR.rom", @@ -1054,7 +1054,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2FREXT.rom", "destination": "Machines/Shared Roms/MSX2FREXT.rom", @@ -1063,7 +1063,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2G.rom", "destination": "Machines/Shared Roms/MSX2G.rom", @@ -1072,7 +1072,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2GEXT.rom", "destination": "Machines/Shared Roms/MSX2GEXT.rom", @@ -1081,7 +1081,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2HAN.rom", "destination": "Machines/Shared Roms/MSX2HAN.rom", @@ -1090,7 +1090,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2J.rom", "destination": "Machines/Shared Roms/MSX2J.rom", @@ -1099,7 +1099,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2JEXT.rom", "destination": "Machines/Shared Roms/MSX2JEXT.rom", @@ -1108,7 +1108,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2KR.rom", "destination": "Machines/Shared Roms/MSX2KR.rom", @@ -1117,7 +1117,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2KREXT.rom", "destination": "Machines/Shared Roms/MSX2KREXT.rom", @@ -1126,7 +1126,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2R.rom", "destination": "Machines/Shared Roms/MSX2R.rom", @@ -1135,7 +1135,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2R2.ROM", "destination": "Machines/Shared Roms/MSX2R2.ROM", @@ -1144,7 +1144,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2REXT.rom", "destination": "Machines/Shared Roms/MSX2REXT.rom", @@ -1153,7 +1153,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2SE.rom", "destination": "Machines/Shared Roms/MSX2SE.rom", @@ -1162,7 +1162,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2SP.rom", "destination": "Machines/Shared Roms/MSX2SP.rom", @@ -1171,7 +1171,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSX2SPEXT.rom", "destination": "Machines/Shared Roms/MSX2SPEXT.rom", @@ -1180,7 +1180,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXAR.ROM", "destination": "Machines/Shared Roms/MSXAR.ROM", @@ -1189,7 +1189,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXBR.rom", "destination": "Machines/Shared Roms/MSXBR.rom", @@ -1198,7 +1198,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXFR.rom", "destination": "Machines/Shared Roms/MSXFR.rom", @@ -1207,7 +1207,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXG.rom", "destination": "Machines/Shared Roms/MSXG.rom", @@ -1216,7 +1216,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXHAN.rom", "destination": "Machines/Shared Roms/MSXHAN.rom", @@ -1225,7 +1225,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXJ.rom", "destination": "Machines/Shared Roms/MSXJ.rom", @@ -1234,7 +1234,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXKANJI.rom", "destination": "Machines/Shared Roms/MSXKANJI.rom", @@ -1243,7 +1243,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXKR.rom", "destination": "Machines/Shared Roms/MSXKR.rom", @@ -1252,7 +1252,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXR.rom", "destination": "Machines/Shared Roms/MSXR.rom", @@ -1261,7 +1261,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXR2.ROM", "destination": "Machines/Shared Roms/MSXR2.ROM", @@ -1270,7 +1270,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXSE.ROM", "destination": "Machines/Shared Roms/MSXSE.ROM", @@ -1279,7 +1279,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "MSXSP.rom", "destination": "Machines/Shared Roms/MSXSP.rom", @@ -1288,7 +1288,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "NATIONALDISK.rom", "destination": "Machines/Shared Roms/NATIONALDISK.rom", @@ -1297,7 +1297,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "NOVAXIS.rom", "destination": "Machines/Shared Roms/NOVAXIS.rom", @@ -1306,7 +1306,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "PAINT.rom", "destination": "Machines/Shared Roms/PAINT.rom", @@ -1315,7 +1315,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "PANASONICDISK.rom", "destination": "Machines/Shared Roms/PANASONICDISK.rom", @@ -1324,7 +1324,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "PHILIPSDISK.rom", "destination": "Machines/Shared Roms/PHILIPSDISK.rom", @@ -1333,7 +1333,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "RS232.ROM", "destination": "Machines/Shared Roms/RS232.ROM", @@ -1342,7 +1342,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "SUNRISEIDE.rom", "destination": "Machines/Shared Roms/SUNRISEIDE.rom", @@ -1351,7 +1351,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "SWP.rom", "destination": "Machines/Shared Roms/SWP.rom", @@ -1360,7 +1360,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "XBASIC2.rom", "destination": "Machines/Shared Roms/XBASIC2.rom", @@ -1369,7 +1369,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "nowindDos1.rom", "destination": "Machines/Shared Roms/nowindDos1.rom", @@ -1378,7 +1378,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx1,msx2,msxturbor", "name": "nowindDos2.rom", "destination": "Machines/Shared Roms/nowindDos2.rom", @@ -1387,7 +1387,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "lynx", "name": "lynxboot.img", "destination": "lynx/lynxboot.img", @@ -1396,7 +1396,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "atarist", "name": "tos.img", "destination": "atarist/tos.img", @@ -1441,7 +1441,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "o2em", "name": "c52.bin", "destination": "o2em/c52.bin", @@ -1450,7 +1450,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "videopacplus", "name": "g7400.bin", "destination": "o2em/g7400.bin", @@ -1459,7 +1459,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "videopacplus", "name": "jopac.bin", "destination": "o2em/jopac.bin", @@ -1468,7 +1468,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "3do", "name": "panafz1.bin", "destination": "3do/panafz1.bin", @@ -1477,7 +1477,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "3do", "name": "panafz1j.bin", "destination": "3do/panafz1j.bin", @@ -1486,7 +1486,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "3do", "name": "panafz1j-norsa.bin", "destination": "3do/panafz1j-norsa.bin", @@ -1507,7 +1507,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "x1", "name": "IPLROM.X1", "destination": "xmil/IPLROM.X1", @@ -1516,7 +1516,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "x1", "name": "IPLROM.X1T", "destination": "xmil/IPLROM.X1T", @@ -1525,7 +1525,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "psx", "name": "scph101.bin", "destination": "scph101.bin", @@ -1534,7 +1534,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "psx", "name": "scph7001.bin", "destination": "scph7001.bin", @@ -1543,7 +1543,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "psx", "name": "scph1001.bin", "destination": "scph1001.bin", @@ -1576,7 +1576,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "nds", "name": "bios7.bin", "destination": "nds/bios7.bin", @@ -1585,7 +1585,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "nds", "name": "bios9.bin", "destination": "nds/bios9.bin", @@ -1594,7 +1594,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "nds", "name": "firmware.bin", "destination": "nds/firmware.bin", @@ -1603,7 +1603,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "oricatmos", "name": "jasmin.rom", "destination": "oricutron/jasmin.rom", @@ -1612,7 +1612,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "oricatmos", "name": "microdis.rom", "destination": "oricutron/microdis.rom", @@ -1621,7 +1621,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "gb,gbc", "name": "SGB1.sfc", "destination": "sgb/SGB1.sfc", @@ -1630,7 +1630,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "gb,gbc", "name": "SGB2.sfc", "destination": "sgb/SGB2.sfc", @@ -1903,7 +1903,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "64dd", "name": "IPL.n64", "destination": "Mupen64plus/IPL.n64", @@ -1912,7 +1912,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "saturn", "name": "mpr-18811-mx.ic1", "destination": "saturn/mpr-18811-mx.ic1", @@ -1921,7 +1921,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "saturn", "name": "mpr-19367-mx.ic1", "destination": "saturn/mpr-19367-mx.ic1", @@ -1930,7 +1930,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "neogeocd", "name": "neocd.bin", "destination": "neocd/neocd.bin", @@ -1939,7 +1939,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "neogeocd", "name": "neocd.zip", "destination": "geolith/neocd.zip", @@ -1948,7 +1948,7 @@ "new": false }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ti994a", "name": "TI-994A.ctg", "destination": "ti994a/TI-994A.ctg", @@ -1957,7 +1957,7 @@ "new": "fb1ca5c58357896a0be5d6725894845d" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ti994a", "name": "ti-disk.ctg", "destination": "ti994a/ti-disk.ctg", @@ -2002,7 +2002,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "dragon", "name": "d64rom2.rom", "destination": "dragon/d64rom2.rom", @@ -2011,7 +2011,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "dragon", "name": "d200rom1.rom", "destination": "dragon/d200rom1.rom", @@ -2080,7 +2080,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "trs80coco", "name": "bas11.rom", "destination": "trs80coco/bas11.rom", @@ -2089,7 +2089,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "trs80coco", "name": "extbas10.rom", "destination": "trs80coco/extbas10.rom", @@ -2098,7 +2098,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "trs80coco", "name": "mx1600bas.rom", "destination": "trs80coco/mx1600bas.rom", @@ -2107,7 +2107,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "trs80coco", "name": "mc10.rom", "destination": "trs80coco/mc10.rom", @@ -2116,7 +2116,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "trs80coco", "name": "coco3.rom", "destination": "trs80coco/coco3.rom", @@ -2125,7 +2125,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "trs80coco", "name": "coco3p.rom", "destination": "trs80coco/coco3p.rom", @@ -2218,15 +2218,15 @@ } } ], - "hashes_corrected": [ + "hashes_updated": [ "ti994a/TI-994A.ctg md5", "ti994a/ti-disk.ctg md5" ], "hashes_filled": [], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 128, + "required_aligned": 128, "withheld": [], "found_by_content": 103, "removal_candidates": [ diff --git a/exports/retroarch.json b/exports/retroarch.json index 29776aee0..4b00637be 100644 --- a/exports/retroarch.json +++ b/exports/retroarch.json @@ -29,7 +29,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Arcade", "name": "aes.zip", "destination": "aes.zip", @@ -38,7 +38,7 @@ "new": "23c66caa80b93199914fae246a5ea6e84db1be29" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Arcade", "name": "aes.zip", "destination": "aes.zip", @@ -47,7 +47,7 @@ "new": "ad9585c72130c56f04ae26aae87c289d" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Arcade", "name": "aes.zip", "destination": "aes.zip", @@ -56,7 +56,7 @@ "new": "12c66621" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "Arcade", "name": "aes.zip", "destination": "aes.zip", @@ -903,7 +903,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios7.bin", "destination": "dsi_bios7.bin", @@ -912,7 +912,7 @@ "new": "c7c7570bfe51c3c7c5da3b01331b94e7e7cb4f53" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios7.bin", "destination": "dsi_bios7.bin", @@ -921,7 +921,7 @@ "new": "322ceeafb77b0cc96a1fcc6271e0e0ae" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios7.bin", "destination": "dsi_bios7.bin", @@ -930,7 +930,7 @@ "new": "4316cc42" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios9.bin", "destination": "dsi_bios9.bin", @@ -939,7 +939,7 @@ "new": "719b9eef33692406d7170ff526069615759c4dfc" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios9.bin", "destination": "dsi_bios9.bin", @@ -948,7 +948,7 @@ "new": "23031f25602a52c2df32c39554b0c55e" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "Nintendo - Nintendo DSi", "name": "dsi_bios9.bin", "destination": "dsi_bios9.bin", @@ -971,7 +971,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "SNK - NeoGeo CD", "name": "neocdz.zip", "destination": "neocdz.zip", @@ -1979,7 +1979,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "access.dat", "destination": "scummvm/extra/access.dat", @@ -1988,7 +1988,7 @@ "new": "775b12b7e787bbaaeb73aa45b1ad3bb383e041ef" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "access.dat", "destination": "scummvm/extra/access.dat", @@ -1997,7 +1997,7 @@ "new": "4f9dd40b455876d5c54b5ebd93aec88c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "access.dat", "destination": "scummvm/extra/access.dat", @@ -2006,7 +2006,7 @@ "new": "f63ddcc5" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "access.dat", "destination": "scummvm/extra/access.dat", @@ -2015,7 +2015,7 @@ "new": 520352 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "cryomni3d.dat", "destination": "scummvm/extra/cryomni3d.dat", @@ -2024,7 +2024,7 @@ "new": "3971847e26be0abad8196caf03e5852a81ddbdea" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "cryomni3d.dat", "destination": "scummvm/extra/cryomni3d.dat", @@ -2033,7 +2033,7 @@ "new": "7224c7a94074addeb69015b1346695d2" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "cryomni3d.dat", "destination": "scummvm/extra/cryomni3d.dat", @@ -2042,7 +2042,7 @@ "new": "5956e0e9" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "cryomni3d.dat", "destination": "scummvm/extra/cryomni3d.dat", @@ -2051,7 +2051,7 @@ "new": 70416 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "drascula.dat", "destination": "scummvm/extra/drascula.dat", @@ -2060,7 +2060,7 @@ "new": "05c97e92439b67aea8f6eb7fc96db023edf62124" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "drascula.dat", "destination": "scummvm/extra/drascula.dat", @@ -2069,7 +2069,7 @@ "new": "b304979ed642a6dcaf26f43f8a37a1de" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "drascula.dat", "destination": "scummvm/extra/drascula.dat", @@ -2078,7 +2078,7 @@ "new": "b36f7b0e" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "drascula.dat", "destination": "scummvm/extra/drascula.dat", @@ -2087,7 +2087,7 @@ "new": 259625 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "fonts.dat", "destination": "scummvm/extra/fonts.dat", @@ -2096,7 +2096,7 @@ "new": "1eaef39122559500fbbc705c9425500311d23e03" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "fonts.dat", "destination": "scummvm/extra/fonts.dat", @@ -2105,7 +2105,7 @@ "new": "6df29d218962e155d0a2114006af45c0" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "fonts.dat", "destination": "scummvm/extra/fonts.dat", @@ -2114,7 +2114,7 @@ "new": "f59313fa" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "fonts.dat", "destination": "scummvm/extra/fonts.dat", @@ -2123,7 +2123,7 @@ "new": 6449829 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "freescape.dat", "destination": "scummvm/extra/freescape.dat", @@ -2132,7 +2132,7 @@ "new": "c6b3d3152e4e2ff4c9bf55e103bb39ed7faca2bd" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "freescape.dat", "destination": "scummvm/extra/freescape.dat", @@ -2141,7 +2141,7 @@ "new": "71697f90fb0c7747616dbb0799ce966b" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "freescape.dat", "destination": "scummvm/extra/freescape.dat", @@ -2150,7 +2150,7 @@ "new": "54f0350e" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "freescape.dat", "destination": "scummvm/extra/freescape.dat", @@ -2159,7 +2159,7 @@ "new": 30124 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "hugo.dat", "destination": "scummvm/extra/hugo.dat", @@ -2168,7 +2168,7 @@ "new": "74a5fa172f5327fedc145cb9eccc8fd050b9cfb9" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "hugo.dat", "destination": "scummvm/extra/hugo.dat", @@ -2177,7 +2177,7 @@ "new": "a865e70f1b0998a500f522dab441c8fc" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "hugo.dat", "destination": "scummvm/extra/hugo.dat", @@ -2186,7 +2186,7 @@ "new": "fc0dd1cb" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "hugo.dat", "destination": "scummvm/extra/hugo.dat", @@ -2195,7 +2195,7 @@ "new": 186866 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "kyra.dat", "destination": "scummvm/extra/kyra.dat", @@ -2204,7 +2204,7 @@ "new": "31b0b9418af2f9dd4e4094aada05d48eb355975b" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "kyra.dat", "destination": "scummvm/extra/kyra.dat", @@ -2213,7 +2213,7 @@ "new": "ebddd7bbd97135a427083db5daf6f9fc" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "kyra.dat", "destination": "scummvm/extra/kyra.dat", @@ -2222,7 +2222,7 @@ "new": "6b7229ce" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "kyra.dat", "destination": "scummvm/extra/kyra.dat", @@ -2231,7 +2231,7 @@ "new": 2028520 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "lure.dat", "destination": "scummvm/extra/lure.dat", @@ -2240,7 +2240,7 @@ "new": "516347ec0ec2a397265d1b2fb053aa3758382e09" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "lure.dat", "destination": "scummvm/extra/lure.dat", @@ -2249,7 +2249,7 @@ "new": "6f90cf73f36453d332d5c9cee311f81c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "lure.dat", "destination": "scummvm/extra/lure.dat", @@ -2258,7 +2258,7 @@ "new": "d616bea5" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "lure.dat", "destination": "scummvm/extra/lure.dat", @@ -2267,7 +2267,7 @@ "new": 768832 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "macventure.dat", "destination": "scummvm/extra/macventure.dat", @@ -2276,7 +2276,7 @@ "new": "e431e718e3c784532422ea2654d5fc6042a95c09" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "macventure.dat", "destination": "scummvm/extra/macventure.dat", @@ -2285,7 +2285,7 @@ "new": "3b1faf40e685b955372e02f54ed70263" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "macventure.dat", "destination": "scummvm/extra/macventure.dat", @@ -2294,7 +2294,7 @@ "new": "24a09690" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "macventure.dat", "destination": "scummvm/extra/macventure.dat", @@ -2303,7 +2303,7 @@ "new": 4146 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "mm.dat", "destination": "scummvm/extra/mm.dat", @@ -2312,7 +2312,7 @@ "new": "0816fe29b57911b28cbf774348f5492062326a85" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "mm.dat", "destination": "scummvm/extra/mm.dat", @@ -2321,7 +2321,7 @@ "new": "01f074a6ac34dd86fcc6d1e8090614f0" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "mm.dat", "destination": "scummvm/extra/mm.dat", @@ -2330,7 +2330,7 @@ "new": "f007671e" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "mm.dat", "destination": "scummvm/extra/mm.dat", @@ -2339,7 +2339,7 @@ "new": 280803 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "nancy.dat", "destination": "scummvm/extra/nancy.dat", @@ -2348,7 +2348,7 @@ "new": "7c8907d33da59afd4e4b62eee67541910e0b78d9" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "nancy.dat", "destination": "scummvm/extra/nancy.dat", @@ -2357,7 +2357,7 @@ "new": "eb7cfdb3f74972beb8f43196e4e44001" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "nancy.dat", "destination": "scummvm/extra/nancy.dat", @@ -2366,7 +2366,7 @@ "new": "d7976af8" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "nancy.dat", "destination": "scummvm/extra/nancy.dat", @@ -2375,7 +2375,7 @@ "new": 255042 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "queen.tbl", "destination": "scummvm/extra/queen.tbl", @@ -2384,7 +2384,7 @@ "new": "7bafa28dfd9b3be17430d467d8787d8204437ba0" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "queen.tbl", "destination": "scummvm/extra/queen.tbl", @@ -2393,7 +2393,7 @@ "new": "93c6d6379eb0290ce93854a6888ec3f4" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "queen.tbl", "destination": "scummvm/extra/queen.tbl", @@ -2402,7 +2402,7 @@ "new": "d713b1ab" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "queen.tbl", "destination": "scummvm/extra/queen.tbl", @@ -2411,7 +2411,7 @@ "new": 1100505 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "supernova.dat", "destination": "scummvm/extra/supernova.dat", @@ -2420,7 +2420,7 @@ "new": "8a571cfc431b5fe794f0262f0e55f72927ab27b4" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "supernova.dat", "destination": "scummvm/extra/supernova.dat", @@ -2429,7 +2429,7 @@ "new": "89664136c503a30894d5c713be5bfe90" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "supernova.dat", "destination": "scummvm/extra/supernova.dat", @@ -2438,7 +2438,7 @@ "new": "9643ad1a" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "supernova.dat", "destination": "scummvm/extra/supernova.dat", @@ -2447,7 +2447,7 @@ "new": 523211 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "teenagent.dat", "destination": "scummvm/extra/teenagent.dat", @@ -2456,7 +2456,7 @@ "new": "0d488164930332bae3aa1ad38cef44abbe3d00a6" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "teenagent.dat", "destination": "scummvm/extra/teenagent.dat", @@ -2465,7 +2465,7 @@ "new": "4594620989a0bfd8b120da2de7ac30de" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "teenagent.dat", "destination": "scummvm/extra/teenagent.dat", @@ -2474,7 +2474,7 @@ "new": "90b9e752" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "teenagent.dat", "destination": "scummvm/extra/teenagent.dat", @@ -2483,7 +2483,7 @@ "new": 403315 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "toon.dat", "destination": "scummvm/extra/toon.dat", @@ -2492,7 +2492,7 @@ "new": "85bb688125d66f2797a41f153ce1fa177027a83f" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "toon.dat", "destination": "scummvm/extra/toon.dat", @@ -2501,7 +2501,7 @@ "new": "2b71a5d9aa19219d46711e8a77451f0f" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "toon.dat", "destination": "scummvm/extra/toon.dat", @@ -2510,7 +2510,7 @@ "new": "7731190d" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "toon.dat", "destination": "scummvm/extra/toon.dat", @@ -2519,7 +2519,7 @@ "new": 20996 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "ultima.dat", "destination": "scummvm/extra/ultima.dat", @@ -2528,7 +2528,7 @@ "new": "e049932518390257f4d49b1691cad229e6060079" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "ultima.dat", "destination": "scummvm/extra/ultima.dat", @@ -2537,7 +2537,7 @@ "new": "e359fb7f14c4cb072b6e9300a23a6d62" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "ultima.dat", "destination": "scummvm/extra/ultima.dat", @@ -2546,7 +2546,7 @@ "new": "c15f9059" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "ultima.dat", "destination": "scummvm/extra/ultima.dat", @@ -2555,7 +2555,7 @@ "new": 15981464 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "wintermute.zip", "destination": "scummvm/extra/wintermute.zip", @@ -2564,7 +2564,7 @@ "new": "5302f543384b21fb55466118a6966e5ef46b85f6" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "wintermute.zip", "destination": "scummvm/extra/wintermute.zip", @@ -2573,7 +2573,7 @@ "new": "9783248899f9cb531f0a3e0a711ab8b3" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "wintermute.zip", "destination": "scummvm/extra/wintermute.zip", @@ -2582,7 +2582,7 @@ "new": "f7dc9bc4" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "wintermute.zip", "destination": "scummvm/extra/wintermute.zip", @@ -2591,7 +2591,7 @@ "new": 4520 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "gui-icons.dat", "destination": "scummvm/theme/gui-icons.dat", @@ -2600,7 +2600,7 @@ "new": "38e74e6994924781487a0d39cc53dc4708a90c1c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "gui-icons.dat", "destination": "scummvm/theme/gui-icons.dat", @@ -2609,7 +2609,7 @@ "new": "9c59e0e9875434456ec1b7eb8e51666f" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "gui-icons.dat", "destination": "scummvm/theme/gui-icons.dat", @@ -2618,7 +2618,7 @@ "new": "0aa0c87d" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "gui-icons.dat", "destination": "scummvm/theme/gui-icons.dat", @@ -2627,7 +2627,7 @@ "new": 3315855 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "residualvm.zip", "destination": "scummvm/theme/residualvm.zip", @@ -2636,7 +2636,7 @@ "new": "589b622f55c9944c4883e4163d4d49508e22e056" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "residualvm.zip", "destination": "scummvm/theme/residualvm.zip", @@ -2645,7 +2645,7 @@ "new": "b2811fa9b209733b61cd49ddb21e1e95" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "residualvm.zip", "destination": "scummvm/theme/residualvm.zip", @@ -2654,7 +2654,7 @@ "new": "93296840" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "residualvm.zip", "destination": "scummvm/theme/residualvm.zip", @@ -2663,7 +2663,7 @@ "new": 94555 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummclassic.zip", "destination": "scummvm/theme/scummclassic.zip", @@ -2672,7 +2672,7 @@ "new": "69836fb392710f2182df07bab8960e8a2d92c1a9" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummclassic.zip", "destination": "scummvm/theme/scummclassic.zip", @@ -2681,7 +2681,7 @@ "new": "8f37bd19dd51be456ac8f005fd5669a8" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummclassic.zip", "destination": "scummvm/theme/scummclassic.zip", @@ -2690,7 +2690,7 @@ "new": "103168ea" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "scummclassic.zip", "destination": "scummvm/theme/scummclassic.zip", @@ -2699,7 +2699,7 @@ "new": 16488 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummmodern.zip", "destination": "scummvm/theme/scummmodern.zip", @@ -2708,7 +2708,7 @@ "new": "aa2dfd12c11c6e6f3baaf7bf199bd6bcd71a520c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummmodern.zip", "destination": "scummvm/theme/scummmodern.zip", @@ -2717,7 +2717,7 @@ "new": "ee71d6cd464c618c632e6fd62b430d2c" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummmodern.zip", "destination": "scummvm/theme/scummmodern.zip", @@ -2726,7 +2726,7 @@ "new": "00194297" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "scummmodern.zip", "destination": "scummvm/theme/scummmodern.zip", @@ -2735,7 +2735,7 @@ "new": 68087 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummremastered.zip", "destination": "scummvm/theme/scummremastered.zip", @@ -2744,7 +2744,7 @@ "new": "03e53349eefc39ce25220bfcb1ae4a580704d29d" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummremastered.zip", "destination": "scummvm/theme/scummremastered.zip", @@ -2753,7 +2753,7 @@ "new": "0d990484d0dfc1680b20cfabcae373cd" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "scummremastered.zip", "destination": "scummvm/theme/scummremastered.zip", @@ -2762,7 +2762,7 @@ "new": "6fa86fae" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "scummremastered.zip", "destination": "scummvm/theme/scummremastered.zip", @@ -2771,7 +2771,7 @@ "new": 93901 }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "translations.dat", "destination": "scummvm/theme/translations.dat", @@ -2780,7 +2780,7 @@ "new": "e5b6f6d6473e2d6aa0a250fd9983b573ebf6fbe4" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "translations.dat", "destination": "scummvm/theme/translations.dat", @@ -2789,7 +2789,7 @@ "new": "a206e54bac1af13559ac8b2d9e550577" }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "ScummVM", "name": "translations.dat", "destination": "scummvm/theme/translations.dat", @@ -2798,7 +2798,7 @@ "new": "14689714" }, { - "kind": "size_corrected", + "kind": "size_updated", "system": "ScummVM", "name": "translations.dat", "destination": "scummvm/theme/translations.dat", @@ -2947,7 +2947,7 @@ } } ], - "hashes_corrected": [ + "hashes_updated": [ "Arcade/aes.zip sha1", "Arcade/aes.zip md5", "Arcade/aes.zip crc32", @@ -3029,7 +3029,7 @@ "ScummVM/translations.dat crc32" ], "hashes_filled": [], - "sizes_corrected": [ + "sizes_updated": [ "Arcade/aes.zip", "ScummVM/access.dat", "ScummVM/cryomni3d.dat", @@ -3062,7 +3062,7 @@ "SNK - NeoGeo CD/neocdz.zip sha1", "SNK - NeoGeo CD/neocdz.zip crc32" ], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 97, "removal_candidates": [ diff --git a/exports/retrobat.json b/exports/retrobat.json index f39297df9..fbdad54a2 100644 --- a/exports/retrobat.json +++ b/exports/retrobat.json @@ -662,7 +662,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "dragon32", "name": "d64_1.rom", "destination": "dragon/d64_1.rom", @@ -766,7 +766,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "neogeocd", "name": "neocdz.zip", "destination": "neocdz.zip", @@ -885,7 +885,7 @@ } } ], - "hashes_corrected": [ + "hashes_updated": [ "dragon32/d64_1.rom md5", "neogeocd/neocdz.zip md5" ], @@ -894,10 +894,10 @@ "nds/dsi_bios9.bin md5", "nds/dsi_bios7.bin md5" ], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 420, "removal_candidates": [ diff --git a/exports/retrodeck.json b/exports/retrodeck.json index bdf93693b..685d3911b 100644 --- a/exports/retrodeck.json +++ b/exports/retrodeck.json @@ -379,7 +379,7 @@ "new": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "mame", "name": "p1_rom.zip", "destination": "bios/p1_rom.zip", @@ -388,7 +388,7 @@ "new": false }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "fbneo", "name": "neocdz.zip", "destination": "bios/neocdz.zip", @@ -408,7 +408,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "nds", "name": "bios7.bin", "destination": "bios/bios7.bin", @@ -417,7 +417,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "nds", "name": "bios9.bin", "destination": "bios/bios9.bin", @@ -503,7 +503,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "pico8", "name": "pico8_dyn", "destination": "bios/pico-8/pico8_dyn", @@ -649,7 +649,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "amiga", "name": "kick34005.A500", "destination": "bios/kick34005.A500", @@ -658,7 +658,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "amiga", "name": "kick40068.A1200", "destination": "bios/kick40068.A1200", @@ -667,7 +667,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "amiga", "name": "kick34005.CDTV", "destination": "bios/kick34005.CDTV", @@ -676,7 +676,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "amiga", "name": "kick40060.CD32", "destination": "bios/kick40060.CD32", @@ -685,7 +685,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "amiga", "name": "kick40060.CD32.ext", "destination": "bios/kick40060.CD32.ext", @@ -730,7 +730,7 @@ "new": "4b5ed8cb2472013c19021fea99352fee6a50a116295cc38d4c040514c2eb39f1" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "atarixe", "name": "xegs.zip", "destination": "bios/xegs.zip", @@ -739,7 +739,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "atari5200", "name": "ATARIXL.ROM", "destination": "bios/ATARIXL.ROM", @@ -748,7 +748,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "atari5200", "name": "ATARIBAS.ROM", "destination": "bios/ATARIBAS.ROM", @@ -757,7 +757,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "atari5200", "name": "ATARIOSA.ROM", "destination": "bios/ATARIOSA.ROM", @@ -953,7 +953,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "apple2", "name": "a2diskiing.zip", "destination": "bios/a2diskiing.zip", @@ -962,7 +962,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "apple2", "name": "apple2e.zip", "destination": "bios/apple2e.zip", @@ -971,7 +971,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "apple2", "name": "d2fdc.zip", "destination": "bios/d2fdc.zip", @@ -980,7 +980,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "apple2", "name": "apple2gs.zip", "destination": "bios/apple2gs.zip", @@ -989,7 +989,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "cdimono1", "name": "cdibios.zip", "destination": "bios/cdibios.zip", @@ -1186,7 +1186,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "gamecom", "name": "gamecom.zip", "destination": "bios/gamecom.zip", @@ -1206,7 +1206,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "gmaster", "name": "gmaster.zip", "destination": "bios/gmaster.zip", @@ -1226,7 +1226,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "neogeo", "name": "aes.zip", "destination": "bios/aes.zip", @@ -1253,7 +1253,7 @@ "new": "1d39368f8dd5c2c65be1b872e749f7e20fe5707f9897deefe54a82d11d4748c7" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "ngp", "name": "ngp.zip", "destination": "bios/ngp.zip", @@ -1262,7 +1262,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "odyssey2", "name": "c52.bin", "destination": "bios/c52.bin", @@ -1271,7 +1271,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx", "name": "DISK.ROM", "destination": "bios/DISK.ROM", @@ -1289,7 +1289,7 @@ "new": "31740a761447d7590c304338d389b0ae114546a8b9f79c9ffd5e374f80b7d354" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx", "name": "MSXDOS2.ROM", "destination": "bios/MSXDOS2.ROM", @@ -1325,7 +1325,7 @@ "new": "56785703b14f252ce77be586716dbf5d94125e93b9e6069ff26f7686566cd91a" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx", "name": "CARTS.SHA", "destination": "bios/CARTS.SHA", @@ -1343,7 +1343,7 @@ "new": "0a09940701f40647a2715271b43c9ab0d82bd488871427ad627b7e0f462f5dda" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "msx", "name": "RS232.ROM", "destination": "bios/RS232.ROM", @@ -1415,7 +1415,7 @@ "new": "728fbc6dd5f7532b526d1fa6e55bade10060954cf5412d23e61ceafaa1e6bf85" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "nes", "name": "disksys.rom", "destination": "bios/disksys.rom", @@ -1424,7 +1424,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "sega32x", "name": "32X_M_BIOS.BIN", "destination": "bios/32X_M_BIOS.BIN", @@ -1433,7 +1433,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "sega32x", "name": "32X_S_BIOS.BIN", "destination": "bios/32X_S_BIOS.BIN", @@ -1442,7 +1442,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "dreamcast", "name": "dc_boot.bin", "destination": "bios/dc/dc_boot.bin", @@ -1451,7 +1451,7 @@ "new": false }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "dreamcast", "name": "dc_flash.bin", "destination": "bios/dc/dc_flash.bin", @@ -1478,7 +1478,7 @@ "new": "8c8a21335038285cfa03dc076100c1f0bfadf3e4ff70796f11f3dfaaab60eee2" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "colecovision", "name": "colecovision.rom", "destination": "bios/colecovision.rom", @@ -1487,7 +1487,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "colecovision", "name": "coleco.rom", "destination": "bios/coleco.rom", @@ -1677,7 +1677,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "snes", "name": "BS-X.bin", "destination": "bios/BS-X.bin", @@ -1686,7 +1686,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "snes", "name": "STBIOS.bin", "destination": "bios/STBIOS.bin", @@ -1731,7 +1731,7 @@ "new": "ea65e0fb44ee93ede4b6c507509b7e5ddf497fb7155023bea91ef229469fa04d" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "scv", "name": "upd7801g.s01", "destination": "bios/upd7801g.s01", @@ -1749,7 +1749,7 @@ "new": "248ec2f28294316558475ba0e416da19cfff0a5dd8ad3525a484a9be67b8a290" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "scv", "name": "scv.zip", "destination": "bios/scv.zip", @@ -1769,7 +1769,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "supracan", "name": "umc6650.zip", "destination": "bios/umc6650.zip", @@ -1778,7 +1778,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "supracan", "name": "supracan.zip", "destination": "bios/supracan.zip", @@ -1809,7 +1809,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "vsmile", "name": "vsmile.zip", "destination": "bios/vsmile.zip", @@ -1840,7 +1840,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "xbox", "name": "mcpx_1.0.bin", "destination": "bios/mcpx_1.0.bin", @@ -1849,7 +1849,7 @@ "new": true }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "xbox", "name": "Complex.bin", "destination": "bios/Complex.bin", @@ -1878,7 +1878,7 @@ "new": "fc0e900bfec6b52f0f80ba1e65a4712808d2a411b5b00496639ef1a2152351f1" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "tanodragon", "name": "d64tano.rom", "destination": "bios/d64tano.rom", @@ -1905,7 +1905,7 @@ "new": "c3d0b8fd92020c939ec97388402d10b3890e6e26455963d50cc00b5f9cd68fa5" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "tanodragon", "name": "d200rom1.rom", "destination": "bios/d200rom1.rom", @@ -1923,7 +1923,7 @@ "new": "0486c51d3b6f7b6b4bcdde661d54f076278fd6a4ee92cbf9d1bc791554e16bd7" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "tanodragon", "name": "ddos10.rom", "destination": "bios/ddos10.rom", @@ -1986,7 +1986,7 @@ "new": "09e8573e31effc16e9f1a177f46514ed9f332a6c5e4e46a550574bd969319c11" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "tanodragon", "name": "cp400bas.rom", "destination": "bios/cp400bas.rom", @@ -2069,7 +2069,7 @@ } }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "coco", "name": "cp400extbas.rom", "destination": "bios/cp400extbas.rom", @@ -2123,7 +2123,7 @@ "new": "5375b1caef9df815b781d9cd6df81ecdaddd2642b06a01adac521e40f1329751" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "coco", "name": "coco3.rom", "destination": "bios/coco3.rom", @@ -2150,7 +2150,7 @@ "new": "ccd347508f177cb3eeb2e17fd60d3cccb64312d069c5084771467cfbbc04a27c" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "coco", "name": "extbas11.rom", "destination": "bios/extbas11.rom", @@ -2177,7 +2177,7 @@ "new": "8c86c83720043525029797dd3defd0fd285651549a373117304cc470e8e61f91" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "coco", "name": "disk11.rom", "destination": "bios/disk11.rom", @@ -2195,7 +2195,7 @@ "new": "1c40c5a74e72edaeecc2511fee1aab347eae2c3d5fc6778cb622e3df14d63157" }, { - "kind": "requirement_corrected", + "kind": "required_aligned", "system": "coco", "name": "coco3p.rom", "destination": "bios/coco3p.rom", @@ -2265,7 +2265,7 @@ } } ], - "hashes_corrected": [ + "hashes_updated": [ "fbneo/neocdz.zip md5" ], "hashes_filled": [ @@ -2364,10 +2364,10 @@ "coco/disk11.rom sha256", "coco/coco3p.rom sha256" ], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 52, + "required_aligned": 52, "withheld": [], "found_by_content": 279, "removal_candidates": [ diff --git a/exports/retropie.json b/exports/retropie.json index 74bae1e10..1e7c61d32 100644 --- a/exports/retropie.json +++ b/exports/retropie.json @@ -40,12 +40,12 @@ "new": {} } ], - "hashes_corrected": [], + "hashes_updated": [], "hashes_filled": [], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 98, "removal_candidates": [ diff --git a/exports/rocknix.json b/exports/rocknix.json index b3b07add2..b367714ff 100644 --- a/exports/rocknix.json +++ b/exports/rocknix.json @@ -27,12 +27,12 @@ } } ], - "hashes_corrected": [], + "hashes_updated": [], "hashes_filled": [], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 132, "removal_candidates": [ diff --git a/exports/romm.json b/exports/romm.json index a0695b141..760b00df3 100644 --- a/exports/romm.json +++ b/exports/romm.json @@ -1316,7 +1316,7 @@ } }, { - "kind": "hash_corrected", + "kind": "hash_updated", "system": "doom", "name": "PLUTONIA.WAD", "destination": "doom/PLUTONIA.WAD", @@ -1325,14 +1325,14 @@ "new": "816c7c6b0098f66c299c9253f62bd908456efb63" } ], - "hashes_corrected": [ + "hashes_updated": [ "doom/PLUTONIA.WAD sha1" ], "hashes_filled": [], - "sizes_corrected": [], + "sizes_updated": [], "sizes_removed": [], "hashes_cleared": [], - "requirements_corrected": 0, + "required_aligned": 0, "withheld": [], "found_by_content": 0, "removal_candidates": [ diff --git a/scripts/auto_fetch.py b/scripts/auto_fetch.py index e7272524a..08d7b3afe 100644 --- a/scripts/auto_fetch.py +++ b/scripts/auto_fetch.py @@ -6,7 +6,8 @@ Pipeline: 2. Scan old branches (git show origin/branch:path) 3. Search public BIOS repos on GitHub 4. Search archive.org collections -5. Create GitHub Issue for community help +5. Print an issue body for the files still missing (--create-issues). + It posts nothing. Usage: python scripts/auto_fetch.py --platform retroarch [--dry-run] @@ -459,43 +460,37 @@ def fetch_missing( ) stats["errors"] += 1 continue - print(" [5] Not found - needs community contribution") + print(" [5] Not found, open for contributions") still_missing.append(entry) stats["not_found"] += 1 return {"stats": stats, "still_missing": still_missing} +CONTRIBUTING_URL = "https://github.com/Abdess/retrobios/blob/main/CONTRIBUTING.md" + + def generate_issue_body(missing: list[dict], platform: str) -> str: - """Generate a GitHub Issue body for missing BIOS files.""" + """An issue body for the files a platform still needs. + + The hashes are written in full: a contributor matches a file against + them, and a 12-character prefix confirms nothing. + """ lines = [ - f"## Missing BIOS Files for {platform}", - "", - "The following BIOS files are required but not available in the repository.", - "If you have any of these files, please submit a Pull Request!", + f"## Files {platform} still needs", "", "| File | System | SHA1 | MD5 |", "|------|--------|------|-----|", ] - for entry in missing: - sha1 = ", ".join(sorted(_accepted(entry, "sha1"))) or "N/A" - md5 = ", ".join(sorted(_accepted(entry, "md5"))) or "N/A" - lines.append( - f"| `{entry['name']}` | {entry['system']} | `{sha1[:12]}...` | `{md5[:12]}...` |" - ) - - lines.extend( - [ - "", - "### How to Contribute", - "", - "1. Fork this repository", - "2. Add the BIOS file to `bios/Manufacturer/Console/`", - "3. Create a Pull Request - checksums are verified automatically", - ] - ) - + sha1 = ", ".join(f"`{h}`" for h in sorted(_accepted(entry, "sha1"))) or "none declared" + md5 = ", ".join(f"`{h}`" for h in sorted(_accepted(entry, "md5"))) or "none declared" + lines.append(f"| `{entry['name']}` | {entry['system']} | {sha1} | {md5} |") + lines += [ + "", + "A file with one of these hashes can be added through the upload link in" + f" [CONTRIBUTING.md]({CONTRIBUTING_URL}). CI checks it on arrival.", + ] return "\n".join(lines) @@ -563,7 +558,7 @@ def main() -> None: result = fetch_missing(missing, db, args.bios_dir, args.dry_run) if result["still_missing"]: - all_still_missing[platform] = result["still_missing"] + all_still_missing[config.get("platform") or platform] = result["still_missing"] stats = result["stats"] print( diff --git a/scripts/commit_rules.py b/scripts/commit_rules.py index 356b3a871..1da6d7b1b 100644 --- a/scripts/commit_rules.py +++ b/scripts/commit_rules.py @@ -10,6 +10,7 @@ Usage as a hook: python scripts/commit_rules.py MESSAGE_FILE from __future__ import annotations +import argparse import re import sys from pathlib import Path @@ -51,10 +52,11 @@ def problems(message: str) -> list[str]: def main(argv: list[str]) -> int: - if len(argv) != 2: - print("usage: commit_rules.py MESSAGE_FILE", file=sys.stderr) - return 2 - found = problems(Path(argv[1]).read_text(encoding="utf-8", errors="replace")) + parser = argparse.ArgumentParser( + allow_abbrev=False, description="Check a commit message against the rules above") + parser.add_argument("message_file", type=Path, help="the message file a commit-msg hook receives") + args = parser.parse_args(argv[1:]) + found = problems(args.message_file.read_text(encoding="utf-8", errors="replace")) for problem in found: print(f"commit-msg: {problem}", file=sys.stderr) return 1 if found else 0 diff --git a/scripts/export_native.py b/scripts/export_native.py index 449b4c055..032ceb345 100644 --- a/scripts/export_native.py +++ b/scripts/export_native.py @@ -515,7 +515,7 @@ class LandedChanges: @property def applied(self) -> list[str]: - return self._labels("hash_corrected") + return self._labels("hash_updated") @property def filled(self) -> list[str]: @@ -527,7 +527,7 @@ class LandedChanges: @property def resized(self) -> list[str]: - return [label.removesuffix(" size") for label in self._labels("size_corrected")] + return [label.removesuffix(" size") for label in self._labels("size_updated")] @property def unsized(self) -> list[str]: @@ -535,7 +535,7 @@ class LandedChanges: @property def requirements(self) -> int: - return sum(r["kind"] == "requirement_corrected" for r in self.records) + return sum(r["kind"] == "required_aligned" for r in self.records) @property def lists(self) -> int: @@ -624,14 +624,14 @@ def landed_changes(exporter: object, systems: dict) -> LandedChanges: and exporter.required_value(entry) != bool(entry.platform["required"]) ): records.append(_change( - "requirement_corrected", entry, "required", + "required_aligned", entry, "required", bool(entry.platform["required"]), exporter.required_value(entry), )) for field_name in entry.corrections: if field_name == "required" or not exporter.states(entry, field_name): continue records.append(_change( - "hash_corrected", entry, field_name, + "hash_updated", entry, field_name, _platform_value(entry, field_name), _written_value(exporter, entry, field_name), )) @@ -648,7 +648,7 @@ def landed_changes(exporter: object, systems: dict) -> LandedChanges: and exporter.states(entry, field_name) ): records.append(_change( - "hash_corrected", entry, field_name, + "hash_updated", entry, field_name, _platform_value(entry, field_name), listed, )) records.extend( @@ -666,7 +666,7 @@ def landed_changes(exporter: object, systems: dict) -> LandedChanges: and exporter.states(entry, "size") ): records.append(_change( - "size_corrected", entry, "size", _single_size(theirs), ours + "size_updated", entry, "size", _single_size(theirs), ours )) elif ( theirs @@ -690,7 +690,7 @@ def landed_changes(exporter: object, systems: dict) -> LandedChanges: # A value the render rewrote though the model reads it as the # platform's own (RomM's sha1 with a leading space). records.extend( - _change("hash_corrected", entry, field_name, old, new) + _change("hash_updated", entry, field_name, old, new) for entry, field_name, old, new in getattr(exporter, "normalized_values", []) ) return changes @@ -863,12 +863,12 @@ def export_platform( "not_kept": lost, }, "changes": changes.records, - "hashes_corrected": applied, + "hashes_updated": applied, "hashes_filled": filled, - "sizes_corrected": resized, + "sizes_updated": resized, "sizes_removed": unsized, "hashes_cleared": cleared, - "requirements_corrected": requirements, + "required_aligned": requirements, "withheld": list(withheld), "found_by_content": report.labels, "unattributed": report.unattributed, diff --git a/scripts/fetch_site_images.py b/scripts/fetch_site_images.py index 8da5e1c9a..f698289e9 100644 --- a/scripts/fetch_site_images.py +++ b/scripts/fetch_site_images.py @@ -172,7 +172,8 @@ def fetch_logos() -> None: def fetch_icons() -> None: - from generate_site import _group_by_manufacturer, load_database + from common import load_database + from sitecollection import _group_by_manufacturer from siterender import SYSTEM_ICON_BASE, _icon_name db = load_database(str(ROOT / "database.json")) diff --git a/scripts/generate_readme.py b/scripts/generate_readme.py index e845201a0..572f3d1eb 100644 --- a/scripts/generate_readme.py +++ b/scripts/generate_readme.py @@ -13,6 +13,7 @@ from __future__ import annotations import argparse import json import os +import re import sys from collections.abc import Callable from datetime import datetime, timezone @@ -40,6 +41,8 @@ from nativemode import check_label import release_record from siteguides import install_command from validate_pr import LARGE_FILE_ISSUE_URL +from largefiles import load_registered_paths +import pr_checks from verify import verify_platform @@ -447,6 +450,29 @@ REPO = "Abdess/retrobios" UPLOAD_URL = f"https://github.com/{REPO}/upload/main/bios" +def clone_line(db: dict) -> str: + """How to get the clone the commands below run in, and its size. + + Said bare, "clone the repo" started a multi-gigabyte transfer for a user + after a 20 MB --required-only pack. The size counts the collection files + the repository commits, a floor: release assets are fetched only when a + pack needs them. + """ + assets = set(load_registered_paths()) + committed = sum( + entry.get("size", 0) for entry in db.get("files", {}).values() + if entry.get("path") not in assets + ) + return ( + "Packs are built from a clone, and the clone carries the collection:" + f" its checkout alone is over {format_size(committed)}." + f" `git clone --depth 1 https://github.com/{REPO}.git` leaves the" + " history out. Files kept as release assets are downloaded when a" + " pack needs them. Then generate packs for any platform, emulator," + " or system:" + ) + + def _existing_contributor_block(readme_path: str = "README.md") -> list[str]: """The contributors already published, read back from the file. @@ -767,7 +793,7 @@ def generate_readme(db: dict, platforms_dir: str, db_path: str = "database.json" "", "## Build your own pack", "", - "Clone the repo and generate packs for any platform, emulator, or system:", + clone_line(db), "", "```bash", "# The pack of a platform, as released", @@ -891,6 +917,19 @@ def generate_readme(db: dict, platforms_dir: str, db_path: str = "database.json" return "\n".join(lines) + "\n" +def ci_install_lines() -> tuple[str, str]: + """The pip lines the test job of validate.yml runs, verbatim. + + The guide listed the packages without their versions, so a local run + could pass on releases CI never installs. Read from the workflow, the + two cannot disagree. The second line is the Python 3.8 one. + """ + text = (_REPO / ".github" / "workflows" / "validate.yml").read_text(encoding="utf-8") + main = re.search(r'^\s*run: (pip install pyyaml "jsonschema.*markdown.*)$', text, re.M) + floor = re.search(r"^\s*run: (python3\.8 -m pip install .*)$", text, re.M) + return main.group(1), floor.group(1) + + def contributing_text( link: Callable[[str], str], title: str, site_words: str ) -> str: @@ -900,6 +939,9 @@ def contributing_text( site. *link* renders a site page (absolute URL for the README, relative .md for the site); *site_words* names the site from where the text is read. """ + pip_line, floor_line = ci_install_lines() + width = max(len(step.command) for step in pr_checks.STEPS) + steps = "\n".join(f"{step.command:<{width}} # {step.why}" for step in pr_checks.STEPS) return f"""{title} ## Add a BIOS file @@ -974,18 +1016,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 pyte hypothesis vermin setuptools -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 --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 +{pip_line} +python scripts/pr_checks.py ``` +`pr_checks.py` runs these, in CI's order, carries on past a failure as CI +does, and names at the end each check that failed or could not run: + +```bash +{steps} +``` + +The versions are the ones CI pins. Three tools come from the system, not from +that line, and the tests that need them skip without them: `shellcheck` for the +handheld launchers, `bwrap` (bubblewrap) for the probe sandbox, and a Python 3.8 +with pyte (`{floor_line}`) for the full screen. CI has all three. + `profile_sync.py` compares the branch with this repository's `main`, and fetches it in a fork's clone. A profile you only edit is not held to `--strict`: its older refs are reviewed by hand. diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 97a5cd586..3c53f0aee 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -38,6 +38,10 @@ from pathlib import Path from typing import TypeVar sys.path.insert(0, os.path.dirname(__file__)) +from sitecollection import ( # noqa: E402 + _copy_places, _group_by_manufacturer, _profiles_in, + _profiles_on_disk, _slug, collection_paths, +) from common import ( BUCKET_FOLDERS, names_manufacturer, @@ -71,10 +75,6 @@ from cross_reference import FileConsumers, entry_target, held_copy from nativemode import check_label, digest_algorithm, reads_file_contents - - - - yaml = require_yaml() from generate_readme import ( compute_coverage, @@ -139,8 +139,6 @@ GENERATED_DIRS = ["platforms", "systems", "emulators", "wiki", "api", "downloads PUBLIC_DIRS = ("api", "downloads") - - def _content_check_ceiling(profiles: dict) -> str: """How far content checking can reach across every profiled entry.""" hashed = sized = neither = 0 @@ -170,9 +168,6 @@ def _content_check_ceiling(profiles: dict) -> str: ) - - - CLS_LABELS = { "official_port": "Official ports", "community_fork": "Community forks", @@ -202,33 +197,6 @@ CLS_LABEL_ONE = { } -def _copy_places(db: dict) -> dict[str, set[tuple[str, str, str]]]: - """sha1 -> every (manufacturer, console, file name) holding a copy. - - The database keeps one path per content, deduplication having left one - copy. by_path_suffix still records the other folders a copy is filed in, - under the name that folder gives it. - """ - files = db.get("files", {}) - bios_dir = Path(str(db.get("bios_dir") or "bios")) - places: dict[str, set[tuple[str, str, str]]] = {} - for sha1, entry in files.items(): - parts = str(entry.get("path", "")).split("/") - if len(parts) >= 3 and parts[0] == "bios": - places.setdefault(sha1, set()).add((parts[1], parts[2], parts[-1])) - for tail, sha1s in db.get("indexes", {}).get("by_path_suffix", {}).items(): - parts = tail.split("/") - if len(parts) < 3 or parts[1].startswith(".") or len(sha1s) != 1: - continue - if sha1s[0] in files and (bios_dir / tail).is_file(): - places.setdefault(sha1s[0], set()).add((parts[0], parts[1], parts[-1])) - return places - - -def _slug(manufacturer: str) -> str: - return manufacturer.lower().replace(" ", "-") - - def _copies_by_path(db: dict) -> dict[str, set[tuple[str, str, str]]]: """The copy places of each content, keyed by the path the database keeps.""" files = db.get("files", {}) @@ -414,11 +382,6 @@ def _system_pages_from_data( return pages - - - - - def _timestamp() -> str: """The build time for data files (stats, API records), ISO 8601.""" return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") @@ -437,8 +400,6 @@ def _date_line() -> str: return f'
{data_as_of()}.
' - - # Home page @@ -2486,10 +2447,16 @@ def _browser_title(relative: Path, title: str) -> str: return title +_LINK_TEXT = r"(?:`[^`]*`|\[[^\]\[]*\]|[^\]\[])" + + def _plain_markdown(text: str) -> str: text = re.sub(r"<[^>]+>", " ", text) - text = re.sub(r"!\[([^]]*)\]\([^)]*\)", r"\1", text) - text = re.sub(r"\[([^]]+)\]\([^)]*\)", r"\1", text) + # Link text may hold brackets: a code span or a No-Intro name such as + # `[BIOS] LaserActive PAC-N1 (Japan) (v1.02).bin` left the whole link, + # anchor included, in the Pioneer page's meta description. + text = re.sub(rf"!\[({_LINK_TEXT}*)\]\([^)]*\)", r"\1", text) + text = re.sub(rf"\[({_LINK_TEXT}+)\]\([^)]*\)", r"\1", text) # Code spans keep their text whole: validate_schemas.py read # validateschemas.py once every underscore was taken for emphasis. pieces = re.split(r"(`[^`]*`)", text) @@ -5266,122 +5233,6 @@ def write_lookup_shards( return len(shards) -def collection_paths(db: dict) -> dict[str, set[str]]: - """Every path under bios/ that holds each file, by sha1. - - The database keeps one path per content. The others are the full tails - by_path_suffix records that name a file on disk: RPG Maker, engine - trees and the copies filed under several consoles keep 2,502 of them. - """ - files = db.get("files", {}) - bios_dir = Path(str(db.get("bios_dir") or "bios")) - paths: dict[str, set[str]] = {} - for sha1, entry in files.items(): - path = str(entry.get("path", "")) - if path.startswith("bios/") and path.count("/") >= 2: - paths.setdefault(sha1, set()).add(path) - for tail, sha1s in db.get("indexes", {}).get("by_path_suffix", {}).items(): - if tail.count("/") < 2 or len(sha1s) != 1 or sha1s[0] not in files: - continue - if (bios_dir / tail).is_file(): - paths.setdefault(sha1s[0], set()).add(f"bios/{tail}") - return paths - - -def _group_by_manufacturer(db: dict) -> dict[str, dict[str, list]]: - """Group files by manufacturer -> console -> files. - - The database keeps one path per content, and a copy filed elsewhere was - on no page of its own: the Channel F BIOS sat under Arcade > FBNeo only, - and Fairchild had no system page. Every folder holding a copy lists it, - read from the full paths by_path_suffix records. The game engines' - files are grouped by engine (_engine_sections). - """ - files = db.get("files", {}) - manufacturers: dict[str, dict[str, list]] = {} - for sha1, where in _copy_places(db).items(): - for manufacturer, console in sorted({(m, c) for m, c, _ in where}): - manufacturers.setdefault(manufacturer, {}).setdefault(console, []).append(files[sha1]) - return _engine_sections(manufacturers) - - -def _engine_sections(manufacturers: dict[str, dict[str, list]]) -> dict[str, dict[str, list]]: - """A game engine's files in one section headed with the engine's name. - - The Systems index listed ".variants", "scummvm.zip" and "wildmidi.cfg" - as games, and ScummVM's files sat on three pages. A folder of variants - joins the section of its primary, a file kept at a top's root joins - the section of the one engine that reads it (else of the engine the - top is named after), the folder under Other named after a game-engine - top joins that top, and a folder named by a profile key is headed with - the engine's name (SECTION_LABELS), its address unchanged. - """ - from siterender import _PROGRAM_TOPS, SECTION_LABELS, record_card_move # noqa: PLC0415 - - profiles = {key: profile for key, profile in _profiles_on_disk().items() - if profile.get("type") not in ("alias", "test")} - readers: dict[str, set[str]] = {} - for key, profile in profiles.items(): - for entry in profile.get("files") or []: - if isinstance(entry, dict): - for name in [entry.get("name"), *(entry.get("aliases") or [])]: - if name: - readers.setdefault(str(name).rsplit("/", 1)[-1], set()).add(key) - - def engine(key: str) -> str: - return str(profiles[key].get("emulator") or key) - - def profile_of(name: str) -> str | None: - return next((k for k in sorted(profiles) - if name.casefold() in (k.casefold(), engine(k).casefold())), None) - - out: dict[str, dict[str, list]] = {} - for mfr, consoles in manufacturers.items(): - if mfr not in GAME_DATA_TOPS: - out[mfr] = dict(consoles) - continue - own = profile_of(mfr) - regrouped: dict[str, list] = {} - for console, files in sorted(consoles.items(), key=lambda item: item[0].startswith(".")): - for f in files: - parts = str(f.get("path", "")).split("/") - target = console - if console.startswith(".") or (len(parts) == 3 and console == parts[-1]): - # A variant answers its primary's name: scummvm.zip.a93f1c4b. - base = parts[-1].rsplit(".", 1)[0] if console.startswith(".") else parts[-1] - keys = sorted(readers.get(base, ())) - key = keys[0] if len(keys) == 1 else own - found = next((c for c, held in regrouped.items() - if any(h.get("name") == base for h in held)), None) - target = found or (next((c for c in consoles if c == key), None) - or (engine(key) if key else base)) - if target != console: - record_card_move(mfr, console, str(f.get("sha1", "")), mfr, target) - regrouped.setdefault(target, []).append(f) - out[mfr] = regrouped - # The folder under Other an engine top is named after: ScummVM's - # engine data under Other/ScummVM, its themes under ScummVM/. - others = out.get("Other", {}) - for folder in sorted(others): - top = next((t for t in out if t in GAME_DATA_TOPS and t.casefold() == folder.casefold()), - None) - if top is None or top in _PROGRAM_TOPS: - continue - sections = out[top] - target = next((c for c in sections if c.casefold() == folder.casefold()), folder) - held = {str(f.get("sha1", "")) for f in sections.get(target, [])} - for f in others[folder]: - record_card_move("Other", folder, str(f.get("sha1", "")), top, target) - sections[target] = [*sections.get(target, []), - *(f for f in others.pop(folder) if str(f.get("sha1", "")) not in held)] - for mfr in GAME_DATA_TOPS: - for console in out.get(mfr, {}): - key = profiles.get(console) and console - if key and engine(key) != console: - SECTION_LABELS[(mfr, console)] = engine(key) - return {mfr: consoles for mfr, consoles in out.items() if consoles} - - def _console_display(manufacturer: str, console: str) -> str: """A console section's name as the reader looks for it: the heading system_name gives its id, or the folder alone under a program's top @@ -6696,15 +6547,6 @@ def _reading_pairs(sha1: str, consumers: FileConsumers) -> list[tuple[str, dict] return pairs -def _profiles_on_disk() -> dict[str, dict]: - return _profiles_in(str(siteinputs.current().emulators), skip_aliases=False) - - -@functools.lru_cache(maxsize=None) -def _profiles_in(emulators_dir: str, skip_aliases: bool) -> dict[str, dict]: - return load_emulator_profiles(emulators_dir, skip_aliases=skip_aliases) - - def _entry_systems(emulator: str, entry: dict) -> list[str]: """The systems an entry is read for: its own `system:` or `systems:` (a MAME device set several systems load), else its profile's only one.""" @@ -11356,8 +11198,6 @@ def generate_gap_analysis( return "\n".join(lines) + "\n" - - def _core_scope_note(config: dict, profiles: dict) -> str: """" (all libretro)" when the platform resolves every libretro core. diff --git a/scripts/make_og_cards.py b/scripts/make_og_cards.py index ca0a2832d..e35bf3441 100644 --- a/scripts/make_og_cards.py +++ b/scripts/make_og_cards.py @@ -70,7 +70,7 @@ def system_cards() -> dict[str, tuple[str, str]]: manufacturer and listing its consoles. Read from the same grouping the system pages are written from.""" from common import GAME_DATA_TOPS, load_database - from generate_site import _group_by_manufacturer, _slug + from sitecollection import _group_by_manufacturer, _slug def lead(manufacturer: str) -> str: # Other holds one folder per emulator, a game publisher's page its diff --git a/scripts/pr_checks.py b/scripts/pr_checks.py new file mode 100644 index 000000000..0acd4082c --- /dev/null +++ b/scripts/pr_checks.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Run the checks validate.yml runs on a pull request, in its order. + +Usage: + python scripts/pr_checks.py + python scripts/pr_checks.py --list + +Each step runs even when an earlier one failed, as CI runs them, and the +summary names every step that failed or could not run. A step whose tool is +absent (npm, python3.8, vermin) is reported as not run, with what provides it. +""" + +from __future__ import annotations + +import argparse +import os +import shutil +import subprocess +import sys +from dataclasses import dataclass, field +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + + +@dataclass(frozen=True) +class Step: + command: str + why: str + needs: str = "" + provider: str = "" + env: dict[str, str] = field(default_factory=dict) + + +# The order of validate.yml: data fetched, site generated and built, then the +# suite, its 3.8 run and the built-site check. +STEPS: tuple[Step, ...] = ( + Step("python scripts/validate_pr.py --changed", "the BIOS files the branch adds"), + Step("python scripts/validate_schemas.py --source-only", "profiles and platform files"), + Step("python scripts/profile_sync.py --added --strict", "each profile the branch adds"), + Step("vermin --no-tips -t=3.8- --violations install.py", "install.py stays on Python 3.8", + needs="vermin", provider="the pip line of CONTRIBUTING.md"), + Step("python scripts/export_native.py --all --fetch --output-dir tmp/native", + "files the exporter tests compare with"), + Step("python scripts/refresh_data_dirs.py", "data directories the pack-building tests read"), + Step("python scripts/generate_site.py", "the pages the site tests read"), + Step("npm ci --prefix website", "the site's packages, Node 24", + needs="npm", provider="Node 24"), + Step("npm run build --prefix website", "the built site", + needs="npm", provider="Node 24"), + Step("python -m unittest discover -s tests -t .", "the suite", + env={"RETROBIOS_OFFLINE": "1"}), + Step("python3.8 -m unittest tests.test_install_tui", "the full screen on the installer's floor", + needs="python3.8", provider="a Python 3.8 with pyte", + env={"RETROBIOS_OFFLINE": "1"}), + Step("python scripts/validate_site.py --site-dir website/dist", "links and page budgets"), +) + + +def argv(step: Step) -> list[str]: + """The command, with `python` run by the interpreter running this script.""" + words = step.command.split() + return [sys.executable, *words[1:]] if words[0] == "python" else words + + +def run(steps: tuple[Step, ...] = STEPS) -> int: + failed: list[str] = [] + not_run: list[str] = [] + for step in steps: + print(f"\n== {step.command}", flush=True) + if step.needs and shutil.which(step.needs) is None: + print(f"not run: {step.needs} is not installed ({step.provider})") + not_run.append(f"{step.command} ({step.needs}: {step.provider})") + continue + code = subprocess.run(argv(step), cwd=ROOT, env={**os.environ, **step.env}).returncode + if code: + failed.append(f"{step.command} (exit {code})") + print() + for line in failed: + print(f"FAILED {line}") + for line in not_run: + print(f"NOT RUN {line}") + print(f"{len(steps) - len(failed) - len(not_run)}/{len(steps)} checks passed") + return 1 if failed else 0 + + +def main() -> int: + parser = argparse.ArgumentParser(allow_abbrev=False, description=__doc__.splitlines()[0]) + parser.add_argument("--list", action="store_true", help="print the steps and exit") + args = parser.parse_args() + if args.list: + for step in STEPS: + print(step.command) + return 0 + return run() + + +if __name__ == "__main__": + from common import run_cli + + run_cli(main) diff --git a/scripts/sitecollection.py b/scripts/sitecollection.py new file mode 100644 index 000000000..937bebe77 --- /dev/null +++ b/scripts/sitecollection.py @@ -0,0 +1,169 @@ +"""The collection as the site files it: every folder holding each file. + +The database keeps one path per content. The folders a copy is also filed +in, the system sections and the game-engine sections are read here, by the +page generator and by the tools that draw or fetch per-system images. The +bios folder comes from siteinputs, never the working directory. +""" + +from __future__ import annotations + +import functools +import os +import sys + +sys.path.insert(0, os.path.dirname(__file__)) +import siteinputs # noqa: E402 +from common import GAME_DATA_TOPS, load_emulator_profiles # noqa: E402 + + +def _copy_places(db: dict) -> dict[str, set[tuple[str, str, str]]]: + """sha1 -> every (manufacturer, console, file name) holding a copy. + + The database keeps one path per content, deduplication having left one + copy. by_path_suffix still records the other folders a copy is filed in, + under the name that folder gives it. + """ + files = db.get("files", {}) + bios_dir = siteinputs.bios_dir(db) + places: dict[str, set[tuple[str, str, str]]] = {} + for sha1, entry in files.items(): + parts = str(entry.get("path", "")).split("/") + if len(parts) >= 3 and parts[0] == "bios": + places.setdefault(sha1, set()).add((parts[1], parts[2], parts[-1])) + for tail, sha1s in db.get("indexes", {}).get("by_path_suffix", {}).items(): + parts = tail.split("/") + if len(parts) < 3 or parts[1].startswith(".") or len(sha1s) != 1: + continue + if sha1s[0] in files and (bios_dir / tail).is_file(): + places.setdefault(sha1s[0], set()).add((parts[0], parts[1], parts[-1])) + return places + + +def _slug(manufacturer: str) -> str: + return manufacturer.lower().replace(" ", "-") + + +def collection_paths(db: dict) -> dict[str, set[str]]: + """Every path under bios/ that holds each file, by sha1. + + The database keeps one path per content. The others are the full tails + by_path_suffix records that name a file on disk: RPG Maker, engine + trees and the copies filed under several consoles keep 2,502 of them. + """ + files = db.get("files", {}) + bios_dir = siteinputs.bios_dir(db) + paths: dict[str, set[str]] = {} + for sha1, entry in files.items(): + path = str(entry.get("path", "")) + if path.startswith("bios/") and path.count("/") >= 2: + paths.setdefault(sha1, set()).add(path) + for tail, sha1s in db.get("indexes", {}).get("by_path_suffix", {}).items(): + if tail.count("/") < 2 or len(sha1s) != 1 or sha1s[0] not in files: + continue + if (bios_dir / tail).is_file(): + paths.setdefault(sha1s[0], set()).add(f"bios/{tail}") + return paths + + +def _group_by_manufacturer(db: dict) -> dict[str, dict[str, list]]: + """Group files by manufacturer -> console -> files. + + The database keeps one path per content, and a copy filed elsewhere was + on no page of its own: the Channel F BIOS sat under Arcade > FBNeo only, + and Fairchild had no system page. Every folder holding a copy lists it, + read from the full paths by_path_suffix records. The game engines' + files are grouped by engine (_engine_sections). + """ + files = db.get("files", {}) + manufacturers: dict[str, dict[str, list]] = {} + for sha1, where in _copy_places(db).items(): + for manufacturer, console in sorted({(m, c) for m, c, _ in where}): + manufacturers.setdefault(manufacturer, {}).setdefault(console, []).append(files[sha1]) + return _engine_sections(manufacturers) + + +def _engine_sections(manufacturers: dict[str, dict[str, list]]) -> dict[str, dict[str, list]]: + """A game engine's files in one section headed with the engine's name. + + The Systems index listed ".variants", "scummvm.zip" and "wildmidi.cfg" + as games, and ScummVM's files sat on three pages. A folder of variants + joins the section of its primary, a file kept at a top's root joins + the section of the one engine that reads it (else of the engine the + top is named after), the folder under Other named after a game-engine + top joins that top, and a folder named by a profile key is headed with + the engine's name (SECTION_LABELS), its address unchanged. + """ + from siterender import _PROGRAM_TOPS, SECTION_LABELS, record_card_move # noqa: PLC0415 + + profiles = {key: profile for key, profile in _profiles_on_disk().items() + if profile.get("type") not in ("alias", "test")} + readers: dict[str, set[str]] = {} + for key, profile in profiles.items(): + for entry in profile.get("files") or []: + if isinstance(entry, dict): + for name in [entry.get("name"), *(entry.get("aliases") or [])]: + if name: + readers.setdefault(str(name).rsplit("/", 1)[-1], set()).add(key) + + def engine(key: str) -> str: + return str(profiles[key].get("emulator") or key) + + def profile_of(name: str) -> str | None: + return next((k for k in sorted(profiles) + if name.casefold() in (k.casefold(), engine(k).casefold())), None) + + out: dict[str, dict[str, list]] = {} + for mfr, consoles in manufacturers.items(): + if mfr not in GAME_DATA_TOPS: + out[mfr] = dict(consoles) + continue + own = profile_of(mfr) + regrouped: dict[str, list] = {} + for console, files in sorted(consoles.items(), key=lambda item: item[0].startswith(".")): + for f in files: + parts = str(f.get("path", "")).split("/") + target = console + if console.startswith(".") or (len(parts) == 3 and console == parts[-1]): + # A variant answers its primary's name: scummvm.zip.a93f1c4b. + base = parts[-1].rsplit(".", 1)[0] if console.startswith(".") else parts[-1] + keys = sorted(readers.get(base, ())) + key = keys[0] if len(keys) == 1 else own + found = next((c for c, held in regrouped.items() + if any(h.get("name") == base for h in held)), None) + target = found or (next((c for c in consoles if c == key), None) + or (engine(key) if key else base)) + if target != console: + record_card_move(mfr, console, str(f.get("sha1", "")), mfr, target) + regrouped.setdefault(target, []).append(f) + out[mfr] = regrouped + # The folder under Other an engine top is named after: ScummVM's + # engine data under Other/ScummVM, its themes under ScummVM/. + others = out.get("Other", {}) + for folder in sorted(others): + top = next((t for t in out if t in GAME_DATA_TOPS and t.casefold() == folder.casefold()), + None) + if top is None or top in _PROGRAM_TOPS: + continue + sections = out[top] + target = next((c for c in sections if c.casefold() == folder.casefold()), folder) + held = {str(f.get("sha1", "")) for f in sections.get(target, [])} + for f in others[folder]: + record_card_move("Other", folder, str(f.get("sha1", "")), top, target) + sections[target] = [*sections.get(target, []), + *(f for f in others.pop(folder) if str(f.get("sha1", "")) not in held)] + for mfr in GAME_DATA_TOPS: + for console in out.get(mfr, {}): + key = profiles.get(console) and console + if key and engine(key) != console: + SECTION_LABELS[(mfr, console)] = engine(key) + return {mfr: consoles for mfr, consoles in out.items() if consoles} + + +def _profiles_on_disk() -> dict[str, dict]: + return _profiles_in(str(siteinputs.current().emulators), skip_aliases=False) + + +@functools.lru_cache(maxsize=None) +def _profiles_in(emulators_dir: str, skip_aliases: bool) -> dict[str, dict]: + return load_emulator_profiles(emulators_dir, skip_aliases=skip_aliases) diff --git a/scripts/siteexports.py b/scripts/siteexports.py index 870cbe43d..300ce9758 100644 --- a/scripts/siteexports.py +++ b/scripts/siteexports.py @@ -22,9 +22,9 @@ API_EXPORTS = "api/v1/exports" # Report key, then how a line of it reads on the page. CHANGE_KINDS = ( - ("hashes_corrected", "Hashes updated to the emulator source"), + ("hashes_updated", "Hashes updated to the emulator source"), ("hashes_filled", "Hashes added where the original lists none"), - ("sizes_corrected", "Sizes updated to the emulator source"), + ("sizes_updated", "Sizes updated to the emulator source"), ("sizes_removed", "Sizes no longer stated"), ("hashes_cleared", "Hashes no longer listed"), ("withheld", "Changes not applied"), @@ -33,9 +33,9 @@ CHANGE_KINDS = ( # Report key, then the kind each line of `changes` carries. CHANGE_RECORDS = { - "hashes_corrected": "hash_corrected", + "hashes_updated": "hash_updated", "hashes_filled": "hash_filled", - "sizes_corrected": "size_corrected", + "sizes_updated": "size_updated", "sizes_removed": "size_removed", "hashes_cleared": "hash_cleared", } @@ -52,11 +52,11 @@ def describe_change(change: dict) -> str: """One change in words, with the value before and after it.""" label = f"`{change['system']}/{change['name']}` {change['field']}" kind, old, new = change["kind"], change["old"], change["new"] - if kind in ("hash_corrected", "size_corrected"): + if kind in ("hash_updated", "size_updated"): return f"{label} {_value(old)} becomes {_value(new)}" if kind == "hash_filled": return f"{label} {_value(new)} added" - if kind == "requirement_corrected": + if kind == "required_aligned": return f"`{change['system']}/{change['name']}` becomes {'required' if new else 'optional'}" return f"{label} {_value(old)} no longer listed" @@ -76,7 +76,7 @@ def requirement_lines(report: dict) -> list[str]: """Each required flag aligned with the code, and what it is now.""" items = [ describe_change(c) for c in report.get("changes") or [] - if c["kind"] == "requirement_corrected" + if c["kind"] == "required_aligned" ] if not items: return [] @@ -108,11 +108,11 @@ def change_text(report: dict) -> str: parts = [f"{_plural(counts['kept'], 'entry', 'entries')} kept"] extra = [ (counts["added"], "file", "files", "added"), - (len(report.get("hashes_corrected") or []), "hash", "hashes", "updated"), - (report.get("requirements_corrected") or 0, "required flag", "required flags", + (len(report.get("hashes_updated") or []), "hash", "hashes", "updated"), + (report.get("required_aligned") or 0, "required flag", "required flags", "aligned with the code"), (len(report.get("hashes_filled") or []), "hash", "hashes", "added"), - (len(report.get("sizes_corrected") or []), "size", "sizes", "updated"), + (len(report.get("sizes_updated") or []), "size", "sizes", "updated"), ] parts += [f"{_plural(n, one, many)} {verb}" for n, one, many, verb in extra if n] return ", ".join(parts) @@ -216,7 +216,7 @@ def _display(platform: str, names: dict[str, str]) -> str: return names.get(platform) or platform -def _corrected_link(report: dict) -> str: +def _updated_link(report: dict) -> str: platform, files = report["platform"], report["files"] if len(files) == 1: path = f"{EXPORTS_DIR}/{platform}/{files[0]}" @@ -286,7 +286,7 @@ def generate_exports_page(reports: list[dict], names: dict[str, str]) -> str: platform = report["platform"] lines.append( f"| [{_display(platform, names)}](#{platform}) | " - f"{_original_link(report)} | {_corrected_link(report)} | " + f"{_original_link(report)} | {_updated_link(report)} | " f"{change_text(report)} |" ) lines.append("") diff --git a/scripts/siteinputs.py b/scripts/siteinputs.py index 7aa3326ff..1df5389c3 100644 --- a/scripts/siteinputs.py +++ b/scripts/siteinputs.py @@ -30,3 +30,14 @@ def current() -> SiteInputs: def use(inputs: SiteInputs) -> None: _current[0] = inputs + + +def bios_dir(db: dict) -> Path: + """The database's bios folder, as the inputs place it. + + database.json names it relative to its own folder ("bios"). Read from the + working directory instead, a tool started elsewhere found no copy on disk + and dropped five system sections and 2,500 collection paths in silence. + """ + named = Path(str(db.get("bios_dir") or "bios")) + return named if named.is_absolute() else current().db.parent / named diff --git a/scripts/sitemeasurements.py b/scripts/sitemeasurements.py index 7514d1253..e7a71a05c 100644 --- a/scripts/sitemeasurements.py +++ b/scripts/sitemeasurements.py @@ -733,8 +733,8 @@ def data_page_section(path: Path) -> list[str]: "", "[`measurements.sqlite`](downloads/measurements.sqlite) holds every core probe " f"record kept in [`{mr.RECORDS_DIR}/`]({REPO}/tree/main/{mr.RECORDS_DIR}), and " - "`downloads/measurements/.sqlite` the records of one emulator, linked " - f"from its page. The [Measurements]({EXPLORER_PAGE}) page reads " + "`downloads/measurements/.sqlite` the records of one emulator. The " + f"[Measurements]({EXPLORER_PAGE}) page reads " "[`data/measurements/index.json`](data/measurements/index.json). A record line is " "valid against [`core-probe-record.schema.json`](api/v1/schemas/core-probe-record.schema.json).", "", diff --git a/scripts/siterender.py b/scripts/siterender.py index 0e4fda9b2..c71d2db10 100644 --- a/scripts/siterender.py +++ b/scripts/siterender.py @@ -780,7 +780,7 @@ def _ensure_console_sections() -> None: import json from common import load_emulator_profiles - from generate_site import _group_by_manufacturer + from sitecollection import _group_by_manufacturer inputs = siteinputs.current() with open(inputs.db, encoding="utf-8") as handle: diff --git a/scripts/sitesources.py b/scripts/sitesources.py index c28b7c5f1..a5cdbbccc 100644 --- a/scripts/sitesources.py +++ b/scripts/sitesources.py @@ -16,9 +16,10 @@ from dataclasses import dataclass from pathlib import Path import upstream -from siterender import _by_mode +from siterender import _by_mode, _escape_tags from sourceref import ( - _HEX_ADDRESS_RE, _prose_runs, is_binary_citation, ref_role, source_ref_values, + _HEX_ADDRESS_RE, _prose_runs, is_binary_citation, ref_annotation, ref_role, + source_ref_values, split_source_ref, ) @@ -512,7 +513,7 @@ def source_ref_items(profile: dict, value: str | dict | None) -> list[tuple[str, for part in split_source_ref(refs): if is_binary_citation(part.path): text, binary = _binary_citation(part.path, binary) - rendered.append((text, "")) + rendered.append((text + _annotation_markdown(ref_annotation(part.raw)), "")) continue binary = "" role, bare = ref_role(part.path) @@ -544,19 +545,31 @@ def source_ref_items(profile: dict, value: str | dict | None) -> list[tuple[str, role = (role_label(profile, current_role, part.path) if current_role and _two_repositories(profile) else "") + note = _annotation_markdown(ref_annotation(part.raw)) if selected is None: - rendered.append((f"`{display}`", role)) + rendered.append((f"`{display}`{note}", role)) else: repo, pin = selected url = _source_permalink( repo, pin, real_path, part.start, part.end ) - rendered.append((f"[`{display}`]({url})", role)) + rendered.append((f"[`{display}`]({url}){note}", role)) groups.append((label, rendered or [(f"`{refs}`", "")])) return groups +def _annotation_markdown(text: str) -> str: + """A part's annotation after its citation, as written: `(lsf default)`. + + Profile prose, so placeholders keep their brackets and nothing in it + opens emphasis or a table cell. + """ + if not text: + return "" + return " " + _escape_tags(re.sub(r"([*|\\])", r"\\\1", text)) + + def _binary_citation(path: str, previous: str) -> tuple[str, str]: """A part citing code inside a shipped binary, and the binary it names. diff --git a/scripts/sourceref.py b/scripts/sourceref.py index ec980b6e2..a7436f977 100644 --- a/scripts/sourceref.py +++ b/scripts/sourceref.py @@ -123,6 +123,25 @@ def split_source_ref(ref: str) -> list[RefPart]: return parts +_LOCATION_RE = re.compile(r":\d+(?:-\d+)?|^\s*\d+(?:-\d+)?(?=\s|$)") + + +def ref_annotation(raw: str) -> str: + """The words a part writes beside its location, for a reader. + + split_source_ref drops them because a comparison has no use for them, + but `src/loader2.cpp:284 (lsf default), src/netcfg.cpp:204 (lsf + override)` says which line wins only through them, and 1,103 of the + profiles' annotations were on no page. + """ + paren = raw.find("(") + head = raw if paren < 0 else raw[:paren] + located = _LOCATION_RE.search(head) + if located: + return raw[located.end():].strip() + return raw[paren:].strip() if paren >= 0 else "" + + def ref_prose_text(ref: str) -> str: """The text of a source_ref its parts do not read, positions kept. diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index c5e4a8ecc..555bbd50a 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -7574,5 +7574,70 @@ class TupleAnnotationsMatchTheirReturns(unittest.TestCase): self.assertEqual(mismatched, []) +class PrboomPlusCitesFullPaths(unittest.TestCase): + """prboom-plus cited gl_hires.c, e6y.c, d_client.c and g_game.c bare: + none resolves against the repository, so the page showed them as plain + code with no link, and "behind GL_DOOM and PRBOOM_DEBUG" was read as a + seventeenth citation. Each name has one path in the tree at the pin.""" + + def test_every_cited_file_has_its_directory(self): + import yaml + + from scripts.sourceref import source_ref_values, split_source_ref + + profile = yaml.safe_load((ROOT / "emulators" / "prboom-plus.yml").read_text(encoding="utf-8")) + refs: list[str] = [] + + def walk(node): + if isinstance(node, dict): + for key, value in node.items(): + if key == "source_ref": + refs.extend(r for _, r in source_ref_values(value)) + else: + walk(value) + elif isinstance(node, list): + for item in node: + walk(item) + + walk(profile) + parts = [p for ref in refs for p in split_source_ref(ref)] + self.assertGreater(len(parts), 40) + for part in parts: + with self.subTest(path=part.path): + self.assertIsNotNone(part.start) + self.assertTrue(part.path.startswith("prboom2/"), part.path) + + +class AutoFetchIssueBodyReadsAsWritten(unittest.TestCase): + """The --create-issues body was a template: Title Case headings, "The + following...", an exclamation mark, numbered orders to the reader, a + spaced dash, and hashes cut to 12 characters a contributor cannot + match a file against.""" + + ENTRY = {"name": "scph5501.bin", "system": "sony-playstation", + "sha1": "b056ee5a4d65937e1a3a17e1e78f3258ea49c38e", + "md5": "490f666e1afb15b7362b406ed1cea246"} + + def test_full_hashes_and_no_template_phrasing(self): + from scripts import auto_fetch + + body = auto_fetch.generate_issue_body([self.ENTRY], "RetroArch") + self.assertIn(f"`{self.ENTRY['sha1']}`", body) + self.assertIn(f"`{self.ENTRY['md5']}`", body) + self.assertNotIn("...", body) + self.assertNotIn("!", body) + self.assertNotRegex(body, r"(?i)the following|please| - |\bfork\b") + self.assertNotRegex(body, r"(?m)^\d+\. ") + for heading in re.findall(r"(?m)^#+ (.*)$", body): + later = [w for w in heading.split()[1:] if w != "RetroArch"] + self.assertEqual(later, [w.lower() for w in later], heading) + + def test_the_docstring_says_nothing_is_posted(self): + from scripts import auto_fetch + + self.assertNotIn("Create GitHub Issue", auto_fetch.__doc__) + self.assertIn("posts nothing", auto_fetch.__doc__) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_batocera_md5_list.py b/tests/test_batocera_md5_list.py index cc207dd7d..de98a6c71 100644 --- a/tests/test_batocera_md5_list.py +++ b/tests/test_batocera_md5_list.py @@ -345,7 +345,7 @@ class SeveralAcceptedMd5BecomeAList(unittest.TestCase): self.assertEqual( produced, LISTING.replace(f'"md5": "{C}"', f'"md5": [ "{C}", "{D}" ]') ) - record = next(r for r in changes.records if r["kind"] == "hash_corrected") + record = next(r for r in changes.records if r["kind"] == "hash_updated") self.assertEqual((record["old"], record["new"]), (C, [C, D])) self.assertEqual(changes.lists, 1) @@ -433,7 +433,7 @@ class SeveralAcceptedMd5BecomeAList(unittest.TestCase): class SitePageShowsAList(unittest.TestCase): def test_a_list_reads_as_any_of_its_values(self): line = describe_change({ - "kind": "hash_corrected", "system": "psx", "name": "scph5500.bin", + "kind": "hash_updated", "system": "psx", "name": "scph5500.bin", "field": "md5", "old": C, "new": [C, D], }) self.assertEqual(line, f"`psx/scph5500.bin` md5 `{C}` becomes `{C}` or `{D}`") diff --git a/tests/test_contributing_text.py b/tests/test_contributing_text.py index 3e16e4838..c79ea1413 100644 --- a/tests/test_contributing_text.py +++ b/tests/test_contributing_text.py @@ -37,7 +37,6 @@ class OneContributingText(unittest.TestCase): ) - class CoverageReadsTheGivenProfiles(unittest.TestCase): """generate_site --emulators-dir changed the emulator pages but not the platform coverage, which read ./emulators whatever was given.""" @@ -67,7 +66,7 @@ class BeforeOpeningAPullRequest(unittest.TestCase): text = (REPO_ROOT / "CONTRIBUTING.md").read_text(encoding="utf-8") section = text.split("## Before opening a PR", 1)[1].split("\n## ", 1)[0] - block = _re.search(r"```bash\n(.*?)```", section, _re.S).group(1) + block = "".join(_re.findall(r"```bash\n(.*?)```", section, _re.S)) for command in ("validate_pr.py --changed", "validate_schemas.py --source-only", "unittest discover"): self.assertIn(command, block) @@ -116,11 +115,41 @@ class BeforeOpeningAPullRequest(unittest.TestCase): line naming the old theme installed packages nothing reads.""" text = generate_readme.generate_contributing() section = text.split("## Before opening a PR", 1)[1].split("\n## ", 1)[0] - self.assertIn('pip install pyyaml "jsonschema[format-nongpl]" markdown', section) + self.assertIn('pip install pyyaml "jsonschema[format-nongpl]==', section) self.assertNotIn("mkdocs", section) self.assertTrue((REPO_ROOT / "website" / "package-lock.json").is_file()) +class TheChecklistInstallsWhatCiInstalls(unittest.TestCase): + """The pip line named the packages without versions, and nothing said + that shellcheck, bubblewrap and a Python 3.8 with pyte come from the + system: their tests skipped locally and failed first in CI.""" + + def test_the_pip_lines_are_the_workflow_lines(self): + workflow = (REPO_ROOT / ".github" / "workflows" / "validate.yml").read_text(encoding="utf-8") + text = generate_readme.generate_contributing() + section = text.split("## Before opening a PR", 1)[1].split("\n## ", 1)[0] + pinned = [line.split("run: ", 1)[1] for line in workflow.splitlines() + if re.match(r"\s*run: (python3\.8 -m )?pip install .*==", line) + and "pyte" in line] + self.assertEqual(len(pinned), 2) + for line in pinned: + with self.subTest(line=line): + self.assertIn(line, section) + + def test_it_names_the_system_tools_ci_installs(self): + workflow = (REPO_ROOT / ".github" / "workflows" / "validate.yml").read_text(encoding="utf-8") + text = generate_readme.generate_contributing() + section = text.split("## Before opening a PR", 1)[1].split("\n## ", 1)[0] + for tool, step in (("shellcheck", "shellcheck --version"), + ("bwrap", "bwrap --ro-bind"), + ("python3.8 -m unittest tests.test_install_tui", + "python3.8 -m unittest tests.test_install_tui")): + with self.subTest(tool=tool): + self.assertIn(step, workflow) + self.assertIn(tool, section) + + class OneFileThroughTheBrowser(unittest.TestCase): """Adding one BIOS asked for a fork, a 5.5 GB clone, five pip packages, the data directories and the whole suite, and promised a PR comment that @@ -174,5 +203,83 @@ class OneFileThroughTheBrowser(unittest.TestCase): )) +class TheGuideRebuildsWhenItsPinsMove(unittest.TestCase): + """The checklist quotes validate.yml, so a pin bump there must redeploy + the guide and meet the README/CONTRIBUTING freshness check.""" + + def test_deploy_site_watches_the_validation_workflow(self): + import yaml + + deploy = yaml.safe_load( + (REPO_ROOT / ".github" / "workflows" / "deploy-site.yml").read_text(encoding="utf-8")) + paths = deploy[True]["push"]["paths"] + self.assertIn(".github/workflows/validate.yml", paths) + + +class OneCommandRunsTheChecks(unittest.TestCase): + """A contributor copied ten commands by hand before a pull request, and + skipping one (refresh_data_dirs) failed the pack tests with no hint.""" + + CHECK = re.compile(r"(?:xargs -0 |run: |^\s*)((?:python3?\.?8? -m unittest|python scripts/\w+\.py" + r"|vermin|npm (?:ci|run build)))") + + def test_every_check_ci_runs_is_a_step(self): + import pr_checks + + workflow = (REPO_ROOT / ".github" / "workflows" / "validate.yml").read_text(encoding="utf-8") + ran = {m.group(1) for line in workflow.splitlines() + if not line.strip().startswith("#") for m in [self.CHECK.search(line)] if m} + self.assertGreaterEqual(len(ran), 10) + steps = [step.command for step in pr_checks.STEPS] + for check in sorted(ran): + with self.subTest(check=check): + self.assertTrue(any(command.startswith(check) for command in steps)) + + def test_steps_keep_ci_order(self): + import pr_checks + + workflow = (REPO_ROOT / ".github" / "workflows" / "validate.yml").read_text(encoding="utf-8") + order = [workflow.index(key) for key in ( + "refresh_data_dirs.py", "run: python scripts/generate_site.py", + "npm run build --prefix website", "python -m unittest discover", + "validate_site.py")] + self.assertEqual(order, sorted(order)) + commands = [step.command for step in pr_checks.STEPS] + positions = [next(i for i, c in enumerate(commands) if key in c) for key in ( + "refresh_data_dirs.py", "generate_site.py", "npm run build", + "unittest discover", "validate_site.py")] + self.assertEqual(positions, sorted(positions)) + + def test_the_guide_gives_the_runner_and_what_it_runs(self): + import pr_checks + + text = generate_readme.generate_contributing() + section = text.split("## Before opening a PR", 1)[1].split("\n## ", 1)[0] + self.assertIn("python scripts/pr_checks.py\n", section) + for step in pr_checks.STEPS: + with self.subTest(step=step.command): + self.assertIn(step.command, section) + + def test_a_missing_tool_is_reported_and_a_failure_does_not_stop_the_run(self): + import contextlib + import io + + import pr_checks + + steps = ( + pr_checks.Step("python -c raise(SystemExit(3))", "fails"), + pr_checks.Step("no-such-tool-here --x", "absent", needs="no-such-tool-here", + provider="nothing"), + pr_checks.Step("python -c pass", "passes"), + ) + out = io.StringIO() + with contextlib.redirect_stdout(out): + code = pr_checks.run(steps) + self.assertEqual(code, 1) + self.assertIn("FAILED python -c raise(SystemExit(3)) (exit 3)", out.getvalue()) + self.assertIn("NOT RUN no-such-tool-here --x", out.getvalue()) + self.assertIn("1/3 checks passed", out.getvalue()) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_doc_pages.py b/tests/test_doc_pages.py index dbdf0c8b5..4f0a1a537 100644 --- a/tests/test_doc_pages.py +++ b/tests/test_doc_pages.py @@ -1985,7 +1985,6 @@ class NoTextClaimsAScheduledScrape(unittest.TestCase): self.assertNotIn("list_platforms", workflow.read_text(encoding="utf-8")) - class TheCatalogRowsStateFacts(unittest.TestCase): """tools.md said "redump.org serves stale DATs" and "Dat-o-Matic blocks automation", verdicts on two preservation projects, and gave Redump's @@ -2003,6 +2002,100 @@ class TheCatalogRowsStateFacts(unittest.TestCase): self.assertIn("`/bios/`", rows[0]) +class RegionFreeRuleNamesWhereItDoesNotHold(unittest.TestCase): + """advanced-usage.md said psxonpsp660.bin is declared [world] and always + survives --region. PCSX ReARMed declares it [japan], and on the platforms + where it is the only PlayStation core the filter drops it.""" + + def test_every_platform_that_drops_a_named_file_is_named(self): + from common import (list_registered_platforms, load_emulator_profiles, + load_platform_config) + from region import WORLD, platform_region_index + + section = _section(_page("advanced-usage.md"), "Region filtering") + bullet = next(p for p in section.split("\n- ") if "Region-free" in p) + files = re.findall(r"`([^`]+\.bin)`", bullet) + self.assertTrue(files) + profiles = load_emulator_profiles(str(ROOT / "emulators")) + for platform in list_registered_platforms(str(ROOT / "platforms")): + config = load_platform_config(platform, str(ROOT / "platforms")) + index = platform_region_index(config, profiles) + for name in files: + entry = index.get(name) + drops = bool(entry and entry["regions"] and WORLD not in entry["regions"] + and not entry["has_untagged"]) + if drops: + with self.subTest(platform=platform, file=name): + self.assertRegex(bullet, rf"(?i)\b{re.escape(platform)}\b") + + +class BuildYourOwnPackSaysWhatTheCloneCosts(unittest.TestCase): + """The README sent a user after a minimal pack to "Clone the repo", with no + command and no size, and the clone runs to several gigabytes.""" + + def test_readme_gives_the_clone_command_and_its_size(self): + import generate_readme + + db = json.loads((ROOT / "database.json").read_text(encoding="utf-8")) + line = generate_readme.clone_line(db) + self.assertIn("git clone --depth 1", line) + self.assertRegex(line, r"over \d+(\.\d)? GB") + section = _section((ROOT / "README.md").read_text(encoding="utf-8"), + "Build your own pack") + self.assertIn("git clone --depth 1", section) + + def test_advanced_usage_names_the_shallow_clone(self): + self.assertIn("--depth 1", _page("advanced-usage.md").split("## Custom packs")[0]) + + +class LandingAChangeNamesTheCommitRules(unittest.TestCase): + """The page left out the review-tag rule scripts/commit_rules.py + enforces, and never said where the rules live or how a clone runs them: + the hook is not versioned.""" + + def _landing(self) -> str: + return _section(_page("testing-guide.md"), "Landing a change") + + def test_every_refusal_is_on_the_page(self): + import commit_rules + + section = self._landing() + words = re.search(r"\\b\((.*?)\)", commit_rules.REFUSED_WORDS.pattern).group(1) + from tests.test_prose_style import NoMarketingVocabulary + + for word in words.replace("|", " ").split(): + if NoMarketingVocabulary.WORDS.search(word): + # Named by "the marketing vocabulary above": spelled out, + # the page would fail that test. + continue + with self.subTest(word=word): + self.assertIn(word, section.lower()) + self.assertIn("which review asked for it", section) + self.assertIn("scripts/commit_rules.py", section) + + def test_the_hook_command_on_the_page_refuses_a_review_tag(self): + import subprocess + import tempfile + + block = re.search(r"```bash\n(.*?)```", self._landing(), re.S).group(1) + with tempfile.TemporaryDirectory(dir=ROOT / "tmp") as scratch: + clone = Path(scratch) + (clone / ".git" / "hooks").mkdir(parents=True) + (clone / "scripts").mkdir() + (clone / "scripts" / "commit_rules.py").write_bytes( + (ROOT / "scripts" / "commit_rules.py").read_bytes()) + subprocess.run(["sh", "-c", block], cwd=clone, check=True) + message = clone / "MSG" + message.write_text("fix: address review 3 notes\n", encoding="utf-8") + refused = subprocess.run([str(clone / ".git" / "hooks" / "commit-msg"), "MSG"], + cwd=clone, capture_output=True, text=True) + message.write_text("fix: say what the change does\n", encoding="utf-8") + accepted = subprocess.run([str(clone / ".git" / "hooks" / "commit-msg"), "MSG"], + cwd=clone, capture_output=True, text=True) + self.assertEqual(refused.returncode, 1, refused.stderr) + self.assertIn("which review asked for it", refused.stderr) + self.assertEqual(accepted.returncode, 0, accepted.stderr) + + if __name__ == "__main__": unittest.main() - diff --git a/tests/test_doc_surfaces.py b/tests/test_doc_surfaces.py index 75daca3d0..c87d74052 100644 --- a/tests/test_doc_surfaces.py +++ b/tests/test_doc_surfaces.py @@ -436,8 +436,6 @@ class PagesOpenOnTheirContent(unittest.TestCase): self.assertEqual(announcing, []) - - class CoreCountsReadFromTheData(unittest.TestCase): """The cores page said "623 cores across 18 platforms": that was every profile, and 91 of them no platform runs.""" @@ -611,5 +609,31 @@ class EveryOptionExists(unittest.TestCase): if (bad := unknown_options(text))} self.assertEqual(wrong, {}) + +class ContributorChecksFetchThePlatformsFiles(unittest.TestCase): + """validate.yml fetches each platform's own file before the suite. The + checklist left it out, so the exporter fidelity cases skipped on a + contributor's clone and first ran, red, in CI.""" + + def test_the_pr_checklist_fetches_them_before_the_tests(self): + workflow = _read(".github/workflows/validate.yml") + self.assertLess( + workflow.index("python scripts/export_native.py --all --fetch"), + workflow.index("python -m unittest discover"), + ) + for text in ( + contributing_text(lambda page: page, "# C", "here"), + _read("CONTRIBUTING.md"), + ): + block = _section(text, "## Before opening a PR") + self.assertIn("python scripts/export_native.py --all --fetch", block) + self.assertLess( + block.index("export_native.py"), block.index("unittest discover") + ) + + def test_the_testing_guide_names_the_command(self): + self.assertIn("export_native.py --all --fetch", _read("wiki/testing-guide.md")) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_export_counts.py b/tests/test_export_counts.py index 79734c9bd..75a6e8907 100644 --- a/tests/test_export_counts.py +++ b/tests/test_export_counts.py @@ -1083,7 +1083,7 @@ class AnExtendedListIsReported(unittest.TestCase): self.assertEqual(fe.hashes("md5"), [A, B]) self.assertEqual( [(r["kind"], r["field"], r["old"], r["new"]) for r in records], - [("hash_corrected", "md5", A, [A, B])], + [("hash_updated", "md5", A, [A, B])], ) def test_an_unchanged_value_reports_nothing(self): diff --git a/tests/test_export_report.py b/tests/test_export_report.py index e3127fe9f..da01affd0 100644 --- a/tests/test_export_report.py +++ b/tests/test_export_report.py @@ -29,8 +29,8 @@ def _report(platform: str, files: list[str], **changes: object) -> dict: "originals": {f: f"https://example.org/{platform}/{f}" for f in files}, "files": files, "summary": "3 kept, 1 file added, 7 hashes updated to the emulator source", - "hashes_corrected": [], "hashes_filled": [], "sizes_corrected": [], - "sizes_removed": [], "hashes_cleared": [], "requirements_corrected": 0, + "hashes_updated": [], "hashes_filled": [], "sizes_updated": [], + "sizes_removed": [], "hashes_cleared": [], "required_aligned": 0, "withheld": [], "invalid": [], } report.update(changes) @@ -58,8 +58,8 @@ class ExportReportIsWrittenAndRead(unittest.TestCase): body = source[source.index("def export_platform("):source.index("def write_report(")] call = body[body.index("write_report("):] for key, value in ( - ("hashes_corrected", "applied"), ("hashes_filled", "filled"), - ("sizes_corrected", "resized"), ("sizes_removed", "unsized"), + ("hashes_updated", "applied"), ("hashes_filled", "filled"), + ("sizes_updated", "resized"), ("sizes_removed", "unsized"), ("hashes_cleared", "cleared"), ("invalid", "issues"), ): self.assertIn(f'"{key}": {value},', call) @@ -73,7 +73,7 @@ class CorrectedFilesPage(unittest.TestCase): def setUp(self): corrected = [f"sys/file{i}.bin md5" for i in range(12)] self.reports = [ - _report("recalbox", ["es_bios.xml"], hashes_corrected=corrected), + _report("recalbox", ["es_bios.xml"], hashes_updated=corrected), _report("retropie", ["scriptmodules/a.sh", "scriptmodules/b.sh"], invalid=["bash -n failed on a.sh"], withheld=["a size the format has no field for"]), @@ -154,7 +154,7 @@ class ThePageAddressesMaintainersAsPeers(unittest.TestCase): from siteexports import change_text report = _report("batocera", ["batocera-systems"], - hashes_corrected=["a md5", "b md5"], requirements_corrected=3) + hashes_updated=["a md5", "b md5"], required_aligned=3) report["counts"] = {"kept": 692, "added": 253, "not_added": 10, "not_kept": 0} self.assertEqual( change_text(report), @@ -163,7 +163,7 @@ class ThePageAddressesMaintainersAsPeers(unittest.TestCase): ) def test_no_wording_that_judges_the_original(self): - report = _report("batocera", ["batocera-systems"], hashes_corrected=["a md5"]) + report = _report("batocera", ["batocera-systems"], hashes_updated=["a md5"]) report["counts"] = {"kept": 1, "added": 0, "not_added": 4, "not_kept": 1} page = generate_exports_page([report], {}).lower() for word in ("their file", "theirs", "corrected", "wrong", "drift", "left out"): @@ -243,13 +243,29 @@ class TheRunReportStatesWhatTheFileCarries(unittest.TestCase): for text in [report["summary"], *messages]: for word in self.JUDGING: self.assertNotIn(word, text.lower()) + # The report is served as api/v1/exports/.json, keys and + # change kinds included: "hash_corrected" sat on 130 Recalbox lines. + names = [*report, *(c["kind"] for c in report["changes"])] + self.assertIn("hash_updated", names) + for name in names: + for word in self.JUDGING: + self.assertNotIn(word, name) + + def test_the_committed_reports_use_the_same_names(self): + import json # noqa: PLC0415 + + for path in sorted((ROOT / "exports").glob("*.json")): + report = json.loads(path.read_text(encoding="utf-8")) + names = [*report, *(c["kind"] for c in report.get("changes") or [])] + with self.subTest(report=path.name): + self.assertFalse([n for n in names if "corrected" in n]) UPSTREAM_CACHE = ROOT / ".cache" / "upstream-native" _DAT_ROM = re.compile(r"^\trom \( name (\"[^\"]+\"|\S+) (.*) \)$") _DAT_FIELDS = {"size": "size", "crc": "crc32", "md5": "md5", "sha1": "sha1"} _HASH_KINDS = { - "hash_corrected", "hash_filled", "hash_cleared", "size_corrected", "size_removed", + "hash_updated", "hash_filled", "hash_cleared", "size_updated", "size_removed", } @@ -366,7 +382,7 @@ class ReportedChangesAreTheDiff(unittest.TestCase): ((c["system"], path_of[(c["system"], c["name"], c["destination"])]), c["field"], _norm(c["old"]), _norm(c["new"])) for c in changes - if c["kind"] in _HASH_KINDS or c["kind"] == "requirement_corrected" + if c["kind"] in _HASH_KINDS or c["kind"] == "required_aligned" } self.assertTrue(changed, "nothing changed, the comparison proves nothing") self.assertEqual(reported, changed) @@ -420,10 +436,10 @@ class ThePageShowsEachValueBeforeAndAfter(unittest.TestCase): self.report = _report( "recalbox", ["es_bios.xml"], - hashes_corrected=["psx/scph5501.bin md5"], requirements_corrected=1, + hashes_updated=["psx/scph5501.bin md5"], required_aligned=1, changes=[ - change("hash_corrected", "md5", "5626" + "0" * 28, "c523" + "1" * 28), - change("requirement_corrected", "required", True, False), + change("hash_updated", "md5", "5626" + "0" * 28, "c523" + "1" * 28), + change("required_aligned", "required", True, False), change("hash_filled", "sha1", None, "f" * 40), ], ) diff --git a/tests/test_exporters.py b/tests/test_exporters.py index d4181744c..985b0c1d6 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -1451,7 +1451,7 @@ class RecalboxExport(unittest.TestCase): self.assertNotEqual(element.get("mandatory"), "false") self.assertEqual( [c for c in landed_changes(exporter, systems).records - if c["kind"] == "requirement_corrected"], + if c["kind"] == "required_aligned"], [], ) diff --git a/tests/test_measurements.py b/tests/test_measurements.py index e38e3ad2a..099d18807 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -470,6 +470,22 @@ class SitePages(unittest.TestCase): def item(self, **values: object) -> dict: return {**self.ITEM, **values} + def test_the_data_page_claims_no_link_an_emulator_page_lacks(self) -> None: + """The Data page said each per-emulator database is "linked from its + page". An emulator page shows the proof lines, which link the record + lines and never the database.""" + with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as scratch: + path = Path(scratch) / "m.sqlite" + connection = sqlite3.connect(path) + connection.execute("CREATE TABLE meta (key TEXT)") + connection.commit() + connection.close() + text = "\n".join(sm.data_page_section(path)) + proofs = "\n".join(sm.proof_section([self.item()])) + self.assertNotIn("downloads/measurements/", proofs) + self.assertNotIn("linked from its page", text) + self.assertIn("downloads/measurements/.sqlite", text) + URL = f"{REPO_URL}/blob/{'e' * 40}/measurements/beetle_psx.jsonl" def record(self, line: int, proves=(), shown=None, observed=None) -> dict: diff --git a/tests/test_prose_style.py b/tests/test_prose_style.py index 9081f77f4..ac533c382 100644 --- a/tests/test_prose_style.py +++ b/tests/test_prose_style.py @@ -157,6 +157,17 @@ class NoGeneratedTypography(unittest.TestCase): offenders.append(f"{path.relative_to(ROOT)}:{number}: {line.strip()[:100]}") self.assertEqual(offenders, [], "\n".join(offenders[:40])) + def test_no_double_hyphen_dash_in_profiles(self): + """GLYPHS sees U+2014, not its ASCII spelling: "No separate upstream -- + written directly for the libretro API" reached the cruzes page as is.""" + dash = re.compile(r"[A-Za-z0-9)] -- [A-Za-z0-9(]") + offenders = [] + for path in sorted((ROOT / "emulators").glob("*.yml")): + for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + if dash.search(BACKTICKS.sub("", line)): + offenders.append(f"{path.relative_to(ROOT)}:{number}: {line.strip()[:100]}") + self.assertEqual(offenders, [], "\n".join(offenders)) + class NoMarketingVocabulary(unittest.TestCase): """The project bans these words in published text, and nothing checked diff --git a/tests/test_site_emulator_page.py b/tests/test_site_emulator_page.py index 9ffcb5842..f7e49ea5e 100644 --- a/tests/test_site_emulator_page.py +++ b/tests/test_site_emulator_page.py @@ -150,6 +150,15 @@ class WhatASearchSnippetSays(unittest.TestCase): self.assertTrue(plain.startswith(text[:-3])) self.assertIn(plain[len(text) - 3], " ,:") + def test_a_link_whose_text_holds_brackets_keeps_its_text_only(self): + """The Pioneer meta description published the whole link, anchor + included: the text `[BIOS] ...bin` stopped the pattern at its "]".""" + self.assertEqual( + gs._plain_markdown("**Start with** [`[BIOS] LaserActive PAC-N1 (Japan) (v1.02).bin`]" + "(#sha1-6973e2593e66). ares reads it."), + "Start with [BIOS] LaserActive PAC-N1 (Japan) (v1.02).bin. ares reads it.") + self.assertEqual(gs._plain_markdown("[a [b] c](x) and [d](y)"), "a [b] c and d") + def test_code_keeps_its_underscores(self): self.assertEqual(gs._plain_markdown("only `validate_schemas.py` and _this_"), "only validate_schemas.py and this") diff --git a/tests/test_site_inputs.py b/tests/test_site_inputs.py index 031ae8b10..0d08a5226 100644 --- a/tests/test_site_inputs.py +++ b/tests/test_site_inputs.py @@ -87,11 +87,48 @@ class TheGivenProfilesAndPlatforms(_Inputs): self.assertEqual(sitediagrams.registry()["retroarch"].get("release_pack"), False) +class TheCollectionDoesNotDependOnTheWorkingDirectory(unittest.TestCase): + """database.json names its bios folder "bios", and the collection model + read it from the working directory: started from tmp/, it found no copy + on disk and dropped five system sections and 2,500 collection paths.""" + + def test_same_sections_and_paths_from_another_directory(self) -> None: + import json + + import sitecollection + + db = json.loads((REPO_ROOT / "database.json").read_text(encoding="utf-8")) + here = sitecollection.collection_paths(db) + places = sitecollection._copy_places(db) + elsewhere = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp" if (REPO_ROOT / "tmp").is_dir() else None)) + self.addCleanup(shutil.rmtree, elsewhere) + cwd = os.getcwd() + os.chdir(elsewhere) + self.addCleanup(os.chdir, cwd) + self.assertEqual(sitecollection.collection_paths(db), here) + self.assertEqual(sitecollection._copy_places(db), places) + self.assertGreater(sum(map(len, here.values())), len(here)) + + def test_the_bios_folder_sits_beside_the_given_database(self) -> None: + import siteinputs + + siteinputs.use(siteinputs.SiteInputs(db=Path("/elsewhere/database.json"))) + self.addCleanup(siteinputs.use, siteinputs.SiteInputs()) + self.assertEqual(siteinputs.bios_dir({"bios_dir": "bios"}), Path("/elsewhere/bios")) + self.assertEqual(siteinputs.bios_dir({"bios_dir": "/abs/bios"}), Path("/abs/bios")) + + def test_the_image_tools_do_not_load_the_page_generator(self) -> None: + for module in ("make_og_cards.py", "fetch_site_images.py", "siterender.py"): + with self.subTest(module=module): + source = (REPO_ROOT / "scripts" / module).read_text(encoding="utf-8") + self.assertNotIn("from generate_site import", source) + + class NoPageModuleReadsTheRepositoryInputs(unittest.TestCase): """A helper that opens the repository's database, profiles or platform files itself bypasses the flags. siteinputs.current() is the one source.""" - MODULES = ("generate_site.py", "siterender.py", "sitediagrams.py") + MODULES = ("generate_site.py", "siterender.py", "sitediagrams.py", "sitecollection.py") INPUTS = {"database.json", "emulators", "platforms"} def test_no_literal_input_path(self) -> None: diff --git a/tests/test_site_privacy.py b/tests/test_site_privacy.py index 9bf1e0808..bf2dae448 100644 --- a/tests/test_site_privacy.py +++ b/tests/test_site_privacy.py @@ -51,6 +51,25 @@ class TheCounterAsksNothingOfTheDevice(unittest.TestCase): self.assertIn("countEvent(`search-none/${searchWords(query)}`)", self.code) self.assertIn("message.startsWith('No results for')", self.code) + def _shape(self, name: str): + """A search-shape predicate of count.ts, its regexes run by Python + (the counter's patterns use no syntax the two read differently).""" + body = re.search(rf"const {name} = \(query: string\): boolean =>(.*?);\n", self.code, re.S).group(1) + patterns = [re.compile(p) for p in re.findall(r"/(\^.*?\$|\[[^/]*?\]|\\d)/\.test\(query\)", body)] + digits = "(query.match(/\\d/g) ?? []).length <= 4" in body + return lambda q: all(p.search(q) for p in patterns) and ( + not digits or len(re.findall(r"\d", q)) <= 4) + + def test_a_number_alone_is_never_sent(self) -> None: + """An 8-digit phone number is valid hex and went out as a hash.""" + shapes = [self._shape(n) for n in ("isHash", "isFileName", "isFileStem")] + for query in ("55512345", "91234567", "0612345678", "+33612345678"): + with self.subTest(query=query): + self.assertFalse(any(shape(query) for shape in shapes)) + for query in ("b4430a3b", "scph5501", "scph5501.bin", "a" * 40): + with self.subTest(query=query): + self.assertTrue(any(shape(query) for shape in shapes)) + def test_one_endpoint(self) -> None: self.assertEqual(set(re.findall(r"https://[\w.-]+", self.code)), {"https://retrobios.goatcounter.com"}) @@ -72,6 +91,19 @@ class ThePageSaysWhatIsCounted(unittest.TestCase): with self.subTest(said=said): self.assertIn(said, page) + def test_the_page_names_what_the_counter_cannot_tell_apart(self) -> None: + """The page left out outbound clicks, which count.ts sends, said the + referrer went by domain when the origin goes, and promised a search + word goes out only as a file name while a nickname with digits does.""" + page = PRIVACY.read_text(encoding="utf-8") + code = COUNTER.read_text(encoding="utf-8") + self.assertIn("`out/${url.hostname}`", code) + self.assertIn("link to another site", page) + self.assertIn("from.origin", code) + self.assertIn("`https://www.google.com`", page) + self.assertIn("`dupont1985`", page) + self.assertIn("phone number, never is", page) + def test_the_installer_has_no_counter(self) -> None: installer = (REPO_ROOT / "install.py").read_text(encoding="utf-8") self.assertNotIn("goatcounter", installer.lower()) diff --git a/tests/test_site_reference.py b/tests/test_site_reference.py index a7619f931..a4a19b3a1 100644 --- a/tests/test_site_reference.py +++ b/tests/test_site_reference.py @@ -1104,7 +1104,7 @@ class SourceLinksOnlyWhereTheFileIs(unittest.TestCase): profile = {"source": "https://github.com/o/r", "source_commit": "e" * 40} text = sitesources._source_ref_markdown( profile, "Nesoid 2.5.0 libnes.so 0x455a4-0x45694 (setter), 0x8094-0x80ac") - self.assertEqual(text, "offset `0x455a4-0x45694` in `Nesoid 2.5.0 libnes.so`, " + self.assertEqual(text, "offset `0x455a4-0x45694` in `Nesoid 2.5.0 libnes.so` (setter), " "offset `0x8094-0x80ac` in `Nesoid 2.5.0 libnes.so`") def test_only_a_recorded_path_links(self): diff --git a/tests/test_site_render.py b/tests/test_site_render.py index 09d25c7b1..6cbaeb078 100644 --- a/tests/test_site_render.py +++ b/tests/test_site_render.py @@ -257,6 +257,47 @@ class AnAliasTreeIsListedOnItsParentsPage(unittest.TestCase): self.assertEqual([t["ref"] for t in trees], ["freeintv-overlays"]) +class CitationsKeepTheirAnnotations(unittest.TestCase): + """split_source_ref drops annotations for profile_sync, and the cards + reused it: abuse's two lsf lines lost "(lsf default)" and "(lsf + override)", the only words saying which one wins.""" + + @mock.patch("sitesources.tree_holds", lambda *_args: True) + def test_each_part_shows_what_the_profile_wrote_beside_it(self): + import sitesources + + profile = {"source": "https://github.com/Xenoveritas/abuse", + "source_commit": "a" * 40} + ref = ("src/loader2.cpp:284 (lsf default), src/netcfg.cpp:204 (lsf override), " + "libretro.cpp:1520-1521 candidates_a1200, 30-31 (see ), src/x.c:1") + (label, items), = sitesources.source_ref_items(profile, ref) + texts = [text for text, _role in items] + self.assertTrue(texts[0].endswith(") (lsf default)"), texts[0]) + self.assertTrue(texts[1].endswith(") (lsf override)"), texts[1]) + self.assertTrue(texts[2].endswith(") candidates_a1200"), texts[2]) + self.assertTrue(texts[3].endswith(") (see <system_dir>)"), texts[3]) + self.assertTrue(texts[4].endswith(")"), texts[4]) + + def test_the_annotation_is_what_follows_the_location(self): + from sourceref import ref_annotation + + self.assertEqual(ref_annotation("munt ROMInfo.cpp:206-213"), "") + self.assertEqual(ref_annotation("source atari800/src/sysrom.c:91"), "") + self.assertEqual(ref_annotation("doc/notes.txt (read at boot)"), "(read at boot)") + self.assertEqual(ref_annotation("a.c (see b.c:12)"), "(see b.c:12)") + self.assertEqual(ref_annotation("0x53c338-0x53c36f (dreamBios row)"), "(dreamBios row)") + + def test_a_binary_citation_keeps_its_annotation(self): + import sitesources + + (_label, items), = sitesources.source_ref_items( + {}, "demul.exe 0x6b4d80-0x6b4ea3 (dc table), 0x53ca4d-0x53ca7e (load, fatal)") + self.assertEqual([text for text, _ in items], [ + "offset `0x6b4d80-0x6b4ea3` in `demul.exe` (dc table)", + "offset `0x53ca4d-0x53ca7e` in `demul.exe` (load, fatal)", + ]) + + class ForgesUpstreamDoesNotRead(unittest.TestCase): """Profiles pinned on SourceForge, voidpoint.io, Bitbucket or Software Heritage showed no revision, and their citations were plain code.""" diff --git a/website/src/data/source-paths.json b/website/src/data/source-paths.json index d0f00e7a2..b5e05212f 100644 --- a/website/src/data/source-paths.json +++ b/website/src/data/source-paths.json @@ -2878,11 +2878,19 @@ "github.com/coelckers/prboom-plus@969515162c5aebea4ad7a125ee178dcad3d576ad": [ "prboom2/CMakeLists.txt", "prboom2/data/CMakeLists.txt", + "prboom2/doc/prboom-plus.6", "prboom2/src/CMakeLists.txt", "prboom2/src/MUSIC/flplayer.c", "prboom2/src/SDL/i_system.c", + "prboom2/src/d_client.c", "prboom2/src/d_main.c", + "prboom2/src/e6y.c", + "prboom2/src/g_game.c", + "prboom2/src/gl_hires.c", + "prboom2/src/gl_shader.c", + "prboom2/src/m_menu.c", "prboom2/src/m_misc.c", + "prboom2/src/r_demo.c", "prboom2/src/w_wad.c" ], "github.com/crawl/crawl@a5ed93406571b9682050cfc318b15b9f637d4e7e": [ diff --git a/website/src/scripts/count.ts b/website/src/scripts/count.ts index 31c5473e7..1a798738c 100644 --- a/website/src/scripts/count.ts +++ b/website/src/scripts/count.ts @@ -4,11 +4,12 @@ // not to be tracked (Do Not Track, Global Privacy Control), from a local // build, or from a page not served at the site's address. // -// Sent: the page path (never its query or #anchor), the domain of the site -// the reader came from, and the name of an action. An action is a click on +// Sent: the page path (never its query or #anchor), the origin of the site +// the reader came from (scheme and host, no path), and the name of an +// action. An action is a click on // a copy button, a pack or file download, a link that leaves the site, a -// site search that found nothing (its words only when they are a file -// name or a hash), and the answer to "Did this page help?". +// site search that found nothing (its words only in the shapes below), +// and the answer to "Did this page help?". const ENDPOINT = 'https://retrobios.goatcounter.com/count'; const HOST = 'abdess.github.io'; @@ -37,7 +38,7 @@ export const countEvent = (name: string): void => { send({ p: name.slice(0, 120), t: pagePath(), e: 'true' }); }; -// The site the reader came from, by domain alone: a path can carry a +// The site the reader came from, by its origin alone: a path can carry a // search or a private thread. const referrer = (): string => { try { @@ -104,11 +105,16 @@ const onClick = (event: MouseEvent): void => { // Search // A search that found nothing names a missing page. Its words are sent -// only in a shape no person's details take: a hash, a file name -// ("scph5501.bin"), or one word of letters and at most four digits -// ("scph5501", "ps3updat"). Any other search is counted with no words, -// as "other": a name, a phone number or an address is never sent. -const isHash = (query: string): boolean => /^(?:[0-9a-f]{8}|[0-9a-f]{32}|[0-9a-f]{40}|[0-9a-f]{64})$/.test(query); +// only in three shapes: a hash with at least one letter, a file name +// ("scph5501.bin"), or one word that starts with a letter and holds at most +// four digits ("scph5501", "ps3updat"). Any other search is counted with no +// words, as "other". A number alone, a phone number among them, is never +// sent. A nickname of the third shape ("dupont1985") is, as wiki/privacy.md +// says: nothing tells it from a file name. +// An 8-digit phone number is valid hex: a hash needs a letter, and a CRC32 +// written in digits only is counted as "other". +const isHash = (query: string): boolean => + /^(?:[0-9a-f]{8}|[0-9a-f]{32}|[0-9a-f]{40}|[0-9a-f]{64})$/.test(query) && /[a-f]/.test(query); // The extensions of the files the collection holds: "john.smith" is no file. const isFileName = (query: string): boolean => /^[\w.+-]{1,60}\.(?:bin|rom|zip|7z|pup|keys|img|dat|bios|ic\d+|u\d+|sys|nvm|mec|rom1|j64|pce|col|sms|gb|gbc|gba|nds|cfg|chd|iso|txt|fw|xml|yaml|pak|pk3|grp)$/.test(query); diff --git a/wiki/advanced-usage.md b/wiki/advanced-usage.md index aff9b10b6..ecc7a801d 100644 --- a/wiki/advanced-usage.md +++ b/wiki/advanced-usage.md @@ -19,7 +19,8 @@ boot, with no message saying why. It is not a guarantee either: source profiles can document files nobody has dumped or that only the user can provide, all of them visible in the [gap analysis](../gaps.md). -From a clone of the repository, `python scripts/generate_pack.py --platform +A clone carries the whole collection, several gigabytes, and `git clone +--depth 1` leaves its history out. From that clone, `python scripts/generate_pack.py --platform retroarch --region us` builds a narrower pack, `--required-only` the bare minimum each core needs to start, and `--no-game-data` keeps the firmware and leaves out the files a game reads. `--help` lists every way to build your own, @@ -134,9 +135,12 @@ Three rules keep a filtered pack usable: - **No system is ever emptied.** If nothing matches, the whole group is kept. `--region us` still ships `disksys.rom`, because the Famicom Disk System has no American BIOS. -- **Region-free files always survive.** `psxonpsp660.bin`, `ps1_rom.bin` and - `openbios.bin` are declared `[world]`: they add a capability rather than - competing regionally. +- **Region-free files survive.** A file one of the platform's cores declares + `[world]` is kept. SwanStation declares `psxonpsp660.bin` and `ps1_rom.bin` + that way, DuckStation `openbios.bin`. The tag comes from each core: + PCSX ReARMed reads the region inside `psxonpsp660.bin` and files it as + `[japan]`, so on MinUI, NextUI and MyMinUI, where it is the only PlayStation + core, `--region us` drops it. - **Untagged files always survive.** Most BIOS are not regional and carry no `region:` at all. diff --git a/wiki/privacy.md b/wiki/privacy.md index 16ccd5c07..70f7972dc 100644 --- a/wiki/privacy.md +++ b/wiki/privacy.md @@ -16,7 +16,8 @@ When a page opens, your browser sends one request to carries: - the page address, without the part after `?` or `#`; -- the site you came from, by its domain only (`google.com`, `reddit.com`). +- the site you came from, by its address without a path + (`https://www.google.com`). GoatCounter reads the browser name and the country from that request. It does not keep your IP address. To tell a reload from a second visitor it keeps a @@ -25,9 +26,13 @@ code made from the request for 8 hours, then drops it. These actions are counted the same way, with the page they happened on: - a click on a copy button, a pack download or a file link; +- a click on a link to another site, by that site's domain (`reddit.com`); - a site search that found nothing, so a missing page shows up. Its words are sent only when they read as a file name (`scph5501.bin`, `scph5501`) or a - hash. Any other search is counted as "other", without its words; + hash with at least one letter. A word of letters with a few digits is sent + as typed, so a nickname written that way (`dupont1985`) would be too. A + number alone, such as a phone number, never is. Any other search is counted + as "other", without its words; - the answer to "Did this page help?". Not sent: your screen size, your language, a search that found results. diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md index 9e32c6481..9c8d73b1f 100644 --- a/wiki/testing-guide.md +++ b/wiki/testing-guide.md @@ -42,7 +42,9 @@ tests are reported as skipped, so install them before a pull request full-screen tests drive `install.py --tui` on a pseudo-terminal and read the screen back with `pyte`, and fuzz the key decoder with `hypothesis`: both skip without them (`pip install pyte hypothesis`). `pip install -e ".[dev]"` from the -checkout installs all of these at once, from the `dev` extra of `pyproject.toml`. +checkout installs all of these at once, from the `dev` extra of `pyproject.toml`, +which sets lower bounds only. The contributing guide's checklist reads the exact +versions CI pins from `validate.yml`. No test framework beyond the standard library `unittest` module. ## Modules at a glance @@ -377,6 +379,9 @@ to the way the project writes: line naming a .info, core-info or System.dat no "is correct", "is accurate", "still declares", "omits" or "missing". The note states what that file declares and what the code reads. +- No pointer at a review in a comment, a docstring or a test name: a review + number in parentheses, a note file named after one, a test module or class + named after a review. The comment says what the code does. Formatting stays: bold labels, lists, tables and admonitions make a page easier to use. When markup shows raw on the site, the rendering is fixed and @@ -460,8 +465,10 @@ A commit subject is English, 50 characters at most, `type: description` with a lowercase word after the colon. The types are `feat`, `refactor`, `chore`, `docs` and `fix`. The message does not use bug, regression, revert, dead code, broken, wrong, hack, workaround or implement (derived forms included), nor the -marketing vocabulary above, and carries no Co-Authored-By trailer. A body -explains the why when the subject cannot: the scenario that failed, the +marketing vocabulary above, and carries no Co-Authored-By trailer. It says +what the change does, never which review asked for it: "review 3", "audit 12" +or "round 5" with its number points at notes no clone holds and is refused. A +body explains the why when the subject cannot: the scenario that failed, the upstream `file:line` that decides it, what users will see. ``` @@ -469,6 +476,14 @@ feat: add retrobat platform support refactor: extract shared utils to common.py ``` +`scripts/commit_rules.py` holds these rules. Git does not copy hooks with a +clone, so the commit-msg hook that runs them is set up once per clone: + +```bash +printf '#!/bin/sh\nexec python3 scripts/commit_rules.py "$1"\n' > .git/hooks/commit-msg +chmod +x .git/hooks/commit-msg +``` + Files are staged by name, never with `git add -A` or `git add .`, which sweep in deletions and work that is not yours. Generated artifacts (`database.json`, `retired.json`, `derivable.json`, `install/*.json`, `README.md`, `CONTRIBUTING.md`, `exports/`, @@ -512,6 +527,10 @@ of main passes, not every commit under it. Modules that need real artifacts skip themselves when those artifacts are absent, so `test_pack_integrity` is a no-op in CI (no `dist/`) and a real check locally. +The exporter fidelity cases go the other way: they compare with each platform's +own file, which `python scripts/export_native.py --all --fetch --output-dir +tmp/native` downloads into `.cache/upstream-native/`. CI runs it before the +suite, and a clone without that cache skips them. Run `python -m unittest discover -s tests -t .` before pushing. Tests must pass before merge. If a test fails in CI, reproduce locally with: diff --git a/wiki/tools.md b/wiki/tools.md index 763189ae2..a99c6ba6c 100644 --- a/wiki/tools.md +++ b/wiki/tools.md @@ -817,8 +817,9 @@ hard-links `.cache/large/`, `data/` and the gitignored `bios/` files from the main checkout. Before each job it is fast-forwarded, or rebased when it holds unpushed commits, onto `origin/main`. A job that finds anything uncommitted there stops as `blocked` and cleans nothing. Commits name their files -(`git commit -- `), and their subjects are checked against the -commit-msg hook when the registry loads. A job that leaves a change no commit +(`git commit -- `), and their subjects are checked against +`scripts/commit_rules.py`, the rules the commit-msg hook runs, when the +registry loads. A job that leaves a change no commit rule takes fails. `--push` sends the batch: rebase on `origin/main` (a conflict keeps the batch @@ -1356,6 +1357,7 @@ before a release (wiki/release-process.md). Its lines stay in | `slots.py` | Destinations the platform and a core both claim, and how the pack settles each (`--platform`, `--target`, `--json`, `--strict`, and pipeline step 3b passes the run's own `--target`) | | `region_audit.py` | Confront every profile `region:` with the regions No-Intro names for the same dump (`--json`, `--db`, `--emulators-dir`). An opinion, never an authority: the code decides, and a disagreement is a line to read in the emulator's source | | `validate_pr.py` | Validate BIOS files in pull requests, post markdown report | +| `pr_checks.py` | Run the checks `validate.yml` runs on a pull request, in its order, past a failure as CI does, and name each check that failed or whose tool is absent (`--list` prints them) | | `validate_schemas.py` | Validate the data contracts: schemas and semantic invariants. `--source-only` checks `emulators/` and `platforms/` alone, which is what PR validation runs | | `generate_event_schema.py` | Write `schemas/install-events.schema.json` from `EVENT_KEYS` in `install.py` (`--check` exits 1 when the file is stale, which `validate_schemas.py` also reports) | | `generate_installer_doc.py` | Rewrite the generated blocks of `wiki/installer.md` from `install.py`: the tables (options, events, exit codes, keys per route, card markers), the line menu, and the run samples `install.py` prints in a sandbox against the committed `install/*.json`, so a new list rewrites the page (`--check` exits 1 when a block is stale, `--page FILE` writes another copy of the page). Pipeline step 7 | @@ -1391,7 +1393,7 @@ Further options, by script: | Script | Options | |--------|---------| -| `auto_fetch.py` | `--create-issues` prints a GitHub issue body per file still missing. It posts nothing | +| `auto_fetch.py` | `--create-issues` prints a GitHub issue body per platform for the files still missing, with their full hashes. It posts nothing | | `check_buildbot_system.py` | `--update` refreshes the entries found stale, `--json`, `--registry` | | `check_freshness.py` | `--cache-dir`, `--native-cache-dir` (originals of the native export) and `--truth-dir` move the caches it reads | | `check_release.py` | `--tag`, `--sums dist/SHA256SUMS.txt`, `--record` (default `release.json`), `--json` |