mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 12:33:53 -06:00
chore: merge site and docs fixes, fifth pass
This commit is contained in:
commit
b9f309940c
67 files changed
+1495
-786
No files matched your search
@@ -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:
|
||||
|
||||
+25
-10
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 <system_dir>/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"
|
||||
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
+14
-14
@@ -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: "<hires image>.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, <config dir>/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)"
|
||||
+4
-4
@@ -23,7 +23,7 @@ systems:
|
||||
# 1. Base path set to <system_dir>/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:
|
||||
# <system_dir>/keropi/iplrom.dat (or iplrom30.dat, iplromco.dat, iplromxv.dat)
|
||||
@@ -41,7 +41,7 @@ systems:
|
||||
# <system_dir>/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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
+7
-7
@@ -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": [
|
||||
|
||||
+106
-106
@@ -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": [
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
+133
-133
File diff suppressed because it is too large.
Load diff
+106
-106
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
+56
-56
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
+4
-4
@@ -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": [
|
||||
|
||||
+21
-26
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
-11
@@ -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,
|
||||
|
||||
@@ -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"))
|
||||
|
||||
+57
-11
@@ -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.
|
||||
|
||||
+12
-172
@@ -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'<div class="rb-timestamp">{data_as_of()}.</div>'
|
||||
|
||||
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
+12
-12
@@ -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("")
|
||||
|
||||
@@ -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
|
||||
@@ -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/<emulator>.sqlite` the records of one emulator, linked "
|
||||
f"from its page. The [Measurements]({EXPLORER_PAGE}) page reads "
|
||||
"`downloads/measurements/<emulator>.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).",
|
||||
"",
|
||||
|
||||
@@ -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:
|
||||
|
||||
+18
-5
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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}`")
|
||||
|
||||
@@ -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()
|
||||
+95
-2
@@ -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/<CODE>`", 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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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):
|
||||
|
||||
+28
-12
@@ -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/<platform>.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),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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"],
|
||||
[],
|
||||
)
|
||||
|
||||
|
||||
@@ -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/<emulator>.sqlite", text)
|
||||
|
||||
URL = f"{REPO_URL}/blob/{'e' * 40}/measurements/beetle_psx.jsonl"
|
||||
|
||||
def record(self, line: int, proves=(), shown=None, observed=None) -> dict:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 <system_dir>), 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."""
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
+7
-2
@@ -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.
|
||||
|
||||
+22
-3
@@ -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:
|
||||
|
||||
+5
-3
@@ -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 -- <paths>`), and their subjects are checked against the
|
||||
commit-msg hook when the registry loads. A job that leaves a change no commit
|
||||
(`git commit -- <paths>`), 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` |
|
||||
|
||||
Reference in new issue
Block a user