feat: watch the rom folder pins

This commit is contained in:
Abdessamad Derraz committed 2026-11-05 12:09:10 +01:00
1 parent c34ed4d2a1
commit 2c81e1a1af
7 files changed
+179 -5

No files matched your search

+1 -1
View File
@@ -1 +1 @@
{"arcade":{"fbneo":{"file":"arcade/fbneo.json","gzip_size":115955,"sha256":"88961c9a6be804772baa767f6ee3a9162478c30c60fe55c8363d8fe4fbc909da","size":423362},"flycast":{"file":"arcade/flycast.json","gzip_size":1841,"sha256":"957189a6e993fda08e21c90ffc6ad94aed9ca9e6dd9ed8c7724e29db036791cb","size":5441},"mame":{"file":"arcade/mame.json","gzip_size":154527,"sha256":"b12010301293a218ecfac45706d81a26c3ac0c27eebeca7409bc8f6ce0163e9e","size":604648},"mame0258":{"file":"arcade/mame0258.json","gzip_size":141151,"sha256":"7ecff1979ed2ac123ea3ba4825dde6e2c31906415165fabefc9af93fa48a6de4","size":556059},"mame0278":{"file":"arcade/mame0278.json","gzip_size":148595,"sha256":"7881e3516f451ec83e886aaf41ebac9d60f497e1f954f49749d52890d7955cbf","size":582797},"mame2000":{"file":"arcade/mame2000.json","gzip_size":8624,"sha256":"f0d69b0872ecfc2845eeb7304543e5b17c11db82060b5514c206b6962ed08263","size":23316},"mame2003":{"file":"arcade/mame2003.json","gzip_size":17456,"sha256":"3b44e2a92c80a8f104b7781fcc1c776d025ad2cf4986638efa3ea041baa9a56d","size":48929},"mame2003_plus":{"file":"arcade/mame2003_plus.json","gzip_size":21077,"sha256":"2857283556fa49c20d8160ed113465bf0c2e643ed0b8acd2d38266a78be8e25e","size":62373},"mame2010":{"file":"arcade/mame2010.json","gzip_size":30869,"sha256":"50b1b381b4b07463f69f1f4c512dd1c98a78d4d0e9c3aeaf49b7b1b007b42152","size":96528},"mame2015":{"file":"arcade/mame2015.json","gzip_size":86990,"sha256":"afeb8c32f651421f74c6de057b0c0b837acd14c533b5160bb31f4411e0afffec","size":360117},"mame2016":{"file":"arcade/mame2016.json","gzip_size":101249,"sha256":"1f75147977d9d082a38b8f8e47a02b301f3d6e6db97c9b9282f3e7f27f26ee47","size":406190}},"containers":{"7z":{"description":"Per-file CRCs and sizes sit in the header at the end of the archive, LZMA-compressed by default (kEncodedHeader). One LZMA decode of the header, independent of the archive size, gives them. Python's lzma is an optional module.","re_read":true,"signature":{"bytes_hex":"377abcaf271c","offset":0},"source_ref":"ip7z/7zip@b88c6bc0 DOC/7zFormat.txt:171-189, DOC/7zFormat.txt:318-334, CPP/7zip/Archive/7z/7zHandlerOut.cpp:859"},"cd-sector":{"description":"User data of a CD sector. In a 2048-byte image (.iso) sector N starts at N * 2048. In a raw 2352-byte image it starts at N * 2352 plus 16 for Mode 1 and plus 24 for Mode 2 Form 1 (12 sync bytes, a 4-byte header, and for Mode 2 an 8-byte subheader).","re_read":true,"sector_size":{"iso":2048,"raw":2352},"source_ref":"stenzek/duckstation@526b4a12 src/util/iso_reader.cpp:157-171, src/util/cd_image.h:28-34","user_data_offset":{"mode1":16,"mode2":24}},"chd-v5":{"description":"MAME compressed hunks, big-endian. The header holds sizes, codecs and hashes only, never the system or the region. The overall SHA-1 is what MAME software lists record. Neither SHA-1 equals a Redump track hash: chdman hashes 2448-byte frames (subcode included, zero-filled), pads each track to 4 frames and byte-swaps audio, all tracks in one stream.","fields":{"compressors":{"encoding":"fourcc4","length":16,"offset":16},"header_length":{"encoding":"u32be","length":4,"offset":8,"value":124},"hunk_bytes":{"encoding":"u32be","length":4,"offset":56},"logical_bytes":{"encoding":"u64be","length":8,"offset":32},"map_offset":{"encoding":"u64be","length":8,"offset":40},"metadata_offset":{"encoding":"u64be","length":8,"offset":48},"overall_sha1":{"encoding":"hex","length":20,"offset":84},"parent_sha1":{"encoding":"hex","length":20,"offset":104},"raw_sha1":{"encoding":"hex","length":20,"offset":64},"unit_bytes":{"encoding":"u32be","length":4,"offset":60},"version":{"encoding":"u32be","length":4,"offset":12,"value":5}},"magic":{"bytes":"MComprHD","offset":0},"media_tags":{"CHCD":"cd","CHGD":"gd-rom","CHGT":"gd-rom","CHT2":"cd","CHTR":"cd","DVD ":"dvd","GDDD":"hard-disk"},"metadata_entry":{"description":"16-byte entry header, payload follows. Linked list from metadata_offset.","flags":{"encoding":"u8","length":1,"offset":4},"length":{"encoding":"u24be","length":3,"offset":5},"next":{"encoding":"u64be","length":8,"offset":8},"tag":{"encoding":"ascii","length":4,"offset":0}},"re_read":true,"source_ref":"mamedev/mame@f34f0250 src/lib/util/chd.h:148-162, src/lib/util/chd.cpp:2021-2061, src/lib/util/chd.cpp:2822-2831, src/lib/util/chd.h:212-235, src/lib/util/chd.cpp:1709-1749, src/tools/chdman.cpp:443-445, src/tools/chdman.cpp:2195-2203, src/lib/util/cdrom.cpp:2669-2672, src/emu/romload.cpp:1211"},"gamecube-disc":{"description":"Where disc offset 0 sits inside each container Dolphin opens.","formats":{"ciso":{"disc_header_at":32768,"magic":"CISO"},"gcz":{"disc_header_at":null,"magic_u32le":2970337281,"note":"block 0 is zlib unless bit 63 of its pointer is set"},"iso":{"disc_header_at":0,"magic":null},"rvz":{"disc_header_at":88,"magic":"RVZ\u0001","note":"first 0x80 disc bytes copied into header 2"},"wbfs":{"disc_header_at":null,"magic":"WBFS","note":"disc offset 0 at wlba[0] << wbfs_sector_shift"},"wia":{"disc_header_at":88,"magic":"WIA\u0001","note":Line truncated
{"arcade":{"fbneo":{"file":"arcade/fbneo.json","gzip_size":115955,"sha256":"88961c9a6be804772baa767f6ee3a9162478c30c60fe55c8363d8fe4fbc909da","size":423362},"flycast":{"file":"arcade/flycast.json","gzip_size":1841,"sha256":"957189a6e993fda08e21c90ffc6ad94aed9ca9e6dd9ed8c7724e29db036791cb","size":5441},"mame":{"file":"arcade/mame.json","gzip_size":154527,"sha256":"b12010301293a218ecfac45706d81a26c3ac0c27eebeca7409bc8f6ce0163e9e","size":604648},"mame0258":{"file":"arcade/mame0258.json","gzip_size":141151,"sha256":"7ecff1979ed2ac123ea3ba4825dde6e2c31906415165fabefc9af93fa48a6de4","size":556059},"mame0278":{"file":"arcade/mame0278.json","gzip_size":148595,"sha256":"7881e3516f451ec83e886aaf41ebac9d60f497e1f954f49749d52890d7955cbf","size":582797},"mame2000":{"file":"arcade/mame2000.json","gzip_size":8624,"sha256":"f0d69b0872ecfc2845eeb7304543e5b17c11db82060b5514c206b6962ed08263","size":23316},"mame2003":{"file":"arcade/mame2003.json","gzip_size":17456,"sha256":"3b44e2a92c80a8f104b7781fcc1c776d025ad2cf4986638efa3ea041baa9a56d","size":48929},"mame2003_plus":{"file":"arcade/mame2003_plus.json","gzip_size":21077,"sha256":"2857283556fa49c20d8160ed113465bf0c2e643ed0b8acd2d38266a78be8e25e","size":62373},"mame2010":{"file":"arcade/mame2010.json","gzip_size":30869,"sha256":"50b1b381b4b07463f69f1f4c512dd1c98a78d4d0e9c3aeaf49b7b1b007b42152","size":96528},"mame2015":{"file":"arcade/mame2015.json","gzip_size":86990,"sha256":"afeb8c32f651421f74c6de057b0c0b837acd14c533b5160bb31f4411e0afffec","size":360117},"mame2016":{"file":"arcade/mame2016.json","gzip_size":101249,"sha256":"1f75147977d9d082a38b8f8e47a02b301f3d6e6db97c9b9282f3e7f27f26ee47","size":406190}},"containers":{"7z":{"description":"Per-file CRCs and sizes sit in the header at the end of the archive, LZMA-compressed by default (kEncodedHeader). One LZMA decode of the header, independent of the archive size, gives them. Python's lzma is an optional module.","re_read":true,"signature":{"bytes_hex":"377abcaf271c","offset":0},"source_ref":"ip7z/7zip@b88c6bc0 DOC/7zFormat.txt:171-189, DOC/7zFormat.txt:318-334, CPP/7zip/Archive/7z/7zHandlerOut.cpp:859"},"cd-sector":{"description":"User data of a CD sector. In a 2048-byte image (.iso) sector N starts at N * 2048. In a raw 2352-byte image it starts at N * 2352 plus 16 for Mode 1 and plus 24 for Mode 2 Form 1 (12 sync bytes, a 4-byte header, and for Mode 2 an 8-byte subheader).","re_read":true,"sector_size":{"iso":2048,"raw":2352},"source_ref":"stenzek/duckstation@526b4a12 src/util/iso_reader.cpp:157-171, src/util/cd_image.h:28-34","user_data_offset":{"mode1":16,"mode2":24}},"chd-v5":{"description":"MAME compressed hunks, big-endian. The header holds sizes, codecs and hashes only, never the system or the region. The overall SHA-1 is what MAME software lists record. Neither SHA-1 equals a Redump track hash: chdman hashes 2448-byte frames (subcode included, zero-filled), pads each track to 4 frames and byte-swaps audio, all tracks in one stream.","fields":{"compressors":{"encoding":"fourcc4","length":16,"offset":16},"header_length":{"encoding":"u32be","length":4,"offset":8,"value":124},"hunk_bytes":{"encoding":"u32be","length":4,"offset":56},"logical_bytes":{"encoding":"u64be","length":8,"offset":32},"map_offset":{"encoding":"u64be","length":8,"offset":40},"metadata_offset":{"encoding":"u64be","length":8,"offset":48},"overall_sha1":{"encoding":"hex","length":20,"offset":84},"parent_sha1":{"encoding":"hex","length":20,"offset":104},"raw_sha1":{"encoding":"hex","length":20,"offset":64},"unit_bytes":{"encoding":"u32be","length":4,"offset":60},"version":{"encoding":"u32be","length":4,"offset":12,"value":5}},"magic":{"bytes":"MComprHD","offset":0},"media_tags":{"CHCD":"cd","CHGD":"gd-rom","CHGT":"gd-rom","CHT2":"cd","CHTR":"cd","DVD ":"dvd","GDDD":"hard-disk"},"metadata_entry":{"description":"16-byte entry header, payload follows. Linked list from metadata_offset.","flags":{"encoding":"u8","length":1,"offset":4},"length":{"encoding":"u24be","length":3,"offset":5},"next":{"encoding":"u64be","length":8,"offset":8},"tag":{"encoding":"ascii","length":4,"offset":0}},"re_read":true,"source_ref":"mamedev/mame@f34f0250 src/lib/util/chd.h:148-162, src/lib/util/chd.cpp:2021-2061, src/lib/util/chd.cpp:2822-2831, src/lib/util/chd.h:212-235, src/lib/util/chd.cpp:1709-1749, src/tools/chdman.cpp:443-445, src/tools/chdman.cpp:2195-2203, src/lib/util/cdrom.cpp:2669-2672, src/emu/romload.cpp:1211"},"gamecube-disc":{"description":"Where disc offset 0 sits inside each container Dolphin opens.","formats":{"ciso":{"disc_header_at":32768,"magic":"CISO"},"gcz":{"disc_header_at":null,"magic_u32le":2970337281,"note":"block 0 is zlib unless bit 63 of its pointer is set"},"iso":{"disc_header_at":0,"magic":null},"rvz":{"disc_header_at":88,"magic":"RVZ\u0001","note":"first 0x80 disc bytes copied into header 2"},"wbfs":{"disc_header_at":null,"magic":"WBFS","note":"disc offset 0 at wlba[0] << wbfs_sector_shift"},"wia":{"disc_header_at":88,"magic":"WIA\u0001","note":Line truncated
+1 -1
View File
@@ -1 +1 @@
{"case_sensitive":null,"folders":[{"basis":"id","core":"opera","emulator":"retroarch","extensions":[".bin",".chd",".cue",".iso"],"fallback":["panasonic-3do|opera"],"folder":"3do","profile":"opera","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:5","systems":["panasonic-3do"]},{"basis":"native name of batocera","core":"advision","emulator":"mame","extensions":[".7z",".bin",".zip"],"fallback":["entex-advision|"],"folder":"advision","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:25","systems":["entex-advision"]},{"basis":"id","core":"puae2021","emulator":"retroarch","extensions":[".adf",".adz",".dms",".hdf",".hdz",".ipf",".lha",".m3u",".uae",".zip"],"fallback":["commodore-amiga|puae2021"],"folder":"amiga","profile":"puae2021","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:45","systems":["commodore-amiga"]},{"basis":"normalized id","core":"puae2021","emulator":"retroarch","extensions":[".chd",".cue",".iso",".lha"],"fallback":["commodore-amiga-cd32|puae2021"],"folder":"amigacd32","profile":"puae2021","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:72","systems":["commodore-amiga-cd32"]},{"basis":"normalized id","core":"crocods","emulator":"retroarch","extensions":[".7z",".cdt",".dsk",".kcr",".m3u",".sna",".tap",".voc",".zip"],"fallback":["amstrad-cpc|crocods"],"folder":"amstradcpc","profile":"crocods","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:94","systems":["amstrad-cpc"]},{"arcade":"mame2003_plus","basis":"id","core":"mame2003_plus","emulator":"retroarch","extensions":[".7z",".zip"],"fallback":["arcade|mame2003_plus"],"folder":"arcade","profile":"mame2003_plus","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:116","systems":["arcade"]},{"basis":"id","core":"arduous","emulator":"retroarch","extensions":[".hex"],"fallback":["arduboy|arduous"],"folder":"arduboy","profile":"arduous","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:153","systems":["arduboy"]},{"basis":"normalized id","core":"stella2014","emulator":"retroarch","extensions":[".7z",".a26",".bin",".zip"],"fallback":["atari-2600|stella2014"],"folder":"atari2600","profile":"stella2014","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:173","systems":["atari-2600"]},{"basis":"native name of amberelec","core":"a5200","emulator":"retroarch","extensions":[".7z",".a52",".atr",".atx",".bin",".car",".cas",".cdm",".rom",".xex",".xfd",".zip"],"fallback":["atari-5200|a5200"],"folder":"atari5200","profile":"a5200","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:200","systems":["atari-5200"]},{"basis":"id","core":"prosystem","emulator":"retroarch","extensions":[".7z",".a78",".bin",".zip"],"fallback":["atari-7800|prosystem"],"folder":"atari7800","profile":"prosystem","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:221","systems":["atari-7800"]},{"basis":"native name of amberelec","core":"atari800","emulator":"retroarch","extensions":[".7z",".a52",".atr",".atx",".bin",".car",".cas",".cdm",".rom",".xex",".xfd",".zip"],"fallback":["atari-400-800|atari800"],"folder":"atari800","profile":"atari800","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:247","systems":["atari-400-800"]},{"basis":"native name of retrodeck","core":"virtualjaguar","emulator":"retroarch","extensions":[".7z",".j64",".jag",".zip"],"fallback":["atari-jaguar|virtualjaguar"],"folder":"atarijaguar","profile":"virtualjaguar","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:292","systems":["atari-jaguar"]},{"basis":"native name of amberelec","core":"handy","emulator":"retroarch","extensions":[".7z",".lnx",".o",".zip"],"fallback":["atari-lynx|handy"],"folder":"atarilynx","profile":"handy","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:1043","systems":["atari-lynx"]},{"basis":"native name of amberelec","core":"hatari","emulator":"retroarch","extensions":[".7z",".dim",".ipf",".m3u",".msa",".st",".stx",".zip"],"fallback":["atari-st|hatari"],"folder":"atarist","profile":"hatari","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:267","systems":["atari-st"]},{"arcade":true,"basis":"native name of amberelec","core":"flycast2021le","emulator":"retrorun","extensions":[".7z",".bin",".dat",".lst",".zip"],"fallback":["sega-dreamcast-arcade|flycast2021le"],"folder":"atomiswave","profile":"flycast2021le","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:317","systems":["sega-dreamcast-arcade"]},{"basis":"systems of raze","core":"raze","emulator":"raze","extensions":[".build"],"fallback":["blood|raze","duke3d|raze","exhumed|raze","nam|raze","powerslave|raze","redneck|raze","shadow-warrior|raze","ww2gi|raze"],"folder":"build","profile":"raze","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:2855","systems":["blood","duke3d","exhumed","nam","powerslave","redneck","shadow-warrior","ww2gi"]},{"basis":"normalized idLine truncated
{"case_sensitive":null,"folders":[{"basis":"id","core":"opera","emulator":"retroarch","extensions":[".bin",".chd",".cue",".iso"],"fallback":["panasonic-3do|opera"],"folder":"3do","profile":"opera","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:5","systems":["panasonic-3do"]},{"basis":"native name of batocera","core":"advision","emulator":"mame","extensions":[".7z",".bin",".zip"],"fallback":["entex-advision|"],"folder":"advision","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:25","systems":["entex-advision"]},{"basis":"id","core":"puae2021","emulator":"retroarch","extensions":[".adf",".adz",".dms",".hdf",".hdz",".ipf",".lha",".m3u",".uae",".zip"],"fallback":["commodore-amiga|puae2021"],"folder":"amiga","profile":"puae2021","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:45","systems":["commodore-amiga"]},{"basis":"normalized id","core":"puae2021","emulator":"retroarch","extensions":[".chd",".cue",".iso",".lha"],"fallback":["commodore-amiga-cd32|puae2021"],"folder":"amigacd32","profile":"puae2021","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:72","systems":["commodore-amiga-cd32"]},{"basis":"normalized id","core":"crocods","emulator":"retroarch","extensions":[".7z",".cdt",".dsk",".kcr",".m3u",".sna",".tap",".voc",".zip"],"fallback":["amstrad-cpc|crocods"],"folder":"amstradcpc","profile":"crocods","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:94","systems":["amstrad-cpc"]},{"arcade":"mame2003_plus","basis":"id","core":"mame2003_plus","emulator":"retroarch","extensions":[".7z",".zip"],"fallback":["arcade|mame2003_plus"],"folder":"arcade","profile":"mame2003_plus","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:116","systems":["arcade"]},{"basis":"id","core":"arduous","emulator":"retroarch","extensions":[".hex"],"fallback":["arduboy|arduous"],"folder":"arduboy","profile":"arduous","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:153","systems":["arduboy"]},{"basis":"normalized id","core":"stella2014","emulator":"retroarch","extensions":[".7z",".a26",".bin",".zip"],"fallback":["atari-2600|stella2014"],"folder":"atari2600","profile":"stella2014","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:173","systems":["atari-2600"]},{"basis":"native name of amberelec","core":"a5200","emulator":"retroarch","extensions":[".7z",".a52",".atr",".atx",".bin",".car",".cas",".cdm",".rom",".xex",".xfd",".zip"],"fallback":["atari-5200|a5200"],"folder":"atari5200","profile":"a5200","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:200","systems":["atari-5200"]},{"basis":"id","core":"prosystem","emulator":"retroarch","extensions":[".7z",".a78",".bin",".zip"],"fallback":["atari-7800|prosystem"],"folder":"atari7800","profile":"prosystem","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:221","systems":["atari-7800"]},{"basis":"native name of amberelec","core":"atari800","emulator":"retroarch","extensions":[".7z",".a52",".atr",".atx",".bin",".car",".cas",".cdm",".rom",".xex",".xfd",".zip"],"fallback":["atari-400-800|atari800"],"folder":"atari800","profile":"atari800","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:247","systems":["atari-400-800"]},{"basis":"native name of retrodeck","core":"virtualjaguar","emulator":"retroarch","extensions":[".7z",".j64",".jag",".zip"],"fallback":["atari-jaguar|virtualjaguar"],"folder":"atarijaguar","profile":"virtualjaguar","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:292","systems":["atari-jaguar"]},{"basis":"native name of amberelec","core":"handy","emulator":"retroarch","extensions":[".7z",".lnx",".o",".zip"],"fallback":["atari-lynx|handy"],"folder":"atarilynx","profile":"handy","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:1043","systems":["atari-lynx"]},{"basis":"native name of amberelec","core":"hatari","emulator":"retroarch","extensions":[".7z",".dim",".ipf",".m3u",".msa",".st",".stx",".zip"],"fallback":["atari-st|hatari"],"folder":"atarist","profile":"hatari","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:267","systems":["atari-st"]},{"arcade":true,"basis":"native name of amberelec","core":"flycast2021le","emulator":"retrorun","extensions":[".7z",".bin",".dat",".lst",".zip"],"fallback":["sega-dreamcast-arcade|flycast2021le"],"folder":"atomiswave","profile":"flycast2021le","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:317","systems":["sega-dreamcast-arcade"]},{"basis":"systems of raze","core":"raze","emulator":"raze","extensions":[".build"],"fallback":["blood|raze","duke3d|raze","exhumed|raze","nam|raze","powerslave|raze","redneck|raze","shadow-warrior|raze","ww2gi|raze"],"folder":"build","profile":"raze","source_ref":"os packages/ui/emulationstation/config/es_systems.cfg:2855","systems":["blood","duke3d","exhumed","nam","powerslave","redneck","shadow-warrior","ww2gi"]},{"basis":"normalized idLine truncated
+1 -1
View File
@@ -9,7 +9,7 @@ params:
sources:
os:
repo: https://github.com/AmberELEC/AmberELEC
commit: 5a3882cbb755dcd614aea8952ff20f5a4151505b
commit: a21975bfb1a3af68f66e07a931b994b523e1e6e2
es:
repo: https://github.com/AmberELEC/emulationstation
commit: e3846751ce50fdced1f2bbd7a5bb51f4da2e7007
+1 -1
View File
@@ -4,7 +4,7 @@ platform: amberelec
sources:
os:
repo: "https://github.com/AmberELEC/AmberELEC"
commit: 5a3882cbb755dcd614aea8952ff20f5a4151505b
commit: a21975bfb1a3af68f66e07a931b994b523e1e6e2
es:
repo: "https://github.com/AmberELEC/emulationstation"
commit: e3846751ce50fdced1f2bbd7a5bb51f4da2e7007
+119 -1
View File
@@ -57,6 +57,7 @@ from common import ( # noqa: E402
apply_target_overrides,
list_registered_platforms,
load_emulator_profiles,
load_platform_registry,
upstream_profile_index,
write_text_atomic,
yaml_load,
@@ -82,7 +83,9 @@ INSTALLERS = ("install.sh", "install.ps1")
SHOWN_ITEMS = 4 # items named in a diff summary before ", ..."
SHOWN_NAMES = 12 # core names named in an unresolved row before ", ..."
AREAS = ("platforms", "native", "targets", "coreinfo", "data", "catalogs", "ci", "profiles")
AREAS = (
"platforms", "native", "targets", "romfolders", "coreinfo", "data", "catalogs", "ci", "profiles",
)
OK, STALE, UNKNOWN, ERROR, SKIPPED = "OK", "STALE", "UNKNOWN", "ERROR", "SKIPPED"
# Every status a finding may carry, in the order the summary counts them.
STATUSES = (STALE, UNKNOWN, ERROR, SKIPPED, OK)
@@ -725,6 +728,119 @@ def _age_days(stamp: str, now: datetime) -> int | None:
return (now - then).days
# --- rom folders ------------------------------------------------------------
def romfolder_sources(platforms_dir: Path) -> dict[str, dict]:
"""setup -> its platforms/romfolders/_sources/<setup>.yml."""
out = {}
for path in sorted((platforms_dir / "romfolders" / "_sources").glob("*.yml")):
with path.open(encoding="utf-8") as fh:
out[path.stem] = yaml_load(fh) or {}
return out
def _os_source(doc: dict) -> dict:
"""The source the setup is pinned on: `os`, else the first declared.
The others follow from it (an EmulationStation pinned by a package.mk)."""
sources = doc.get("sources") or {}
return sources.get("os") or next(iter(sources.values()), {})
def platform_revision(setup: str, doc: dict, platforms_dir: Path) -> str:
"""The revision the setup's own platform file was scraped at, when the
setup is a registered platform read from the same repository; '' else."""
path = platforms_dir / f"{setup}.yml"
if setup not in load_platform_registry(str(platforms_dir)) or not path.is_file():
return ""
with path.open(encoding="utf-8") as fh:
source = str((yaml_load(fh) or {}).get("source") or "")
repo = upstream.parse_repo(str(_os_source(doc).get("repo") or ""))
if repo is None or f"/{repo.slug.lower()}/" not in source.lower() + "/":
return ""
return export_native.source_revision(source)
def romfolder_pin_drift(setup: str, doc: dict, platforms_dir: Path) -> str | None:
"""Why the folders and the BIOS of one platform come from two revisions,
None when they do not or when only the forge can tell (a tag on one
side, a commit on the other)."""
revision = platform_revision(setup, doc, platforms_dir)
os_source = _os_source(doc)
tag, commit = os_source.get("tag"), str(os_source.get("commit") or "")
if not revision or revision in (tag, commit):
return None
if tag or len(revision) == len(commit):
return f"platforms/{setup}.yml was scraped at {revision[:12]}, the rom folders at {tag or commit[:12]}"
return None
def check_romfolders(platforms_dir: Path, cache_dir: str, offline: bool) -> list[Finding]:
"""The pins of platforms/romfolders/_sources/ against what they follow.
A setup that is a registered platform is read at the revision its
platform file was scraped at: the games-on-card mode otherwise installs
the BIOS of one release for the folders of another. Any other setup is
compared with its project's newest release, or with its branch head
when the project publishes none. A pin is moved by hand, then
`python -m scripts.scraper.romfolders --setup NAME` reads it again.
"""
findings: list[Finding] = []
for setup, doc in romfolder_sources(platforms_dir).items():
os_source = _os_source(doc)
pin = str(os_source.get("tag") or str(os_source.get("commit") or "")[:12])
drift = romfolder_pin_drift(setup, doc, platforms_dir)
if drift:
findings.append(Finding("romfolders", setup, STALE, local=pin, detail=drift))
continue
revision = platform_revision(setup, doc, platforms_dir)
repo = upstream.parse_repo(str(os_source.get("repo") or ""))
commit = str(os_source.get("commit") or "")
if revision and revision in (os_source.get("tag"), commit):
findings.append(Finding("romfolders", setup, OK, local=pin,
detail=f"read where platforms/{setup}.yml is"))
continue
if offline:
findings.append(Finding("romfolders", setup, SKIPPED, local=pin, detail="offline"))
continue
if repo is None or not commit:
findings.append(Finding("romfolders", setup, UNKNOWN, local=pin, detail="no forge to ask"))
continue
findings.append(_romfolder_upstream(setup, repo, os_source, revision, cache_dir))
return findings
def _romfolder_upstream(
setup: str, repo: upstream.Repo, os_source: dict, revision: str, cache_dir: str,
) -> Finding:
commit, tag = str(os_source["commit"]), os_source.get("tag")
pin = str(tag or commit[:12])
if revision:
# The platform names a tag and the setup a commit: the tag's commit decides.
tagged = upstream.resolve_tag_commit(repo, revision, cache_dir)
if tagged is None:
return Finding("romfolders", setup, UNKNOWN, local=pin, detail=f"tag {revision} not listed")
if tagged == commit:
return Finding("romfolders", setup, OK, local=pin, detail=f"commit of {revision}")
return Finding("romfolders", setup, STALE, local=pin, remote=revision,
detail=f"platforms/{setup}.yml was scraped at {revision} ({tagged[:12]})")
pinned_at = (upstream.commit_date(repo, commit, cache_dir) or "")[:10]
release = upstream.latest_release(repo, cache_dir)
if release is not None and release.date:
newer = release.tag != tag and release.date > pinned_at
return Finding("romfolders", setup, STALE if newer else OK, local=f"{pin} ({pinned_at})",
remote=f"{release.tag} ({release.date})",
detail="a release is newer than the pin" if newer else "newest release")
head = upstream.resolve_head(repo, cache_dir)
if head is None:
return Finding("romfolders", setup, UNKNOWN, local=pin, detail="branch head unavailable")
if head == commit:
return Finding("romfolders", setup, OK, local=pin, detail="branch head")
counts = upstream.ancestry(repo, commit, head, cache_dir)
behind = f"{counts[0]} commits behind the branch head" if counts else "the branch head moved"
return Finding("romfolders", setup, STALE, local=pin, remote=head[:12], detail=behind)
# --- core-info --------------------------------------------------------------
@@ -1574,6 +1690,8 @@ def main() -> int:
)
if "targets" in areas:
findings.extend(check_targets(platforms_dir, workdir, profiles, args.jobs, args.offline))
if "romfolders" in areas:
findings.extend(check_romfolders(platforms_dir, args.cache_dir, args.offline))
if "coreinfo" in areas:
findings.extend(check_coreinfo(profiles, args.cache_dir, args.offline))
if "data" in areas:
+48
View File
@@ -353,6 +353,54 @@ class PinParsingTests(unittest.TestCase):
self.assertTrue(cf.specifier_allows(">=10.14", "12.0.1"))
class RomFolderPins(unittest.TestCase):
"""The rom folders of a setup were pinned by hand and nothing compared
the pins with the platform file or the project's releases."""
def _tree(self, tmp: str, platform_source: str, os_source: str) -> Path:
root = Path(tmp)
(root / "romfolders" / "_sources").mkdir(parents=True)
(root / "_registry.yml").write_text("platforms:\n plat: {}\n")
(root / "plat.yml").write_text(f"source: {platform_source}\n")
(root / "romfolders" / "_sources" / "plat.yml").write_text(
f"sources:\n os:\n repo: https://github.com/o/r\n{os_source}")
(root / "romfolders" / "_sources" / "other.yml").write_text(
"sources:\n os:\n repo: https://github.com/x/y\n commit: " + "c" * 40 + "\n")
return root
def test_a_platform_setup_follows_its_platform_file(self):
with tempfile.TemporaryDirectory() as tmp:
root = self._tree(tmp, "https://raw.githubusercontent.com/o/r/v2/list.txt",
" tag: v1\n commit: " + "a" * 40 + "\n")
rows = {f.subject: f for f in cf.check_romfolders(root, "", True)}
self.assertEqual(rows["plat"].status, cf.STALE)
self.assertIn("scraped at v2, the rom folders at v1", rows["plat"].detail)
self.assertEqual(rows["other"].status, cf.SKIPPED)
def test_the_same_revision_is_fresh(self):
with tempfile.TemporaryDirectory() as tmp:
root = self._tree(tmp, "https://github.com/o/r/tree/" + "a" * 40,
" commit: " + "a" * 40 + "\n")
rows = {f.subject: f for f in cf.check_romfolders(root, "", True)}
self.assertEqual(rows["plat"].status, cf.OK)
def test_a_newer_release_makes_another_setup_stale(self):
release = cf.upstream.Release("v9", "2026-11-01", False)
with tempfile.TemporaryDirectory() as tmp:
root = self._tree(tmp, "https://github.com/o/r/tree/" + "a" * 40,
" commit: " + "a" * 40 + "\n")
with mock.patch.object(cf.upstream, "commit_date", return_value="2026-10-01T00:00:00"), \
mock.patch.object(cf.upstream, "latest_release", return_value=release):
rows = {f.subject: f for f in cf.check_romfolders(root, "", False)}
self.assertEqual((rows["other"].status, rows["other"].remote), (cf.STALE, "v9 (2026-11-01)"))
def test_the_committed_setups_agree_with_their_platform_files(self):
platforms = REPO_ROOT / "platforms"
drift = [d for setup, doc in cf.romfolder_sources(platforms).items()
if (d := cf.romfolder_pin_drift(setup, doc, platforms))]
self.assertEqual(drift, [])
class CatalogParsingTests(unittest.TestCase):
def test_game_catalogs_are_watched(self):
"""The 38 game catalogs the games-on-card mode identifies discs by
+8
View File
@@ -676,6 +676,14 @@ patches from and reports an original that is absent, that came from another
URL than the one the platform YAML names, or that was fetched before the
platform YAML was last rewritten.
The `romfolders` area compares each pin of `platforms/romfolders/_sources/`.
A setup that is a registered platform must be read at the revision its
platform file was scraped at, which needs no network unless one side names a
tag and the other a commit. Any other setup is compared with its project's
newest release, or with its branch head when the project publishes none. A
stale pin is moved by hand, then `python -m scripts.scraper.romfolders --setup
NAME` reads the folders again.
The `profiles` area always checks `website/src/data/source-paths.json`
against the profiles, offline: a recorded tree that no profile pins any more
is `STALE` until `sitesources.py --fetch` runs again.