chore: merge audit fixes (site)

# Conflicts:
#	emulators/flycast.yml
#	emulators/primehack.yml
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 11:48:30 +02:00
commit 8643d4a4ea
50 files changed
+1352 -176

No files matched your search

+7 -6
View File
@@ -5,6 +5,7 @@ source: "https://github.com/libretro/81-libretro"
upstream: "https://github.com/charlierobson/EightyOne"
profiled_date: "2026-03-22"
source_commit: "fa7094910d040baa5fd8b11dbf6a1a618330ecd9"
upstream_commit: "4de3cdb8cff0060ad03e43ae2681b0628a9c5f5d"
core_version: "1.0a"
display_name: "Sinclair - ZX 81 (EightyOne)"
cores: ["81"]
@@ -23,10 +24,10 @@ systems:
notes: |
Libretro port embeds zx81.rom and dkchr.rom as C arrays (xxd -i).
Core never accesses filesystem for ROMs. Hardcodes MACHINEZX81.
ref: 81-libretro/81/zx81/snap.c:487-508, src/libretro.cpp:393
ref: source 81/zx81/snap.c:487-508, source src/libretro.cpp:393
Original EightyOne supports 15+ machines, loads ROMs from ROM/ directory.
ref: EightyOne/Source/zx81/zx81.cpp:309, EightyOne/Source/HW_.cpp:2734
ref: upstream Source/zx81/zx81.cpp:309, upstream Source/HW_.cpp:2734
.info has no firmware declarations (firmware_count absent).
@@ -270,7 +271,7 @@ files:
mode: standalone
size: 8192
md5: 5c11c61a2dd2ca4bf39328d9ff42d289
source_ref: "upstream EightyOne/Source/ROMs/FDCs/interface1.edition1.rom"
source_ref: "upstream Source/ROMs/FDCs/interface1.edition1.rom"
- name: "+d.g+dos.v1-a.rom"
system: sinclair-zx-spectrum
required: false
@@ -482,7 +483,7 @@ files:
mode: standalone
size: 16384
md5: 9f6f56003307b9d55be96f406fdbf2b1
source_ref: "upstream EightyOne/Source/ROMs/spectrum48.arabic.rom"
source_ref: "upstream Source/ROMs/spectrum48.arabic.rom"
- name: spectrum48.arabic.version1.rom
system: sinclair-zx-spectrum
required: false
@@ -599,7 +600,7 @@ files:
size: 16384
md5: b7893ad25728fbf6410f08eddd4a1cad
note: "Microdigital TK90X (Spectrum clone)."
source_ref: "upstream EightyOne/Source/ROMs/tk90.rom"
source_ref: "upstream Source/ROMs/tk90.rom"
- name: tk95.rom
system: sinclair-zx-spectrum
required: false
@@ -608,4 +609,4 @@ files:
size: 16384
md5: b7893ad25728fbf6410f08eddd4a1cad
note: "Microdigital TK95 (Spectrum clone)."
source_ref: "upstream EightyOne/Source/ROMs/tk95.rom"
source_ref: "upstream Source/ROMs/tk95.rom"
+1
View File
@@ -6,6 +6,7 @@ source: "https://github.com/ARMSX2/ARMSX2"
upstream: "https://github.com/PCSX2/pcsx2"
profiled_date: "2026-10-04"
source_commit: "d383b3636947565d9201f81e32178d0288623d8d"
upstream_commit: "36907f4b70d149b05c2700acae687bedb720a7af"
core_version: "2.8"
display_name: "Sony - PlayStation 2 (ARMSX2)"
cores:
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/johndoe6345789/azahar_libretro"
upstream: "https://github.com/azahar-emu/azahar"
profiled_date: "2026-03-23"
source_commit: "9dd12da328c753806cbd4f2b0e048fced97ae97d"
upstream_commit: "9b045bf8370d035cacd99e5ad19a082df9e3b262"
core_version: "Git"
display_name: "Nintendo - 3DS (Azahar)"
cores: [azahar]
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/libretro/cannonball"
upstream: "https://github.com/djyt/cannonball"
profiled_date: "2026-03-23"
source_commit: "0d83575d920ea4c0a280847a19d7802f673e6a68"
upstream_commit: "27493ebf62be3498dff93ed6a45e8e2db819bae1"
core_version: "1.0"
display_name: "Cannonball"
cores:
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/libretro/mame2015-libretro"
upstream: "https://github.com/mamedev/mame/tree/mame0160"
profiled_date: "2026-03-23"
source_commit: "7adbf440d5f554097f58f8c6ebb673adc4320a31"
upstream_commit: "0147bb4cc82e642cf75151f039f240eb20989fbc"
core_version: "0.160"
display_name: "Philips CDi (CDi 2015)"
cores: [cdi2015]
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/acidtech/Dolphin-MMJR"
upstream: "https://github.com/weihuoya/dolphin"
profiled_date: "2026-08-11"
source_commit: "1b095b64857c2eabb49a42cc56ffd9d7881bc263"
upstream_commit: "0f4c971326ae9389b3ad55b0fefacb708d148f4d"
core_version: "11460 - bankaimaster999"
display_name: "Nintendo - GameCube / Wii (Dolphin MMJR)"
cores:
+3 -1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/nachoverdon/Dolphin-MMJR2"
upstream: "https://github.com/dolphin-emu/dolphin"
profiled_date: "2026-08-11"
source_commit: "4d0fdb8eea616320e967408cf5547c72a95196f3"
upstream_commit: "4ff214cdd26f135251d2266d676ed72dc1c2f8c9"
core_version: "2.0-18025"
display_name: "Nintendo - GameCube / Wii (Dolphin MMJR2)"
cores:
@@ -22,7 +23,8 @@ notes: |
selective merge of Dolphin Official carrying that label rather than a
checkout of it: at Dolphin 5.0-18025 the memory subsystem has already moved
behind Core::System, and here it has not, so no single Dolphin revision
matches and no upstream pin is declared.
matches the tree. upstream_commit is the Dolphin revision the upstream
citations were read at, not a base of this tree.
ref: Source/Android/app/build.gradle:32, Source/Android/app/build.gradle:135
Two roots. The user directory is mmjr-revamp at the root of external storage.
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/dolphin-emu/dolphin"
logo: "https://raw.githubusercontent.com/dolphin-emu/dolphin/master/Data/dolphin-emu.svg"
profiled_date: "2026-03-23"
source_commit: "4d23cf151640eb810cb1b8e9d9fc922cf59c0b87"
upstream_commit: "f5dfb7e3d36ce24551ed0bb886b79f3f192dac9d"
core_version: "Git"
display_name: "Nintendo - GameCube / Wii (Dolphin)"
cores:
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://codeberg.org/schelling/dosbox-pure"
logo: "https://raw.githubusercontent.com/schellingb/dosbox-pure/main/images/logo.png"
profiled_date: "2026-03-23"
source_commit: "cdaea10ce8cac231bc341ca05d4c5e4e5fe0fbd5"
upstream_commit: "cdaea10ce8cac231bc341ca05d4c5e4e5fe0fbd5"
core_version: "1.0-preview7"
display_name: "DOS (DOSBox-Pure)"
cores: [dosbox_pure]
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/libretro/easyrpg-libretro"
upstream: "https://github.com/EasyRPG/Player"
profiled_date: "2026-03-23"
source_commit: "a3fbb7c24fd5a39fd95388d09135256a6b19f1e4"
upstream_commit: "8acd2e17c6a8a0a75cb7ce95625c6bdc89aebe1a"
core_version: "0.8.1.0"
display_name: "RPG Maker 2000/2003 (EasyRPG)"
cores:
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/libretro/fbalpha"
upstream: "https://github.com/barry65536/FBAlpha"
profiled_date: "2026-10-17"
source_commit: "0581797db6fdffd826086b053ced4b6b29bb6678"
upstream_commit: "416948158d88ba17f87813419ddc3c1209ef68c0"
pin_frozen: "ROCKNIX 20261001 builds this revision as fbalpha2019. Later commits on libretro/fbalpha are not part of that build."
core_version: "v0.2.97.44"
display_name: "Arcade (FB Alpha 2019)"
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/finalburnneo/FBNeo"
logo: "https://raw.githubusercontent.com/finalburnneo/FBNeo/master/projectfiles/xcode/Emulator/Assets.xcassets/AppIcon.appiconset/icon_512.png"
profiled_date: "2026-03-23"
source_commit: "63c4190785cadd5ff84483399375871ed6e98754"
upstream_commit: "8d47450deff3802a61559108f12bb0c846c98e36"
core_version: "v1.0.0.2"
display_name: "Arcade (FinalBurn Neo)"
cores:
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/libretro/libretro-fsuae"
upstream: "https://github.com/FrodeSolheim/fs-uae"
profiled_date: "2026-03-26"
source_commit: "089bb69207fe8fa5eec0006d1d759fb6426f4027"
upstream_commit: "f362278ccd4c60991caac3b4d240d4a3f751bea2"
core_version: "v5.0.7"
display_name: "Commodore - Amiga (FS-UAE)"
cores:
+8 -6
View File
@@ -4,9 +4,10 @@ core_classification: enhanced_fork
source: "https://github.com/antonioginer/GroovyMAME"
upstream: "https://github.com/mamedev/mame"
source_commit: "953db38f4aaab1d75faa4ac79e1b45686214f987"
upstream_commit: "f34f02505e32c1993c6a782b6814232cbfc74e36"
logo: "https://raw.githubusercontent.com/mamedev/mame/master/docs/source/images/MAMElogo.svg"
profiled_date: "2026-08-08"
core_version: "0.288"
core_version: "0.289"
display_name: "Arcade (GroovyMAME)"
cores:
@@ -122,13 +123,14 @@ systems:
notes: |
MAME fork adding Switchres 2.2.2 for CRT modeline generation, frame delay
and low latency vsync. Branch groovymame0288, BARE_BUILD_VERSION 0.288
(makefile:1508). TARGET := mame with no SUBTARGET override (makefile:274-282),
and low latency vsync. Branch groovymame0289, BARE_BUILD_VERSION 0.289
(makefile:1513). TARGET := mame with no SUBTARGET override (makefile:274-282),
so the build carries the full machine set.
src/mame and src/devices are byte identical to mamedev/mame tag mame0288,
so ROM requirements are MAME 0.288's. The fork's changes sit in src/osd and
3rdparty/switchres and pull in no external data file: monitor presets are
src/mame and src/devices are byte identical to mamedev/mame tag mame0289,
so ROM requirements are MAME 0.289's. The fork's changes sit in src/osd,
src/emu, src/frontend, 3rdparty/switchres, 3rdparty/emusync and
3rdparty/lz4, and pull in no external data file: monitor presets are
compiled in (3rdparty/switchres/monitor.cpp:124-145), and switchres.ini plus
display#.ini are optional user configuration read only when the switchres_ini
option is set (src/osd/modules/switchres/switchres_module.cpp:166,173).
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/LLeny/holani-retro"
upstream: "https://github.com/LLeny/holani"
profiled_date: "2026-03-24"
source_commit: "19b974f77247a937369adf48280d2b34a5a66245"
upstream_commit: "4fa0ee920c63a9cf8d07b974ae77fb15161ff967"
core_version: "0.9.9"
display_name: "Atari - Lynx (Holani)"
cores: [holani]
+1
View File
@@ -4,6 +4,7 @@ core_classification: enhanced_fork
source: "https://github.com/DirtBagXon/hypseus-singe"
upstream: "https://github.com/mirror/daphne-emu"
source_commit: "cda87965fe0a031d027b2253c6715d37cca6b56a"
upstream_commit: "cd214c6046365047704f8a0563928970b30444e6"
profiled_date: "2026-08-08"
core_version: "v3.0.0"
display_name: "Arcade (Hypseus Singe)"
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/libretro/ishiiruka"
upstream: "https://github.com/Tinob/Ishiiruka"
profiled_date: "2026-03-24"
source_commit: "f228bde5fbf4861ba749a229803addcd6238663f"
upstream_commit: "cba6d7279b44894297d8c1dde1effdbba3605c32"
core_version: "Git"
display_name: "Nintendo - GameCube / Wii (Ishiiruka)"
cores:
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/nikita36078/J2ME-Loader"
author: "Yury Kharchenko"
profiled_date: "2026-08-11"
source_commit: "f723a190c0bdb44b31c3bc0ead6f8665c7ea517d"
upstream_commit: "c2fdac77edba94709796b67ed5493915f31d620f"
core_version: "0.87.1"
display_name: "Mobile - J2ME (JL-Mod)"
cores:
+1
View File
@@ -6,6 +6,7 @@ source_branch: kronos
upstream: "https://github.com/FCare/Kronos"
profiled_date: "2026-03-24"
source_commit: "3791ffb2221fda61d70aec5cca376418caf05669"
upstream_commit: "d0384bc33aad06bfa9cf5cd308c4bd7ae6cfc814"
core_version: "v2.6.0"
display_name: "Sega - Saturn/ST-V (Kronos)"
cores:
+4 -4
View File
@@ -57,7 +57,7 @@ files:
required: false
max_size: 4194304
description: "DVD player ROM"
source_ref: "upstream pcsx2/ps2/BiosTools.cpp:189-229, 339"
source_ref: "source pcsx2/ps2/BiosTools.cpp:189-229, 339"
note: >
DVD player ROM. Tries <biospath>.rom1 (appended) then <biosbase>.rom1
(extension replaced). Silently skipped if not found.
@@ -67,7 +67,7 @@ files:
required: false
max_size: 524288
description: "Chinese ROM extension"
source_ref: "upstream pcsx2/ps2/BiosTools.cpp:189-229, 340"
source_ref: "source pcsx2/ps2/BiosTools.cpp:189-229, 340"
note: >
Chinese region ROM extension. Same naming convention as rom1.
Only present on Chinese region consoles.
@@ -78,7 +78,7 @@ files:
hle_fallback: true
size: 1024
description: "NVRAM / EEPROM data"
source_ref: "upstream pcsx2/CDVD/CDVD.cpp:196-227"
source_ref: "source pcsx2/CDVD/CDVD.cpp:196-227"
note: >
Console EEPROM data (language, timezone, region, iLink ID, OSD settings).
Path derived from BIOS path with extension replaced to .nvm.
@@ -90,7 +90,7 @@ files:
hle_fallback: true
size: 4
description: "Mechacon version"
source_ref: "upstream pcsx2/CDVD/CDVD.cpp:229-245"
source_ref: "source pcsx2/CDVD/CDVD.cpp:229-245"
note: >
Mechacon (disc drive controller) version as u32.
Auto-created with default 0x00020603 if missing.
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/r-type/mame2009-libretro"
upstream: "https://github.com/mamedev/mame/tree/mame0135u4"
profiled_date: "2026-03-24"
source_commit: "45048806fbc0fea82709a75a523f2eda945e0a47"
upstream_commit: "c285eb9bcd3505c14a338e29f251ba00776cc75c"
core_version: "0.135u4"
display_name: "Arcade (MAME 2009)"
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/mamedev/mame/tree/mame0139"
logo: "https://raw.githubusercontent.com/mamedev/mame/master/docs/source/images/MAMElogo.svg"
profiled_date: "2026-03-22"
source_commit: "dff8aadd1c3f38215af3955746d6e19abe0ddcea"
upstream_commit: "f4f24ba7274010fb8ccb84548b5ca95c2be2f85b"
core_version: "0.139"
display_name: "Arcade (MAME 2010)"
mame_version: "0.139"
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/mamedev/mame/tree/mame0160"
logo: "https://raw.githubusercontent.com/mamedev/mame/master/docs/source/images/MAMElogo.svg"
profiled_date: "2026-10-20"
source_commit: "c8fe6c7399f24466808b67933b2b96feea188e0b"
upstream_commit: "0147bb4cc82e642cf75151f039f240eb20989fbc"
core_version: "0.160"
display_name: "Arcade (MAME 2014)"
mame_version: "0.160"
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/mamedev/mame/tree/mame0160"
logo: "https://raw.githubusercontent.com/mamedev/mame/master/docs/source/images/MAMElogo.svg"
profiled_date: "2026-03-24"
source_commit: "7adbf440d5f554097f58f8c6ebb673adc4320a31"
upstream_commit: "0147bb4cc82e642cf75151f039f240eb20989fbc"
core_version: "0.160"
display_name: "Arcade (MAME 2015)"
mame_version: "0.160"
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/mamedev/mame/tree/mame0174"
logo: "https://raw.githubusercontent.com/mamedev/mame/master/docs/source/images/MAMElogo.svg"
profiled_date: "2026-03-22"
source_commit: "ae07c2f88ff2482ba9f50ffc8c9e7e6fbfe97d0a"
upstream_commit: "68785dccfee3208163c8169abc5c6d01becf36d4"
core_version: "0.174"
cores:
+1
View File
@@ -6,6 +6,7 @@ source: "https://github.com/JesseTG/melonds-ds"
upstream: "https://github.com/melonDS-emu/melonDS"
profiled_date: "2026-10-04"
source_commit: "f394adbacb5722ee97c1b37c8064da9a25818310"
upstream_commit: "906e9ebb27da8c6a715cd7abab4abfe8a8d29427"
core_version: "1.4.0"
display_name: "Nintendo - DS (melonDS DS)"
systems:
+1
View File
@@ -9,6 +9,7 @@ source: "https://github.com/libretro/Mesen-S"
upstream: "https://github.com/SourMesen/Mesen-S"
profiled_date: "2026-03-24"
source_commit: "9e4fdeb9b336470bc96beb8765b2e79c86a2da1e"
upstream_commit: "89199f298a95345a893d814525b6668567a6d490"
core_version: "0.4.0"
cores: [mesen-s, mesen_s]
systems: [nintendo-snes, nintendo-gb, nintendo-gbc, nintendo-super-game-boy, nintendo-satellaview]
+2 -1
View File
@@ -9,6 +9,7 @@ profiled_date: "2026-09-17"
source_commit:
standalone: "20f497c9620a7f4e55b7752e1d22734f9d7492fa"
libretro: "4c4e069f16a8ad6ed79e046046c56d51558c7daf"
upstream_commit: "fabc9a62174f8734a113df6d244f5539ef6b8fcf"
core_version:
standalone: "2.2.1"
libretro: "2.0.0"
@@ -620,7 +621,7 @@ files:
validation: [size]
sha256: "990bf1956f10207d8781b619eb74f89b00d921c8d45c95c334c16c8cceca09ad,fb4a898eb93b19b36773d87a6c70eb28f981b2686bebdd2d431b05dcdf9cffd4"
note: "ColecoVision BIOS, NTSC or PAL console dump. The system boots through it, and games cannot start without it."
source_ref: "source Core/SMS/SmsConsole.cpp:71-74, source Core/Shared/FirmwareHelper.h:381-398, upstream UI/Interop/FirmwareTypeExtensions.cs:70"
source_ref: "source Core/SMS/SmsConsole.cpp:71-74, source Core/Shared/FirmwareHelper.h:381-398, source UI/Interop/FirmwareTypeExtensions.cs:70"
# -------------------------------------------------------
# WonderSwan (standalone only: UseBootRom is never set by the libretro port)
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/libretro/NP2kai"
upstream: "https://github.com/AZO234/NP2kai"
profiled_date: "2026-03-24"
source_commit: "1ea561cd7fd62b33f4e7b3f1c2e30fc3e6089f28"
upstream_commit: "5d47f3a04b0b8ea78f625eeec354ac132c80df06"
core_version: "0.86"
display_name: "NEC - PC-98 (Neko Project II Kai)"
cores: [np2kai]
+8 -7
View File
@@ -5,6 +5,7 @@ source: "https://github.com/shiiion/dolphin"
upstream: "https://github.com/dolphin-emu/dolphin"
profiled_date: "2026-03-26"
source_commit: "53f53e0f5bad27ad62a807cb93b136d84f68777f"
upstream_commit: "d8558142ead21b3fa39809c4aedb982fbfc85feb"
core_version: "1.0.8"
display_name: "PrimeHack (Dolphin fork for Metroid Prime)"
cores:
@@ -41,7 +42,7 @@ files:
required: false
hle_fallback: true
note: "GameCube NTSC-U boot ROM. HLE available but real IPL needed for GC menu boot and accurate fonts. Discs with the Brazilian country code B and SYSCONF countries of the Americas map to NTSC-U, so Latin America reads this directory too (Source/Core/DiscIO/Enums.cpp:198-199, 249-251)."
source_ref: "upstream Source/Core/Common/CommonPaths.h:138, source Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:109-110"
source_ref: "upstream Source/Core/Common/CommonPaths.h:139, source Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:109-110"
- name: "IPL.bin"
path: "GC/EUR/IPL.bin"
@@ -78,7 +79,7 @@ files:
adler32_pairs_with: "dsp_coef.bin"
adler32_pairs: [["0x66f334fe", "0xf3b93527"], ["0xe789b5a5", "0xa4a575f5"]]
note: "DSP instruction ROM for LLE audio. Free replacement (v0.4) included"
source_ref: "upstream Source/Core/Common/CommonPaths.h:135, source Source/Core/Core/HW/DSPLLE/DSPLLE.cpp:87-117, source Source/Core/Core/DSP/DSPCore.cpp:28-102"
source_ref: "upstream Source/Core/Common/CommonPaths.h:136, source Source/Core/Core/HW/DSPLLE/DSPLLE.cpp:87-117, source Source/Core/Core/DSP/DSPCore.cpp:28-102"
- name: "dsp_coef.bin"
path: "GC/dsp_coef.bin"
@@ -106,7 +107,7 @@ files:
sha1: "483a17ff25b68a8f18cae691da99f16beefbe370"
md5: "9bef639b7426ef6948602a12b33522a8"
note: "Windows-1252 font for GC/Wii text rendering. The free alternative ships in Sys/GC/ of the 1.0.8 Windows and macOS releases, and the real one from an IPL dump is preferred"
source_ref: "upstream Source/Core/Common/CommonPaths.h:132, source Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:131, 203-218"
source_ref: "upstream Source/Core/Common/CommonPaths.h:133, source Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp:131, 203-218"
- name: "font_japanese.bin"
path: "GC/font_japanese.bin"
@@ -127,7 +128,7 @@ files:
required: false
hle_fallback: true
note: "GBA BIOS for GC-GBA link feature (uses integrated mGBA). Path configurable in settings"
source_ref: "upstream Source/Core/Common/CommonPaths.h:144, source Source/Core/Core/HW/GBACore.cpp:344-361"
source_ref: "upstream Source/Core/Common/CommonPaths.h:145, source Source/Core/Core/HW/GBACore.cpp:344-361"
# -- Wii System Files --
- name: "SYSCONF"
@@ -143,7 +144,7 @@ files:
required: false
hle_fallback: true
note: "Wii region/language settings. Auto-generated during Wii boot emulation"
source_ref: "upstream Source/Core/Common/CommonPaths.h:152, source Source/Core/Core/Boot/Boot_BS2Emu.cpp:377-454"
source_ref: "upstream Source/Core/Common/CommonPaths.h:153, source Source/Core/Core/Boot/Boot_BS2Emu.cpp:377-454"
# -- Wii NAND Backup (BootMii) --
- name: "nand.bin"
@@ -167,7 +168,7 @@ files:
required: false
hle_fallback: true
note: "Virtual SD card image for Wii homebrew. Auto-created, supports SD/SDHC up to 4GB"
source_ref: "upstream Source/Core/Common/CommonPaths.h:149"
source_ref: "upstream Source/Core/Common/CommonPaths.h:150"
# -- Gecko Code Handler --
- name: "codehandler.bin"
@@ -179,7 +180,7 @@ files:
sha1: "e6c0050b3c67ee26198488fbbe4a5d019c15b628"
md5: "1f118b8735fcf40c20b38c3184eebfd9"
note: "Gecko/Ocarina cheat code handler. Shipped in Sys/ of the 1.0.8 Windows and macOS releases"
source_ref: "upstream Source/Core/Common/CommonPaths.h:154, source Source/Core/Core/GeckoCode.cpp:121"
source_ref: "upstream Source/Core/Common/CommonPaths.h:155, source Source/Core/Core/GeckoCode.cpp:121"
# -- Wii System Menu (WAD) --
- name: "Wii System Menu"
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/scummvm/scummvm"
logo: "https://raw.githubusercontent.com/scummvm/scummvm/master/icons/scummvm.svg"
profiled_date: "2026-03-25"
source_commit: "fcbce3ae815269dacdc309092bc92ccc6d3e13bb"
upstream_commit: "fcbce3ae815269dacdc309092bc92ccc6d3e13bb"
core_version: "2.8.0git"
display_name: "ScummVM"
cores: [scummvm]
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/snes9xgit/snes9x"
logo: "https://raw.githubusercontent.com/snes9xgit/snes9x/master/gtk/data/snes9x.svg"
profiled_date: "2026-03-25"
source_commit: "fae2fea08f74180759ef540ee94259213f503480"
upstream_commit: "85591435763dede7ef111aab8070cdf2d5a7da49"
core_version: "1.61"
display_name: "Nintendo - SNES / SFC (Snes9x)"
systems: [nintendo-snes, nintendo-satellaview, nintendo-sufami-turbo]
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/iyzsong/syobonaction-libretro"
upstream: "https://github.com/akemin-dayo/OpenSyobonAction"
profiled_date: "2026-03-25"
source_commit: "792c40cb590bcc7286cf720b5fb85807082b534e"
upstream_commit: "77ca2c128784512eca10e24a453ca19c270a8af4"
core_version: "0.3"
display_name: "Syobon Action"
cores: [syobonaction]
+1
View File
@@ -5,6 +5,7 @@ source: "https://github.com/REG-Linux/dolphin-triforce"
upstream: "https://github.com/dolphin-emu/dolphin"
profiled_date: "2026-08-08"
source_commit: "5c456e8ff0da0235be2cf1d55ba7bb2115b0cae2"
upstream_commit: "b92e354389bb7c0bd114a8631b8af110d3cb3a14"
core_version: "2407"
display_name: "Namco / Sega / Nintendo - Triforce (Dolphin Triforce)"
cores:
+1
View File
@@ -6,6 +6,7 @@ source: "https://github.com/libretro/uae4arm-libretro"
upstream: "https://github.com/PandTomB/uae4arm"
profiled_date: "2026-03-25"
source_commit: "26f444f01771f0dfa2fa652906ff8222e92e476e"
upstream_commit: "161ddbaafe90a9747e9fb9c65ee4f38c163f335e"
core_version: "v0.5"
display_name: "Commodore - Amiga (UAE4ARM)"
systems:
+1
View File
@@ -6,6 +6,7 @@ upstream: "https://github.com/mamedev/mame/tree/mame0159"
logo: "https://raw.githubusercontent.com/mamedev/mame/master/docs/source/images/MAMElogo.svg"
profiled_date: "2026-03-25"
source_commit: "7adbf440d5f554097f58f8c6ebb673adc4320a31"
upstream_commit: "6ae44e46f91aa2bd74d9895e6bfd345baebd3d22"
core_version: "0.159"
display_name: "Multi (UME 2015)"
mame_version: "0.159"
+71
View File
@@ -25,6 +25,7 @@
"catalog",
"platforms",
"emulators",
"systems",
"verification-and-coverage-gaps",
"file-locations"
]
@@ -261,6 +262,76 @@
}
}
}
},
{
"if": {
"properties": {
"kind": {
"const": "systems"
}
}
},
"then": {
"properties": {
"items": {
"items": {
"type": "object",
"required": [
"consoles",
"id",
"name"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"consoles": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"path",
"sha1"
],
"properties": {
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"size": {
"type": "integer",
"minimum": 0
},
"sha1": {
"type": "string",
"pattern": "^[0-9a-f]{40}$"
},
"md5": {
"type": "string"
},
"sha256": {
"type": "string"
},
"crc32": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
}
]
}
+61 -10
View File
@@ -367,6 +367,52 @@ def _content_matches(location: tuple[str, str], declared: dict[str, set[str]]) -
return any(actual[field] in values for field, values in declared.items())
_tree_cache: dict[tuple[str, str], list[str]] = {}
def _trees(data_root: str, bios_root: str) -> list[str]:
"""Every directory under data/ and bios/, as a path with a trailing slash."""
key = (data_root, bios_root)
if key not in _tree_cache:
_tree_cache[key] = sorted(
f"{path.as_posix()}/"
for root in (data_root, bios_root) if Path(root).is_dir()
for path in Path(root).rglob("*")
if path.is_dir()
and not any(p.startswith(".") for p in path.relative_to(root).parts)
)
return _tree_cache[key]
def held_copy(
entry: dict, data_root: str = "data", bios_root: str = "bios"
) -> tuple[str, str] | None:
"""The data/ file, directory or bios/ ZIP member holding an entry.
The database indexes the files under bios/, and an entry held otherwise
(specmate.rom as a member of spectrum_opus.zip, Dolphin's fonts in its
data directory) read as held with no word of where. Returns (path,
member), the member empty for a file or a directory. A file entry that
declares a hash is held only by a copy carrying it, as _data_hit judges.
"""
names = [entry.get("name", ""), entry.get("path") or "", *(entry.get("aliases") or [])]
names = [str(n) for n in names if n]
if entry.get("type") == "directory" or any(n.endswith("/") for n in names[:1]):
for name in names:
tail = "/" + name.strip("/") + "/"
for tree in _trees(data_root, bios_root):
if tree.endswith(tail):
return tree, ""
return None
declared = _declared_content(entry)
locations = _supplemental_locations(data_root, bios_root)
for name in names:
for location in locations.get(name.rsplit("/", 1)[-1].lower(), ()):
if not declared or _content_matches(location, declared):
return location
return None
class FileConsumers:
"""Who reads each collected file, joined through the resolver.
@@ -469,6 +515,10 @@ class FileConsumers:
self.installed.setdefault(sha1, {}).setdefault(
platform, set()).add(str(entry.get("dest", "")))
def sha1_at(self, path: str) -> str | None:
"""The file the database records at a collection path."""
return self._sha1_by_path.get(path)
def path_of(self, sha1: str) -> str:
"""The collection path the database records for a file."""
return str(self._db.get("files", {}).get(sha1, {}).get("path", ""))
@@ -757,17 +807,18 @@ def _cross_reference_profile(
continue
# --- resolve source provenance ---
storage = f.get("storage", "")
if storage in ("release", "large_file"):
# A release asset is held when the database records it, as any other
# file: storage says where the bytes are served from, not that the
# collection has them (kenji-nx's user-picked firmware package).
source = next(
(
found for version in versions[slot]
if (found := entry_source(version, index)) is not None
),
"missing",
)
if source != "missing" and f.get("storage", "") in ("release", "large_file"):
source = "large_file"
else:
source = next(
(
found for version in versions[slot]
if (found := entry_source(version, index)) is not None
),
"missing",
)
in_repo = source != "missing"
+611 -126
View File
File diff suppressed because it is too large. Load diff
+51 -7
View File
@@ -977,6 +977,9 @@ class ProfileReport:
upstream_pin: str | None = None
upstream_head: str | None = None
upstream_origin: str | None = None
# The release tag the upstream view is pinned on, when it is one. That
# view is read at its tag, and a bump leaves upstream_commit there.
upstream_tag: str | None = None
# Repositories whose pin is no ancestor of the branch the profile
# tracks: history was rewritten, or the pin lies on another branch.
orphaned: list[str] = field(default_factory=list)
@@ -1345,7 +1348,23 @@ def detect_pinned_tag(
The declared `core_version` names the tag, which is looked up by name so
that a repository publishing nightly tags cannot hide an old release behind
pagination.
Only the repository of the documented build freezes the profile. An
`upstream` a port was cut from sits on a release tag as its base
(GroovyMAME on mame0289, triforce on Dolphin 2407) while the port itself
keeps moving: that view is judged against its tag by `tag_pinned_views`,
and the port is still compared with its HEAD and still bumped.
"""
documented = [view for view in views if view.field != "upstream"] or views
tagged = tag_pinned_views(profile, documented, cache_dir, offline)
return next(iter(tagged.values()), None)
def tag_pinned_views(
profile: dict, views: list[RepoView], cache_dir: str, offline: bool
) -> dict[tuple[str, str], str]:
"""Views whose pin is the tag `core_version` names, keyed by repository."""
tagged: dict[tuple[str, str], str] = {}
for view in views:
if view.pin == view.head:
continue
@@ -1353,8 +1372,9 @@ def detect_pinned_tag(
profile.get("core_version"), view.repo.name
):
if upstream.tag_commit(view.repo, tag, cache_dir, offline) == view.pin:
return tag
return None
tagged[(view.repo.host, view.repo.slug)] = tag
break
return tagged
def _resolve_project_words(
@@ -1538,12 +1558,27 @@ def build_report(
report.pinned_tag = frozen or detect_pinned_tag(
profile, views, cache_dir, offline
)
# A repository pinned on its release tag is read at that tag, whichever
# role it plays: comparing mame0174 refs with mamedev HEAD says nothing.
tagged = tag_pinned_views(profile, views, cache_dir, offline)
report.upstream_tag = next(
(
tagged[(view.repo.host, view.repo.slug)]
for view in views
if view.field == "upstream" and (view.repo.host, view.repo.slug) in tagged
),
None,
)
def on_own_revision(view: RepoView) -> bool:
return view.pin == view.head or (view.repo.host, view.repo.slug) in tagged
if not report.pinned_tag:
# A tag or a held pin is off the tracked branch by design.
report.orphaned = [
f"{view.repo.slug}: pin {view.pin[:8]} is no ancestor of {view.head[:8]}"
for view in views
if view.pin != view.head
if not on_own_revision(view)
and upstream.compare(
view.repo, view.pin, view.head, cache_dir, offline
).diverged
@@ -1896,9 +1931,9 @@ def build_report(
if report.pinned_tag:
return True
if not part.role and len(path_roles.get(part.path, ())) > 1:
return all(view.pin == view.head for view in views)
return all(on_own_revision(view) for view in views)
view, _ = resolve_path(part.path, part.start, tuple(tokens), role_of(part))
return view.pin == view.head
return on_own_revision(view)
def judge(
part: RefPart, flag: bool, tokens: Sequence[str], hashes: Sequence[str]
@@ -1931,7 +1966,7 @@ def build_report(
# port's 305-356 read past the end of the upstream's 345
# lines. As in a comparison, a ref fails only everywhere.
for view in views:
if view is owner or view.pin != view.head:
if view is owner or not on_own_revision(view):
continue
lines = fetch_from(view.repo, view.pin, part.path)
if lines is None:
@@ -2048,6 +2083,11 @@ def format_report(report: ProfileReport, changed_only: bool = False) -> str:
)
elif report.pin and report.pin == report.head:
lines.append(" pin is HEAD: checked for self-consistency")
if report.upstream_tag and not report.pinned_tag:
lines.append(
f" upstream pinned to tag {report.upstream_tag}: checked against "
"its own revision, not against HEAD"
)
for url in report.unread or []:
lines.append(f" not read (unsupported host): {url}")
for line in report.orphaned:
@@ -2891,8 +2931,12 @@ def bump_commit(
file=sys.stderr,
)
return False
# An upstream pinned on a release tag is the base the port was cut from:
# its HEAD is another program, so the bump leaves it on the tag.
upstream_moves = bool(
report.upstream_head and report.upstream_pin != report.upstream_head
report.upstream_head
and report.upstream_pin != report.upstream_head
and not report.upstream_tag
)
if document.get("source_commit") == report.head and not upstream_moves:
# Rewriting the pin to the value it already holds is not a bump, and
+3 -1
View File
@@ -419,7 +419,9 @@ def main() -> int:
api_dir / name
for name in ("catalog.json", "platforms.json", "emulators.json", "gaps.json")
if (api_dir / name).exists()
]
] + sorted((api_dir / "platforms").glob("*.json")) + sorted(
(api_dir / "systems").glob("*.json")
)
errors.extend(
_validate_json_files(envelope_paths, "site-api-envelope.schema.json")
)
+4 -5
View File
@@ -644,11 +644,10 @@ def find_undeclared_files(
entry["required"] = True
continue
# Resolution: storage flag, then name, then path basename
storage = f.get("storage", "")
if storage in ("release", "large_file"):
in_repo = True
elif shipped_on_mismatch and (f.get("md5") or f.get("sha1")):
# Resolution: name, then path basename
# storage: release names where the bytes are served from, not
# that the collection holds them: resolved like any file.
if shipped_on_mismatch and (f.get("md5") or f.get("sha1")):
# The entry states what its content should be and the builder
# drops a copy that contradicts it, so content decides here
# too. A name match would not do: generic names collide across
+61
View File
@@ -1700,6 +1700,49 @@ class TestBuildReport(unittest.TestCase):
report = build_report("test", self._versioned("v0.78"), self.dir)
self.assertEqual(report.pinned_tag, "0.78")
def test_an_upstream_on_its_release_tag_does_not_freeze_the_port(self):
"""GroovyMAME declares 0.289 and its upstream_commit is the mame0289
tag. Reading that as a frozen profile stopped every comparison and
bump of the port, which keeps moving. The upstream is read at its tag,
the port against its HEAD."""
self.files[("portpin", "src/p.c")] = ["x", "load p.bin", "y"]
self.files[("porthead", "src/p.c")] = ["x", "load p.bin", "y"]
self.files[("uppin", "src/u.c")] = ["x", "ROM_LOAD(u.bin)", "y"]
self.files[("uphead", "src/u.c")] = ["x", "", "", "", "y"]
heads = {"o/port": "porthead", "o/up": "uphead"}
profile_sync.upstream.resolve_head = (
lambda repo, cache, offline=False, branch=None: heads[repo.slug]
)
# A tag off the tracked branch is not an orphaned pin.
profile_sync.upstream.compare = (
lambda repo, base, head, cache_dir, offline=False: CompareResult(
[], False, repo.slug == "o/up"
)
)
self.tag_commits = {"0.289": "uppin"}
profile = {
"emulator": "Port",
"source": "https://github.com/o/port",
"upstream": "https://github.com/o/up",
"source_commit": "portpin",
"upstream_commit": "uppin",
"core_version": "0.289",
"files": [
{"name": "p.bin", "source_ref": "source src/p.c:2"},
{"name": "u.bin", "source_ref": "upstream src/u.c:2"},
],
}
report = build_report("port", profile, self.dir)
self.assertIsNone(report.pinned_tag)
self.assertEqual(report.upstream_tag, "0.289")
self.assertEqual(report.orphaned, [])
self.assertEqual(
[entry.status for entry in report.entries], ["ANCHORED", "ANCHORED"]
)
self.assertIn(
"upstream pinned to tag 0.289", profile_sync.format_report(report)
)
def test_a_tag_pointing_elsewhere_is_not_flagged(self):
self.files[("pinsha", "a.c")] = ["x", "hit"]
self.files[("headsha", "a.c")] = ["x", "hit"]
@@ -2983,6 +3026,24 @@ class TestBumpCommit(unittest.TestCase):
self.assertEqual(rebase_refs(self.path, report), ["a.c:10-12 -> a.c:30-32"])
self.assertTrue(bump_commit(self.path, report))
def test_an_upstream_on_its_release_tag_stays_there(self):
"""GroovyMAME's upstream_commit is the mame0289 tag it merged: a bump
of the port must not carry it to mamedev HEAD, another program."""
part = PartResult(
RefPart("a.c", 10, 12, "a.c:10-12"), "ANCHORED", None, 10, 12, []
)
report = ProfileReport(
name="p", repo="o/n", pin="pin", head="newhead",
entries=[EntryReport("a.bin", "a.c:10-12", "ANCHORED", [part])],
counts={"ANCHORED": 1},
upstream_pin="uptag", upstream_head="uphead",
upstream_origin="upstream_commit", upstream_tag="mame0289",
)
self.assertTrue(bump_commit(self.path, report))
written = yaml.safe_load(self.path.read_text())
self.assertEqual(written["source_commit"], "newhead")
self.assertNotIn("upstream_commit", written)
def test_upstream_pin_moves_with_source_pin(self):
"""A recale onto upstream HEAD lines must advance upstream_commit too."""
part = PartResult(
+52
View File
@@ -33,6 +33,13 @@ class UnsourceableIsNotHeld(unittest.TestCase):
self.assertTrue(available({"name": "pak0.pk3"}))
self.assertFalse(available({"name": "pak0.pk3", "unsourceable": "retail data"}))
def test_a_release_asset_is_held_only_when_collected(self):
"""storage: large_file said "in repo" for kenji-nx's user-picked
firmware package, which the collection does not hold."""
available = _availability_check(DB, set())
self.assertFalse(available({"name": "<firmware>.zip", "storage": "large_file"}))
self.assertTrue(available({"name": "pak0.pk3", "storage": "release"}))
def test_the_badge_says_why(self):
entry = {"name": "pak0.pk3", "unsourceable": "retail data"}
badges = " ".join(_file_badges(entry, False))
@@ -57,5 +64,50 @@ class UnsourceableIsNotHeld(unittest.TestCase):
self.assertEqual(shown_held, [])
class AHeldEntrySaysWhere(unittest.TestCase):
"""164 entries wore the "in repo" badge with no copy named: they are held
as a ZIP member (zesarux's specmate.rom in spectrum_opus.zip) or in a
data directory (Dolphin's fonts), which the database does not index."""
def test_member_file_and_tree(self):
import hashlib
import tempfile
import zipfile
import generate_site
from cross_reference import FileConsumers, held_copy
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
data, bios = Path(tmp) / "data", Path(tmp) / "bios"
(data / "dolphin-sys" / "GC").mkdir(parents=True)
(data / "dolphin-sys" / "GC" / "font_western.bin").write_bytes(b"font")
(bios / "Arcade" / "MAME").mkdir(parents=True)
archive = bios / "Arcade" / "MAME" / "spectrum_opus.zip"
with zipfile.ZipFile(archive, "w") as zf:
zf.writestr("specmate.rom", b"rom")
roots = {"data_root": str(data), "bios_root": str(bios)}
member = held_copy({"name": "specmate.rom",
"sha1": hashlib.sha1(b"rom").hexdigest()}, **roots)
self.assertEqual(member, (str(archive), "specmate.rom"))
# A declared hash the copy does not carry names no copy.
self.assertIsNone(held_copy({"name": "specmate.rom", "sha1": "0" * 40}, **roots))
self.assertEqual(held_copy({"name": "font_western.bin"}, **roots),
(str(data / "dolphin-sys/GC/font_western.bin"), ""))
self.assertEqual(held_copy({"name": "GC/", "type": "directory"}, **roots),
(f"{(data / 'dolphin-sys/GC').as_posix()}/", ""))
consumers = FileConsumers({"files": {"z" * 40: {
"path": "bios/Arcade/MAME/spectrum_opus.zip"}}}, data_dir_registry={})
line = generate_site._held_copy_line(
("bios/Arcade/MAME/spectrum_opus.zip", "specmate.rom"), consumers)
self.assertEqual(line, "Collection copy: member `specmate.rom` of "
"[`bios/Arcade/MAME/spectrum_opus.zip`]"
"(../systems/arcade.md#sha1-zzzzzzzzzzzz)")
line = generate_site._held_copy_line(("data/dolphin-sys/GC/font_western.bin", ""),
consumers)
self.assertEqual(line, "Collection copy: `GC/font_western.bin` in the "
"`dolphin-sys` data directory")
if __name__ == "__main__":
unittest.main()
+142
View File
@@ -0,0 +1,142 @@
"""What an emulator page says about a profile and its files.
No network and no database: pages are rendered from small profiles.
"""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import generate_site as gs # noqa: E402
EMPTY_DB = {"files": {}, "indexes": {"by_name": {}}}
def _page(name: str, profile: dict) -> str:
return gs.generate_emulator_page(name, profile, EMPTY_DB, None, set())
class WhereAndHowAFileIsRead(unittest.TestCase):
"""load_from, found_by and on_mismatch were in 99, 4 and 25 entries and
on no page: Dolphin's gba_bios.bin showed a Path a RetroArch user put
under system/, while the libretro core reads it from its save directory."""
def test_load_from(self):
page = _page("dolphin", {"type": "standalone + libretro", "files": [
{"name": "gba_bios.bin", "path": "GBA/gba_bios.bin",
"load_from": {"libretro": "save_dir"}},
{"name": "hdd.rom", "load_from": "hdd_image"},
{"name": "bios.bin", "load_from": "system_dir"},
]})
self.assertIn("- Read from: the save directory, not the BIOS directory "
"(libretro build)", page)
self.assertIn("- Read from: inside the emulated hard disk image", page)
self.assertEqual(page.count("Read from:"), 2)
def test_found_by_and_on_mismatch(self):
page = _page("x", {"type": "standalone", "found_by": "content", "files": [
{"name": "scph5501.bin", "found_by": "content", "on_mismatch": "warn"},
{"name": "boot.bin", "on_mismatch": {"signature": "warn"}},
]})
self.assertIn("| Files found by | content (the code scans the folder", page)
self.assertIn("- Found by: content (", page)
self.assertIn("- On a failed check: warns and runs with the file", page)
self.assertIn("- On a failed signature check: warns and runs with the file", page)
class WhoShipsTheBuildAndWhy(unittest.TestCase):
"""built_by, pin_frozen and version_source were in 9, 13 and 40 profiles
and on no page: mame0278 did not say only Recalbox ships it."""
def test_meta_rows(self):
page = _page("mame0278", {
"type": "libretro", "built_by": ["recalbox"],
"pin_frozen": "ROCKNIX 20261001 builds this revision.",
"version_source": {"url": "https://example.org/news", "pattern": "v(\\S+)",
"no_genuine_copy": "13.0"},
"files": [],
})
self.assertIn("| Built by | [recalbox](../platforms/recalbox.md), under its "
"core name.", page)
self.assertIn("| Revision held | ROCKNIX 20261001 builds this revision. |", page)
self.assertIn("| Releases announced at | [https://example.org/news]"
"(https://example.org/news) (version 13.0 announced there, "
"found only as rebuilt copies) |", page)
class ACardKeepsItsAddress(unittest.TestCase):
"""emulators/raze/#file-duke3d-grp stopped pointing at its card once a
second version of the path was declared: the anchor of the first one
became file-duke3d-grp-<hash>."""
def test_a_second_version_moves_nothing(self):
one = {"name": "DUKE3D.GRP", "md5": "aaaa1111bbbb"}
two = {"name": "DUKE3D.GRP", "md5": "cccc2222dddd"}
before = gs.emulator_file_anchors([one])
after = gs.emulator_file_anchors([two, one])
self.assertEqual(before, ["file-duke3d-grp-aaaa1111"])
self.assertEqual(after[1], before[0])
def test_the_plain_address_still_lands(self):
files = [{"name": "DUKE3D.GRP", "md5": "aaaa1111bbbb"}]
page = _page("raze", {"type": "standalone", "files": files})
self.assertIn('<span id="file-duke3d-grp"></span>', page)
self.assertIn("{ #file-duke3d-grp-aaaa1111 .rb-file-title }", page)
# A card already holding the plain id gives it to no one else.
files = [{"name": "x.bin"}, {"name": "x.bin", "sha1": "ab" * 20}]
anchors = gs.emulator_file_anchors(files)
self.assertEqual(anchors, ["file-x-bin", "file-x-bin-abababab"])
self.assertEqual(gs.emulator_file_aliases(files, anchors), ["", ""])
class WhatASearchSnippetSays(unittest.TestCase):
def test_a_core_loading_no_file_promises_no_hash(self):
"""152 pages of cores that load no file announced paths and hashes."""
rel = Path("emulators/lutro.md")
saved = dict(gs.PAGE_DATA)
self.addCleanup(lambda: (gs.PAGE_DATA.clear(), gs.PAGE_DATA.update(saved)))
gs.PAGE_DATA[rel.as_posix()] = {}
text = gs._page_description("# Lutro\n", rel, "Lutro")
self.assertNotIn("hashes", text)
self.assertIn("No BIOS or firmware file is required.", text)
gs.PAGE_DATA[rel.as_posix()] = {"hasPart": [{"name": "a.bin"}]}
self.assertIn("paths, hashes", gs._page_description("# Lutro\n", rel, "Lutro"))
def test_a_long_paragraph_is_cut_at_a_word(self):
plain = ("files/?sha1= is the address that does not move: it opens the card "
"wherever the file is now, " * 4)
text = gs._shorten(plain)
self.assertTrue(text.endswith("..."))
self.assertLessEqual(len(text), 160)
self.assertTrue(plain.startswith(text[:-3]))
self.assertIn(plain[len(text) - 3], " ,:")
def test_code_keeps_its_underscores(self):
self.assertEqual(gs._plain_markdown("only `validate_schemas.py` and _this_"),
"only validate_schemas.py and this")
def test_structured_url_is_encoded_like_the_canonical(self):
"""mkdocs encodes idtech4a++ as idtech4a%2B%2B in the canonical link
and the sitemap, and the JSON-LD named the unencoded URL."""
import json
import tempfile
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
page = Path(tmp) / "emulators" / "idtech4a++.md"
page.parent.mkdir()
page.write_text("# idTech4A++\n\nSome text long enough to be a description.\n")
gs.decorate_markdown_pages(Path(tmp))
text = page.read_text()
data = json.loads(text.split('<script type="application/ld+json">')[1]
.split("</script>")[0].replace("<\\/", "</"))
self.assertEqual(data["url"],
"https://abdess.github.io/retrobios/emulators/idtech4a%2B%2B/")
if __name__ == "__main__":
unittest.main()
+120
View File
@@ -882,5 +882,125 @@ class NestedProfileBlocksRenderAsLists(unittest.TestCase):
self.assertIn("name: a.bin, size: 2", html)
class TheOneLinerIsWhereUsersLand(unittest.TestCase):
"""The download page had no Android line and no Windows form with
options, and the platform and system pages offered only ZIP parts."""
def test_download_page(self):
import generate_site # noqa: PLC0415
page = generate_site.generate_which_pack()
self.assertIn("Android (Termux", page)
self.assertIn("install.ps1))) --check", page)
self.assertIn("--platform retroarch --dest", page)
def test_platform_and_system_pages(self):
import generate_site # noqa: PLC0415
from cross_reference import FileConsumers # noqa: PLC0415
cov = {"platform": "Batocera", "mode": "md5", "present": 0, "total": 0,
"verified": 0, "missing": 0, "details": [], "config": {"systems": {}}}
page = generate_site.generate_platform_page("batocera", cov)
self.assertIn("install.sh | sh -s -- --platform batocera", page)
self.assertIn("install.ps1))) --platform batocera", page)
self.assertLess(page.index("--platform batocera"), page.index("md-button"))
entry = {"name": "a.bin", "sha1": "a" * 40, "md5": "b" * 32, "size": 3,
"path": "bios/Recalbox/MSX/a.bin"}
page = generate_site.generate_system_page(
"Microsoft", {"MSX": [entry]}, FileConsumers({"files": {}}),
{"MSX": [("recalbox", "msx1,msx2,msxturbor"), ("batocera", "msx1")]})
self.assertIn("--platform recalbox --system msx1,msx2,msxturbor\n", page)
self.assertIn("--platform batocera --system msx1", page)
def test_a_system_is_offered_only_where_the_manifest_records_it(self):
import generate_site # noqa: PLC0415
import siterender # noqa: PLC0415
saved = dict(siterender._system_page_map)
self.addCleanup(siterender.set_system_page_map, saved)
siterender.set_system_page_map({"sony-playstation": ("sony", "PlayStation"),
"nowhere": ("sony", "PlayStation")})
cov = {"retroarch": {"config": {"systems": {"sony-playstation": {}, "nowhere": {}}}}}
choices = generate_site.system_install_choices(cov)
self.assertEqual(choices["sony"]["PlayStation"], [("retroarch", "sony-playstation")])
class TheGapPageOpensOnCounts(unittest.TestCase):
"""The page opened on a slogan and on tiles reading "Verified (100%)"
for 5,437 of 5,438 files and "Missing 0", above a table of red cells."""
def test_every_count_is_said(self):
import generate_site # noqa: PLC0415
text = generate_site._platform_lists_sentence(5438, 5437, 1, 0)
self.assertIn("Of the 5,438 files", text)
self.assertIn("5,437 are held", text)
self.assertIn("1 is held but not verified", text)
self.assertIn("0 are missing", text)
self.assertNotIn("%", text)
source = (Path(__file__).resolve().parents[1] / "scripts" / "generate_site.py").read_text()
self.assertNotIn("Unified view of BIOS verification", source)
class WhichPackDescribesNoPlatformByOpinion(unittest.TestCase):
"""The page called RetroPie the largest community with the most guides,
Batocera an easy setup and Recalbox good for a first build: claims
nothing in the repository measures, ranking platforms against each
other."""
def test_no_ranking_words(self):
import re
import generate_site # noqa: PLC0415
page = generate_site.generate_which_pack()
rows = [line for line in page.splitlines() if line.startswith("| ")]
found = [
row for row in rows
if re.search(r"\b(largest|most|popular|easy|best|classic|lightweight|"
r"one-click|plug-and-play|first build|accuracy-focused)\b",
row, re.IGNORECASE)
]
self.assertEqual(found, [])
class EveryPageLinksItsOwnData(unittest.TestCase):
"""An emulator page linked its YAML and JSON. A platform page linked
nothing, and a tool had to download platforms.json (2.8 MB) and filter
it. A system page linked nothing either."""
def test_platform_and_system_documents(self):
import generate_site # noqa: PLC0415
from cross_reference import FileConsumers # noqa: PLC0415
cov = {"platform": "Batocera", "mode": "md5", "present": 0, "total": 0,
"verified": 0, "missing": 0, "details": [], "config": {"systems": {}}}
page = generate_site.generate_platform_page("batocera", cov)
self.assertIn("[JSON](../api/v1/platforms/batocera.json)", page)
self.assertIn("/blob/main/platforms/batocera.yml)", page)
entry = {"name": "a.bin", "sha1": "a" * 40, "md5": "b" * 32, "size": 3,
"path": "bios/Sony/PlayStation/a.bin"}
page = generate_site.generate_system_page(
"Sony", {"PlayStation": [entry]}, FileConsumers({"files": {}}))
self.assertIn("[JSON](../api/v1/systems/sony.json)", page)
item = generate_site.system_export_item("sony", "Sony", {"PlayStation": [entry]})
self.assertEqual(item["consoles"]["PlayStation"][0]["sha1"], "a" * 40)
with tempfile.TemporaryDirectory(dir=Path(__file__).resolve().parents[1] / "tmp") as tmp:
path = Path(tmp) / "sony.json"
generate_site.write_single_item(path, "systems", item, "2026-10-21T00:00:00Z")
document = json.loads(path.read_text())
self.assertEqual(document["count"], 1)
try:
import jsonschema # noqa: PLC0415
except ImportError:
return
root = Path(__file__).resolve().parents[1]
envelope = json.loads((root / "schemas" / "site-api-envelope.schema.json").read_text())
jsonschema.validate(document, envelope)
if __name__ == "__main__":
unittest.main()
+26
View File
@@ -44,5 +44,31 @@ class EachRowItsDeclaration(unittest.TestCase):
self.assertNotIn(A, saturn)
class AnUntestedRowNamesBothHashes(unittest.TestCase):
"""The gaps page said "declared hash contradicted by the local file" for
RetroDECK's dsifirmware.bin and named neither the dump RetroDECK asks for
nor the one the collection holds."""
def test_declared_and_held(self):
import generate_site as gs
path = "bios/Nintendo/Nintendo DS/dsifirmware.bin"
declared, held = "1" * 64, "2" * 64
config = {"systems": {"nintendo-ds": {"files": [
{"name": "dsifirmware.bin", "sha256": declared}]}}}
detail = {"name": "dsifirmware.bin", "system": "nintendo-ds"}
problem = {
"platform": "RetroDECK", "name": "dsifirmware.bin", "status": "untested",
"required": True, "reason": "declared hash contradicted by the local file",
"declared": gs._declared_hashes(config, detail), "path": path,
}
db = {"files": {"f" * 40: {"path": path, "sha256": held}}}
table = "\n".join(gs._render_problem_files([problem], db=db))
row = next(line for line in table.splitlines() if "dsifirmware" in line)
self.assertIn(f"sha256 `{declared}`", row)
self.assertIn(f"sha256 `{held}`", row)
self.assertIn(f"`{path}`", row)
if __name__ == "__main__":
unittest.main()
+83
View File
@@ -147,6 +147,89 @@ class AnIdNamesOneCardPerPage(unittest.TestCase):
self.assertEqual(page.count("rb-sys-file"), 2)
class ACopyFiledByEmulatorDoesNotMoveAMachine(unittest.TestCase):
"""The database keeps one path per content, and the system links followed
it: nintendo-nes and nintendo-fds led to Arcade > FBNeo, where
disksys.rom is kept as fdsbios.nes, sega-mastersystem and
sinclair-zx-spectrum to Other > zesarux."""
def _db(self, tmp: str) -> dict:
for rel in ("Arcade/FBNeo/fdsbios.nes", "Nintendo/Famicom Disk System/disksys.rom",
"Nintendo/NES/other.rom"):
(Path(tmp) / rel).parent.mkdir(parents=True, exist_ok=True)
(Path(tmp) / rel).write_bytes(b"x")
return {
"bios_dir": tmp,
"files": {
"d" * 40: {"path": "bios/Arcade/FBNeo/fdsbios.nes", "sha1": "d" * 40,
"name": "fdsbios.nes"},
"e" * 40: {"path": "bios/Nintendo/NES/other.rom", "sha1": "e" * 40,
"name": "other.rom"},
},
"indexes": {
"by_sha1": {}, "by_md5": {}, "by_name": {},
"by_path_suffix": {
"Arcade/FBNeo/fdsbios.nes": ["d" * 40],
"Nintendo/Famicom Disk System/disksys.rom": ["d" * 40],
"Nintendo/NES/other.rom": ["e" * 40],
},
},
}
def test_the_copy_under_the_declared_name_places_the_system(self):
import tempfile
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
db = self._db(tmp)
files = [{"name": "disksys.rom", "sha1": "d" * 40}]
self.assertEqual(
gs._system_folder(files, db, {}, "nintendo-fds"),
("Nintendo", "Famicom Disk System"),
)
# A file named otherwise still finds the maker's copy before the
# emulator's folder.
files = [{"name": "FDS_disksys-nintendo.rom", "sha1": "d" * 40}]
self.assertEqual(
gs._system_folder(files, db, {}, "fds"),
("Nintendo", "Famicom Disk System"),
)
def test_an_id_that_spells_a_folder_links_to_it(self):
import tempfile
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
db = self._db(tmp)
manufacturers = gs._group_by_manufacturer(db)
config = {"systems": {"nintendo-nes": {"files": [
{"name": "FDS_disksys-nintendo.rom", "sha1": "d" * 40}]}}}
pages = gs._system_pages_from_data(
manufacturers, {"p": {"config": config}}, db)
self.assertEqual(pages["nintendo-nes"], ("nintendo", "NES"))
def test_repository_systems(self):
if not (REPO_ROOT / "database.json").exists():
self.skipTest("database.json is not built")
previous = os.getcwd()
os.chdir(REPO_ROOT)
self.addCleanup(os.chdir, previous)
from common import load_database, load_emulator_profiles
db = load_database("database.json")
manufacturers = gs._group_by_manufacturer(db)
pages = gs._system_pages_from_data(
manufacturers, {}, db, load_emulator_profiles("emulators", skip_aliases=False))
expected = {
"nintendo-nes": ("nintendo", "NES"),
"nintendo-fds": ("nintendo", "Famicom Disk System"),
"sega-mastersystem": ("sega", "Master System"),
"sinclair-zx-spectrum": ("sinclair", "ZX Spectrum"),
"commodore-vic20": ("commodore", "VIC-20"),
}
for sys_id, page in expected.items():
with self.subTest(system=sys_id):
self.assertEqual(pages.get(sys_id), page)
class SystemEmulatorsWhenNoIdMatches(unittest.TestCase):
"""RetroArch files NAOMI and Atomiswave as sega-dreamcast-arcade and the
Enterprise as enterprise-64-128, ids no profile uses: the emulator cell
+4 -1
View File
@@ -301,7 +301,10 @@ only hold at one revision, so a few rules keep them checkable.
it.
- **A frozen line is pinned to its tag.** A profile documenting a release that
lives as a tag in a still active repository (`pcsx2-legacy` at PCSX2 v1.6.0)
is checked against its own revision and never rebased onto the head.
is checked against its own revision and never rebased onto the head. An
`upstream` whose pin is the tag `core_version` names (GroovyMAME's base,
`mame0289`) is read at that tag the same way, while the port itself is still
compared with its head, and a bump leaves `upstream_commit` on the tag.
- **A fork's base comes from the forge.** `upstream_commit` of a fork is the
merge base the compare API returns. A vendored `version.h` can name the
project the fork once came from. When a package ships prebuilt binaries
+1 -1
View File
@@ -46,7 +46,7 @@ Every module in `tests/`, by the area it guards.
| Installer and downloads | loopback server | `test_check_release`, `test_download`, `test_install`, `test_install_cores`, `test_install_experience`, `test_install_selection` |
| Truth and native export | synthetic + cached originals | `test_export_counts`, `test_export_native_layer`, `test_export_report`, `test_exporters`, `test_truth_diff`, `test_truth_layout` |
| Scrapers, upstream and provenance | inline sources, synthetic | `test_check_freshness`, `test_dedup`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_profile_sync`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_scraper_contract`, `test_upstream`, `test_upstream_index` |
| Site, docs and repository contracts | rendered pages, sources | `test_contributing_text`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_doc_pages`, `test_doc_surfaces`, `test_optional_dependencies`, `test_prose_style`, `test_site_availability`, `test_site_cores`, `test_site_exports`, `test_site_platform_hashes`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_write`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
| Site, docs and repository contracts | rendered pages, sources | `test_contributing_text`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_doc_pages`, `test_doc_surfaces`, `test_optional_dependencies`, `test_prose_style`, `test_site_availability`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_platform_hashes`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_write`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
## Test architecture