mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-13 12:22:33 -05:00
Compare commits
14 Commits
5cbd461a97
...
f1855641c5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1855641c5 | ||
|
|
2666ebd9b7 | ||
|
|
22a1e7caf4 | ||
|
|
67186448a2 | ||
|
|
70891314d3 | ||
|
|
97b9900f62 | ||
|
|
a1aa97a70e | ||
|
|
97b1c2c08a | ||
|
|
0624e9d87e | ||
|
|
3ded72f72b | ||
|
|
94a28f5459 | ||
|
|
837ac80cca | ||
|
|
43cb7a9884 | ||
|
|
020ff148c2 |
@@ -111,4 +111,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
||||
|
||||
This repository provides BIOS files for personal backup and archival purposes.
|
||||
|
||||
*Auto-generated on 2026-03-27T22:52:26Z*
|
||||
*Auto-generated on 2026-03-28T08:23:13Z*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<database version="1.0" conformance="loose">
|
||||
<game>
|
||||
<cartridge system="NES-PAL" dump="ok" crc="001388B3" sha1="4BCD36C05FCAF45C74001257C65AFB7EC5FA53D7">
|
||||
@@ -69,6 +69,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="00D2CB22" sha1="CCD60DBC65EC004956E972E116BDD114E8818E3E">
|
||||
<board type="NES-NROM-256" mapper="0">
|
||||
<prg size="32k" />
|
||||
<chr size="8k" />
|
||||
<pad h="1" v="0" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="00E95D86" sha1="8957393A397DB102BCE5A64B4D85384D1F2E5D20">
|
||||
<board type="NES-UNROM" mapper="2">
|
||||
@@ -767,6 +779,15 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="084F61CD" sha1="44BC6C4E8B3F6C635281B4C05382E8F316D8269E">
|
||||
<board type="NAMCOT-3301" mapper="0">
|
||||
<prg size="8k" />
|
||||
<chr size="8k" />
|
||||
<pad h="0" v="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="085DE7C9" sha1="93339F07696CE1B98F1272613067482A02F47B95">
|
||||
<board type="NES-SLROM" mapper="1">
|
||||
@@ -1043,7 +1064,7 @@
|
||||
<device type="zapper" />
|
||||
</peripherals>
|
||||
<cartridge system="Famicom" dump="unknown" crc="0AFB395E" sha1="CFFAC7D2ECB18A28C36E0E90A6682DFE5BA6E3D1">
|
||||
<board mapper="5">
|
||||
<board type="HVC-ELROM" mapper="5">
|
||||
<prg size="128k" />
|
||||
<chr size="128k" />
|
||||
</board>
|
||||
@@ -2018,6 +2039,16 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="1394DED0" sha1="B1C6A700A9F3B73666018E46515D376F06B8E9C2">
|
||||
<board type="NES-UNROM" mapper="2">
|
||||
<prg size="64k" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="1394F57E" sha1="FD9079CB5E8479EB06D93C2AE5175BFCE871746A">
|
||||
<board type="NES-SEROM" mapper="1">
|
||||
@@ -2835,9 +2866,20 @@
|
||||
</board>
|
||||
</arcade>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="zapper" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="unknown" crc="1CA9C322" sha1="17869C4F55461D50E134CC3A4D15B89E7CAF8DE3">
|
||||
<board mapper="258">
|
||||
<prg size="128k" />
|
||||
<chr size="128k" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="1CED086F" sha1="46C0B521B3C595409C05972388909CCB0D5F6369">
|
||||
<board mapper="5">
|
||||
<board type="HVC-ETROM" mapper="5">
|
||||
<prg size="256k" />
|
||||
<chr size="128k" />
|
||||
<wram size="8k" />
|
||||
@@ -3086,6 +3128,17 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="zapper" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="1F14123C" sha1="9B1B8354B7449FDDA4176C93BCE7660D47F66019">
|
||||
<board mapper="4">
|
||||
<prg size="32k" />
|
||||
<chr size="16k" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="ok" crc="1F2D9DB7" sha1="544203A8304A7922A46579512665C743527CA1E6">
|
||||
<board type="HVC-NROM-256" mapper="0">
|
||||
@@ -4803,6 +4856,17 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="2DA5ECE0" sha1="F3554E45D3261157653643C23A378C0295A5F893">
|
||||
<board type="NES-NROM-256" mapper="0">
|
||||
<prg size="32k" />
|
||||
<chr size="8k" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="2DB7C31E" sha1="9BF95EEB404F103422E06214566C7D918ED4DC79">
|
||||
<board mapper="1">
|
||||
@@ -4939,6 +5003,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="ok" crc="2EA914FA" sha1="54F6E9C7F59D7A1B961F694716F5D4967BB55AA8">
|
||||
<board type="KONAMI-VRC-4" mapper="25">
|
||||
<prg size="128k" />
|
||||
<chr size="128k" />
|
||||
<chip type="Konami VRC IV">
|
||||
<pin number="3" function="PRG A2" />
|
||||
<pin number="4" function="PRG A3" />
|
||||
</chip>
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="2EBF2E0D" sha1="0E37A2766280D73F2921567348A8D360707A5924">
|
||||
<board mapper="1">
|
||||
@@ -5064,6 +5140,20 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="turbofile" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="3046C4D5" sha1="1AF51255F2837484974DA44650B186333472C7B2">
|
||||
<board type="NES-SXROM" mapper="1">
|
||||
<prg size="512k" />
|
||||
<vram size="8k" />
|
||||
<wram size="32k" battery="1" />
|
||||
<pad h="1" v="0" />
|
||||
<chip type="MMC1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-PAL" dump="ok" crc="304FA926" sha1="EFFE8CCAA78F94F061B142042557B478B4B213EE">
|
||||
<board type="NES-NROM-128" mapper="0">
|
||||
@@ -6706,6 +6796,19 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="40E1F09E" sha1="2B44E45C621D52C882C50BEF7A2F9B4C93DDF908">
|
||||
<board mapper="2">
|
||||
<prg size="64k" />
|
||||
<vram size="8k" />
|
||||
<wram size="8k" />
|
||||
<pad h="1" v="0" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="40ED2A9D" sha1="D4E9126D02C9923C3871FD352248F41298498D4E">
|
||||
<board type="NES-SEROM" mapper="1">
|
||||
@@ -7062,6 +7165,9 @@
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="pachinko" />
|
||||
</peripherals>
|
||||
<cartridge system="Famicom" dump="unknown" crc="44F92026" sha1="9266BE2FD5D0C712FE7BF873D32AE50506A9B277">
|
||||
<board mapper="1">
|
||||
<prg size="128k" />
|
||||
@@ -7963,6 +8069,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="4E6B9078" sha1="93FF8CEC778771C7200F785798E0D1599EE8FEB5">
|
||||
<board type="NES-NROM-256" mapper="0">
|
||||
<prg size="32k" />
|
||||
<chr size="8k" />
|
||||
<pad h="0" v="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="4E7729FF" sha1="5FA23F88432006DCF6874EA36E9E7DA8934427BE">
|
||||
<board mapper="182">
|
||||
@@ -8144,6 +8262,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="Famicom" dump="ok" crc="4FF17864" sha1="5119F1D6B67C5E44D63BA1E7080A6FE17623415C">
|
||||
<board type="NES-SLROM" mapper="1">
|
||||
<prg size="128k" />
|
||||
<chr size="128k" />
|
||||
<chip type="MMC1B2" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="4FFD17F0" sha1="27CB8AEAF0EA97A6C69D3D90BC056C5EB61695F6">
|
||||
<board mapper="194">
|
||||
@@ -8648,6 +8778,15 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="ok" crc="54C6FE75" sha1="7C30C244C36C9D1556C79458F06FC46C786028C6">
|
||||
<board type="JALECO-JF-22-SRAM" mapper="75">
|
||||
<prg size="256k" />
|
||||
<chr size="128k" />
|
||||
<wram size="8k" battery="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-PAL-B" dump="ok" crc="54E43C57" sha1="1F6072AE901F3D3530ADCD3C136178E3C7354990">
|
||||
<board type="NES-TLROM" mapper="4">
|
||||
@@ -8958,6 +9097,19 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="58094016" sha1="8354E33F44156C42C7013EBBEACF378BFA6FD6B2">
|
||||
<board mapper="4">
|
||||
<prg size="64k" />
|
||||
<chr size="128k" />
|
||||
<wram size="8k" battery="1" />
|
||||
<pad h="0" v="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="unknown" crc="58152B42" sha1="1E49BDA9CEF18F6F5C2DA34910487713D364AA68">
|
||||
<board mapper="79">
|
||||
@@ -9318,6 +9470,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="5B8D4378" sha1="7194D3DB031C9342BB473A4ADE364DB1631B5EC9">
|
||||
<board mapper="34">
|
||||
<prg size="32k" />
|
||||
<vram size="12k" />
|
||||
<wram size="8k" battery="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="5BB62688" sha1="D6615439A90FC68758C4149F0CBBE6D1331451F3">
|
||||
<board type="NES-DEROM" mapper="206">
|
||||
@@ -10079,6 +10243,16 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="633AFE6F" sha1="2F29F3DC724027FAD926BC9D4470A481884E42A5">
|
||||
<board type="NES-HKROM" mapper="4">
|
||||
<prg size="32k" />
|
||||
<chr size="8k" />
|
||||
<pad h="0" v="1" />
|
||||
<chip type="MMC6B" battery="0" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="63469396" sha1="C6FEF52264372FAB620D1E5EE6A3E60E46262775">
|
||||
<board mapper="1">
|
||||
@@ -10126,7 +10300,7 @@
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="6396B988" sha1="B326D1984D5D369BC168028AD7672D2EFC2ECDDB">
|
||||
<board mapper="5">
|
||||
<board type="HVC-ETROM" mapper="5">
|
||||
<prg size="256k" />
|
||||
<chr size="128k" />
|
||||
<wram size="8k" />
|
||||
@@ -10248,6 +10422,19 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="64AFD592" sha1="618AC6835B96BB5EBFC57DD3B828FAFBB0E0FC7D">
|
||||
<board type="KONAMI-VRC-4" mapper="23">
|
||||
<prg size="128k" />
|
||||
<chr size="128k" />
|
||||
<wram size="8k" battery="1" />
|
||||
<chip type="Konami VRC IV">
|
||||
<pin number="3" function="PRG A3" />
|
||||
<pin number="4" function="PRG A2" />
|
||||
</chip>
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="64B710D2" sha1="2875F130DAC4C13FFB1D2FDB655A89AED7FEB44A">
|
||||
<board type="NES-UNROM" mapper="2">
|
||||
@@ -10833,6 +11020,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="Famicom" dump="unknown" crc="69977C9E" sha1="C43D5F049F4F7862E6DECCA7500C0C23E349AF9F">
|
||||
<board mapper="0">
|
||||
<prg size="32k" />
|
||||
<chr size="8k" />
|
||||
<pad h="0" v="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="6997F5E1" sha1="4C8716C4651973B5F6811D6CA9A0F1E2C4E26FA3">
|
||||
<board type="NES-CNROM" mapper="3">
|
||||
@@ -11327,7 +11526,7 @@
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="6F4E4312" sha1="99CF6CA63B173A2B86125F16BBE11885EF1AC377">
|
||||
<board mapper="5">
|
||||
<board type="HVC-EWROM" mapper="5">
|
||||
<prg size="512k" />
|
||||
<chr size="256k" />
|
||||
<wram size="32k" battery="1" />
|
||||
@@ -11744,6 +11943,17 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-PAL" dump="ok" crc="73298C87" sha1="16151504C01A3E89C7893A87567B0F31DC651D96">
|
||||
<board type="PAL-ZZ" mapper="37">
|
||||
<prg size="256k" />
|
||||
<chr size="256k" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<arcade system="Playchoice-10" dump="unknown" crc="732B0675" sha1="B50E2DCF63E724F3FE8E5ADED50F32AA95775676">
|
||||
<board mapper="1">
|
||||
@@ -12063,6 +12273,15 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="76A6A813" sha1="0FE9120FD5ADC2790B0B9E8FADD136F9C66A709F">
|
||||
<board type="NAMCOT-3301" mapper="0">
|
||||
<prg size="8k" />
|
||||
<chr size="8k" />
|
||||
<pad h="0" v="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-PAL" dump="ok" crc="76C161E3" sha1="0711BC8D0BF42A0829391C2320393A0D3DF2DD1F">
|
||||
<board type="NES-SGROM" mapper="1">
|
||||
@@ -13195,6 +13414,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="8281C50F" sha1="DDAB9E627E9F7B7DB068B120CF857D51B2A935C9">
|
||||
<board type="KONAMI-VRC-4" mapper="23">
|
||||
<prg size="128k" />
|
||||
<chr size="128k" />
|
||||
<chip type="Konami VRC IV">
|
||||
<pin number="3" function="PRG A3" />
|
||||
<pin number="4" function="PRG A2" />
|
||||
</chip>
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="828F8F1F" sha1="9DC376442DB43C7786230AEEB54D5D643A4104E6">
|
||||
<board mapper="1">
|
||||
@@ -13580,6 +13811,15 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="ok" crc="8589652D" sha1="0CC7ED7F5D7EE0959EE3724C3AF06EF8DF397C59">
|
||||
<board type="NANJING" mapper="163">
|
||||
<prg size="2048k" />
|
||||
<wram size="8k" battery="1" />
|
||||
<pad h="0" v="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="8593E5AD" sha1="84D2B96C2821FDC246DD876932F4E1752DF1CA73">
|
||||
<board type="NES-TLROM" mapper="4">
|
||||
@@ -14365,6 +14605,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="8C29D397" sha1="69612DDE41A2C52A802D9768D7C2942572939867">
|
||||
<board type="NES-NROM-256" mapper="0">
|
||||
<prg size="32k" />
|
||||
<chr size="8k" />
|
||||
<pad h="0" v="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="turbofile" />
|
||||
@@ -14829,6 +15081,17 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="90D2E9F0" sha1="2801AADD9D0308CF2C9069A2BB76242ECA5B1501">
|
||||
<board type="NES-NROM-256" mapper="0">
|
||||
<prg size="32k" />
|
||||
<chr size="8k" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="90D68A43" sha1="7698CE7AE3B83F12518169ECFEEE4D76D643C842">
|
||||
<board type="NES-CNROM" mapper="3">
|
||||
@@ -15735,6 +15998,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="9908C6C9" sha1="0A98AB1B7D069A00ACA5D1EB5975C181B2F79E5A">
|
||||
<board type="NES-CNROM" mapper="3">
|
||||
<prg size="32k" />
|
||||
<chr size="32k" />
|
||||
<pad h="0" v="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="990985C0" sha1="AA08F65D6333448F088D8DCE32F3895662B577DE">
|
||||
<board type="NES-SLROM" mapper="1">
|
||||
@@ -15981,6 +16256,9 @@
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="pachinko" />
|
||||
</peripherals>
|
||||
<cartridge system="Famicom" dump="unknown" crc="9B3C5124" sha1="A96BFC7B51E2F7FF69F42B024CC9FB85CA9A943D">
|
||||
<board mapper="4">
|
||||
<prg size="256k" />
|
||||
@@ -16633,6 +16911,19 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="9FC43DD8" sha1="A4A44A54BA682223503BEABE55D5031B5C62B2A7">
|
||||
<board mapper="34">
|
||||
<prg size="32k" />
|
||||
<vram size="8k" />
|
||||
<wram size="8k" />
|
||||
<pad h="1" v="0" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="ok" crc="9FD35802" sha1="C38AF729C2BE2940FCA620F86415FAE304F1D8C9">
|
||||
<board type="HVC-CNROM" mapper="3">
|
||||
@@ -17110,6 +17401,16 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="A512BDF6" sha1="F794FDA12D34E611D58E652319ED583AE61B81E0">
|
||||
<board type="NES-HKROM" mapper="4">
|
||||
<prg size="32k" />
|
||||
<chr size="8k" />
|
||||
<pad h="0" v="1" />
|
||||
<chip type="MMC6B" battery="0" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="A531E1AB" sha1="0A0772721642DE00FE575CA109891E274251D815">
|
||||
<board mapper="4">
|
||||
@@ -18875,7 +19176,7 @@
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="B4735FAC" sha1="4AC3E9136706AB009EE2F68C7D009422D73EE8E8">
|
||||
<board mapper="5">
|
||||
<board type="HVC-ELROM" mapper="5">
|
||||
<prg size="512k" />
|
||||
<chr size="512k" />
|
||||
</board>
|
||||
@@ -19569,7 +19870,7 @@
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="BB7F829A" sha1="BC7393653D04F3E3D35E3D0623ACA4A2C27E0AA1">
|
||||
<board mapper="5">
|
||||
<board type= "HVC-ELROM" mapper="5">
|
||||
<prg size="128k" />
|
||||
<chr size="128k" />
|
||||
</board>
|
||||
@@ -19696,7 +19997,7 @@
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="BC80FB52" sha1="74DBA27392CA4451875AD0267E5466F92D835A62">
|
||||
<board mapper="5">
|
||||
<board type="HVC-EKROM" mapper="5">
|
||||
<prg size="256k" />
|
||||
<chr size="256k" />
|
||||
<wram size="8k" battery="1" />
|
||||
@@ -19828,6 +20129,15 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="unknown" crc="BD75ED79" sha1="2E2B42DA4E0F41F411C778B5A9B66AA9FBD1167E">
|
||||
<board type="CAMERICA-BF9093" mapper="71">
|
||||
<prg size="64k" />
|
||||
<vram size="8k" />
|
||||
<pad h="1" v="0" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="ok" crc="BD9D0E85" sha1="F82654BE44E4B01B3F9627D81232A086B1CF7599">
|
||||
<board type="HVC-UNROM" mapper="2">
|
||||
@@ -20371,6 +20681,9 @@
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="pachinko" />
|
||||
</peripherals>
|
||||
<cartridge system="Famicom" dump="unknown" crc="C22C23AB" sha1="4CEA0ECDF0A22E678B827C9BFD8D80B5DEBB4094">
|
||||
<board mapper="1">
|
||||
<prg size="128k" />
|
||||
@@ -20947,6 +21260,18 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="pokkunmoguraa" />
|
||||
</peripherals>
|
||||
<cartridge system="Famicom" dump="unknown" crc="C7BCC981" sha1="8A0DEADD84A0967B1D2DB0634262C7BDBBB732B7">
|
||||
<board mapper="3">
|
||||
<prg size="32k" />
|
||||
<chr size="32k" />
|
||||
<pad h="0" v="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Dendy" dump="unknown" crc="C7EDBC2E" sha1="E4414C160C7E91136C62D99154336035E5636EEB">
|
||||
<board mapper="13">
|
||||
@@ -22458,7 +22783,7 @@
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-PAL-B" dump="ok" crc="D67FD6A6" sha1="85DE67A28E01EF680F2FF6AAE80E4315491CEEE9">
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="D67FD6A6" sha1="85DE67A28E01EF680F2FF6AAE80E4315491CEEE9">
|
||||
<board type="NES-SNROM" mapper="1">
|
||||
<prg size="128k" />
|
||||
<vram size="8k" />
|
||||
@@ -23635,6 +23960,9 @@
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="pachinko" />
|
||||
</peripherals>
|
||||
<cartridge system="Famicom" dump="unknown" crc="E08C8A60" sha1="BAF3A4E0423A86E53234E806843149EF7D0974A9">
|
||||
<board mapper="4">
|
||||
<prg size="512k" />
|
||||
@@ -25207,7 +25535,7 @@
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="EEE9A682" sha1="46C443D0EB27AF7A566E744F096F981034A06E59">
|
||||
<board mapper="5">
|
||||
<board type="HVC-ETROM" mapper="5">
|
||||
<prg size="256k" />
|
||||
<chr size="128k" />
|
||||
<wram size="8k" />
|
||||
@@ -25492,6 +25820,14 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="F312D1DE" sha1="35C157A921156E47FD3F6573D150F54108D0EDFC">
|
||||
<board type="NES-TXROM" mapper="4">
|
||||
<prg size="16k" />
|
||||
<chip type="MMC3A" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="F31D36A3" sha1="00147962462C44354735861D0258D72314635458">
|
||||
<board type="NES-TSROM" mapper="4">
|
||||
@@ -25606,17 +25942,6 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
</peripherals>
|
||||
<cartridge system="NES-PAL" dump="ok" crc="73298C87" sha1="16151504C01A3E89C7893A87567B0F31DC651D96">
|
||||
<board type="PAL-ZZ" mapper="37">
|
||||
<prg size="256k" />
|
||||
<chr size="256k" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<peripherals>
|
||||
<device type="fourplayer" />
|
||||
@@ -25698,7 +26023,7 @@
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="F540677B" sha1="44A5BC2B8156D50518EEBEEFA522A7642E0476DC">
|
||||
<board mapper="5">
|
||||
<board type="HVC-EWROM" mapper="5">
|
||||
<prg size="512k" />
|
||||
<chr size="256k" />
|
||||
<wram size="32k" battery="1" />
|
||||
@@ -26686,6 +27011,16 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="NES-NTSC" dump="ok" crc="FDC7C50B" sha1="5E8F67BEFB2B1BCBD0384E3144ACB2766FC3E443">
|
||||
<board type="NES-ETROM" mapper="5">
|
||||
<prg size="128k" />
|
||||
<chr size="128k" />
|
||||
<wram size="8k" />
|
||||
<wram size="8k" battery="1" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="FDD89C45" sha1="D25327E0F0D8539AE761DF861254BDE3A60FDD96">
|
||||
<board mapper="0">
|
||||
@@ -26767,7 +27102,7 @@
|
||||
</game>
|
||||
<game>
|
||||
<cartridge system="Famicom" dump="unknown" crc="FE3488D1" sha1="800AEFE756E85A0A78CCB4DAE68EBBA5DF24BF41">
|
||||
<board mapper="5">
|
||||
<board type="HVC-ETROM" mapper="5">
|
||||
<prg size="512k" />
|
||||
<chr size="128k" />
|
||||
<wram size="8k" />
|
||||
@@ -26916,4 +27251,15 @@
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>
|
||||
<!--game>
|
||||
<peripherals>
|
||||
<device type="zapper" />
|
||||
</peripherals>
|
||||
<cartridge system="Famicom" dump="ok" crc="A0FBF02E" sha1="38236FBD5B70F651674D52EE519AB4DBB11F7955">
|
||||
<board type="UNL-TF1201" mapper="298">
|
||||
<prg size="128k" />
|
||||
<chr size="128k" />
|
||||
</board>
|
||||
</cartridge>
|
||||
</game>-->
|
||||
</database>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"generated_at": "2026-03-27T22:52:09Z",
|
||||
"generated_at": "2026-03-28T08:10:17Z",
|
||||
"total_files": 6756,
|
||||
"total_size": 5589782999,
|
||||
"total_size": 5589795834,
|
||||
"files": {
|
||||
"520d3d1b5897800af47f92efd2444a26b7a7dead": {
|
||||
"path": "bios/3DO Company/3DO/3do_arcade_saot.bin",
|
||||
@@ -33213,15 +33213,15 @@
|
||||
"crc32": "5205222b",
|
||||
"adler32": "34389d20"
|
||||
},
|
||||
"26322f182540211e9b5e3647675b7c593706ae2b": {
|
||||
"f92312bae56e29c5bf00a5103105fce78472bf5c": {
|
||||
"path": "bios/Nintendo/NES/NstDatabase.xml",
|
||||
"name": "NstDatabase.xml",
|
||||
"size": 1009534,
|
||||
"sha1": "26322f182540211e9b5e3647675b7c593706ae2b",
|
||||
"md5": "7bfe8c0540ed4bd6a0f1e2a0f0118ced",
|
||||
"sha256": "914584ee6964e8b4cc4ff092874052e7baf13708cfb3f35940342421fcf1bedc",
|
||||
"crc32": "ebb2196c",
|
||||
"adler32": "88d01ea2"
|
||||
"size": 1022369,
|
||||
"sha1": "f92312bae56e29c5bf00a5103105fce78472bf5c",
|
||||
"md5": "0ee6cbdc6f5c96ce9c8aa5edb59066f4",
|
||||
"sha256": "ef5bfd08928b4c9186ba03628ae9f2b7a3b0e30c9204592ac72cd67fa8a31f3a",
|
||||
"crc32": "0e4d552b",
|
||||
"adler32": "4f5c1356"
|
||||
},
|
||||
"f430a0d752a9fa0c7032db8131f9090d18f71779": {
|
||||
"path": "bios/Nintendo/NES/gamegenie.nes",
|
||||
@@ -70887,7 +70887,7 @@
|
||||
"b953eb1a8fc9922b3f7051c1cdc451f1": "ae7233cae8f94749796e0b740d6021e3b00a8926",
|
||||
"413154dd0e2c824c9b18b807fd03ec4e": "691e46213d8428befdf568157e670b971ab94e1d",
|
||||
"c03f6bbaf644eb9b3ee261dbe199eb42": "2faaf92bcaffe675f54f7249d30f3791507e22ab",
|
||||
"7bfe8c0540ed4bd6a0f1e2a0f0118ced": "26322f182540211e9b5e3647675b7c593706ae2b",
|
||||
"0ee6cbdc6f5c96ce9c8aa5edb59066f4": "f92312bae56e29c5bf00a5103105fce78472bf5c",
|
||||
"7f98d77d7a094ad7d069b74bd553ec98": "f430a0d752a9fa0c7032db8131f9090d18f71779",
|
||||
"aaf3666e4ed478e2964b46d6a7aa27ad": "37027d92e1015b82a7dc5c43e9f1649a961577ab",
|
||||
"8d3d9f294b6e174bc7b1d2fd1c727530": "bf861922dcb78c316360e3e742f4f70ff63c9bc3",
|
||||
@@ -83500,7 +83500,7 @@
|
||||
"2faaf92bcaffe675f54f7249d30f3791507e22ab"
|
||||
],
|
||||
"NstDatabase.xml": [
|
||||
"26322f182540211e9b5e3647675b7c593706ae2b"
|
||||
"f92312bae56e29c5bf00a5103105fce78472bf5c"
|
||||
],
|
||||
"gamegenie.nes": [
|
||||
"f430a0d752a9fa0c7032db8131f9090d18f71779"
|
||||
@@ -99007,7 +99007,7 @@
|
||||
"54c7d10e": "ae7233cae8f94749796e0b740d6021e3b00a8926",
|
||||
"8bbef508": "691e46213d8428befdf568157e670b971ab94e1d",
|
||||
"5205222b": "2faaf92bcaffe675f54f7249d30f3791507e22ab",
|
||||
"ebb2196c": "26322f182540211e9b5e3647675b7c593706ae2b",
|
||||
"0e4d552b": "f92312bae56e29c5bf00a5103105fce78472bf5c",
|
||||
"4c514089": "f430a0d752a9fa0c7032db8131f9090d18f71779",
|
||||
"76f51d6b": "37027d92e1015b82a7dc5c43e9f1649a961577ab",
|
||||
"7f933ce2": "bf861922dcb78c316360e3e742f4f70ff63c9bc3",
|
||||
|
||||
@@ -1325,9 +1325,9 @@ systems:
|
||||
- name: NstDatabase.xml
|
||||
destination: NstDatabase.xml
|
||||
required: true
|
||||
sha1: 26322f182540211e9b5e3647675b7c593706ae2b
|
||||
md5: 7bfe8c0540ed4bd6a0f1e2a0f0118ced
|
||||
crc32: ebb2196c
|
||||
sha1: f92312bae56e29c5bf00a5103105fce78472bf5c
|
||||
md5: 0ee6cbdc6f5c96ce9c8aa5edb59066f4
|
||||
crc32: 0e4d552b
|
||||
size: 1009534
|
||||
core: fceumm
|
||||
manufacturer: Nintendo
|
||||
|
||||
@@ -7310,7 +7310,7 @@ systems:
|
||||
- name: NstDatabase.xml
|
||||
destination: bios/NstDatabase.xml
|
||||
required: false
|
||||
md5: 7bfe8c0540ed4bd6a0f1e2a0f0118ced
|
||||
md5: 0ee6cbdc6f5c96ce9c8aa5edb59066f4
|
||||
nintendo-pokemon-mini:
|
||||
files:
|
||||
- name: bios.min
|
||||
|
||||
@@ -960,8 +960,8 @@ systems:
|
||||
- name: NstDatabase.xml
|
||||
destination: nes/NstDatabase.xml
|
||||
required: true
|
||||
sha1: 26322f182540211e9b5e3647675b7c593706ae2b
|
||||
md5: 7bfe8c0540ed4bd6a0f1e2a0f0118ced
|
||||
sha1: f92312bae56e29c5bf00a5103105fce78472bf5c
|
||||
md5: 0ee6cbdc6f5c96ce9c8aa5edb59066f4
|
||||
crc32: ebb2196c
|
||||
size: 1009534
|
||||
nintendo-pokemon-mini:
|
||||
|
||||
@@ -647,6 +647,29 @@ def resolve_platform_cores(
|
||||
return result
|
||||
|
||||
|
||||
MANUFACTURER_PREFIXES = (
|
||||
"microsoft-", "nintendo-", "sony-", "sega-", "snk-", "panasonic-",
|
||||
"nec-", "epoch-", "mattel-", "fairchild-", "hartung-", "tiger-",
|
||||
"magnavox-", "philips-", "bandai-", "casio-", "coleco-",
|
||||
"commodore-", "sharp-", "sinclair-", "atari-",
|
||||
)
|
||||
|
||||
|
||||
def derive_manufacturer(system_id: str, system_data: dict) -> str:
|
||||
"""Derive manufacturer name for a system.
|
||||
|
||||
Priority: explicit manufacturer field > system ID prefix > 'Other'.
|
||||
"""
|
||||
mfr = system_data.get("manufacturer", "")
|
||||
if mfr and mfr not in ("Various", "Other"):
|
||||
return mfr.split("|")[0].strip()
|
||||
s = system_id.lower().replace("_", "-")
|
||||
for prefix in MANUFACTURER_PREFIXES:
|
||||
if s.startswith(prefix):
|
||||
return prefix.rstrip("-").title()
|
||||
return "Other"
|
||||
|
||||
|
||||
def _norm_system_id(sid: str) -> str:
|
||||
"""Normalize system ID for cross-platform matching.
|
||||
|
||||
@@ -655,11 +678,7 @@ def _norm_system_id(sid: str) -> str:
|
||||
(e.g., "microsoft-xbox", "nintendo-wii-u").
|
||||
"""
|
||||
s = sid.lower().replace("_", "-")
|
||||
for prefix in ("microsoft-", "nintendo-", "sony-", "sega-",
|
||||
"snk-", "panasonic-", "nec-", "epoch-", "mattel-",
|
||||
"fairchild-", "hartung-", "tiger-", "magnavox-",
|
||||
"philips-", "bandai-", "casio-", "coleco-",
|
||||
"commodore-", "sharp-", "sinclair-"):
|
||||
for prefix in MANUFACTURER_PREFIXES:
|
||||
if s.startswith(prefix):
|
||||
s = s[len(prefix):]
|
||||
break
|
||||
@@ -1068,3 +1087,14 @@ def list_system_ids(emulators_dir: str) -> None:
|
||||
for sys_id in sorted(system_emus):
|
||||
count = len(system_emus[sys_id])
|
||||
print(f" {sys_id:35s} ({count} emulator{'s' if count > 1 else ''})")
|
||||
|
||||
|
||||
def list_platform_system_ids(platform_name: str, platforms_dir: str) -> None:
|
||||
"""Print system IDs from a platform's YAML config."""
|
||||
config = load_platform_config(platform_name, platforms_dir)
|
||||
systems = config.get("systems", {})
|
||||
for sys_id in sorted(systems):
|
||||
file_count = len(systems[sys_id].get("files", []))
|
||||
mfr = systems[sys_id].get("manufacturer", "")
|
||||
mfr_display = f" [{mfr.split('|')[0]}]" if mfr else ""
|
||||
print(f" {sys_id:35s} ({file_count} file{'s' if file_count != 1 else ''}){mfr_display}")
|
||||
|
||||
@@ -16,6 +16,7 @@ import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
@@ -25,9 +26,11 @@ from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import (
|
||||
MANUFACTURER_PREFIXES,
|
||||
_build_validation_index, build_zip_contents_index, check_file_validation,
|
||||
check_inside_zip, compute_hashes, fetch_large_file, filter_files_by_mode,
|
||||
group_identical_platforms, list_emulator_profiles, list_registered_platforms,
|
||||
group_identical_platforms, list_emulator_profiles, list_platform_system_ids,
|
||||
list_registered_platforms,
|
||||
filter_systems_by_target, list_system_ids, load_database,
|
||||
load_data_dir_registry, load_emulator_profiles, load_platform_config,
|
||||
md5_composite, resolve_local_file,
|
||||
@@ -46,6 +49,106 @@ DEFAULT_OUTPUT_DIR = "dist"
|
||||
DEFAULT_BIOS_DIR = "bios"
|
||||
MAX_ENTRY_SIZE = 512 * 1024 * 1024 # 512MB
|
||||
|
||||
_HEX_RE = re.compile(r"\b([0-9a-fA-F]{8,40})\b")
|
||||
|
||||
|
||||
def _detect_hash_type(h: str) -> str:
|
||||
n = len(h)
|
||||
if n == 40:
|
||||
return "sha1"
|
||||
if n == 32:
|
||||
return "md5"
|
||||
if n == 8:
|
||||
return "crc32"
|
||||
return "md5"
|
||||
|
||||
|
||||
def parse_hash_input(raw: str) -> list[tuple[str, str]]:
|
||||
"""Parse comma-separated hash string into (type, hash) tuples."""
|
||||
results: list[tuple[str, str]] = []
|
||||
for part in raw.split(","):
|
||||
part = part.strip().lower()
|
||||
if not part:
|
||||
continue
|
||||
m = _HEX_RE.search(part)
|
||||
if m:
|
||||
h = m.group(1)
|
||||
results.append((_detect_hash_type(h), h))
|
||||
return results
|
||||
|
||||
|
||||
def parse_hash_file(path: str) -> list[tuple[str, str]]:
|
||||
"""Parse hash file (one per line, comments with #, mixed formats)."""
|
||||
results: list[tuple[str, str]] = []
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
m = _HEX_RE.search(line.lower())
|
||||
if m:
|
||||
h = m.group(1)
|
||||
results.append((_detect_hash_type(h), h))
|
||||
return results
|
||||
|
||||
|
||||
def lookup_hashes(
|
||||
hashes: list[tuple[str, str]],
|
||||
db: dict,
|
||||
bios_dir: str,
|
||||
emulators_dir: str,
|
||||
platforms_dir: str,
|
||||
) -> None:
|
||||
"""Print diagnostic info for each hash."""
|
||||
files_db = db.get("files", {})
|
||||
by_md5 = db.get("indexes", {}).get("by_md5", {})
|
||||
by_crc32 = db.get("indexes", {}).get("by_crc32", {})
|
||||
|
||||
for hash_type, hash_val in hashes:
|
||||
sha1 = None
|
||||
if hash_type == "sha1" and hash_val in files_db:
|
||||
sha1 = hash_val
|
||||
elif hash_type == "md5":
|
||||
sha1 = by_md5.get(hash_val)
|
||||
elif hash_type == "crc32":
|
||||
sha1 = by_crc32.get(hash_val)
|
||||
|
||||
if not sha1 or sha1 not in files_db:
|
||||
print(f"\n{hash_type.upper()}: {hash_val}")
|
||||
print(" NOT FOUND in database")
|
||||
continue
|
||||
|
||||
entry = files_db[sha1]
|
||||
name = entry.get("name", "?")
|
||||
md5 = entry.get("md5", "?")
|
||||
paths = entry.get("paths") or []
|
||||
aliases = entry.get("aliases") or []
|
||||
|
||||
print(f"\n{hash_type.upper()}: {hash_val}")
|
||||
print(f" SHA1: {sha1}")
|
||||
print(f" MD5: {md5}")
|
||||
print(f" Name: {name}")
|
||||
if paths:
|
||||
print(f" Path: {paths[0]}")
|
||||
if aliases:
|
||||
print(f" Aliases: {aliases}")
|
||||
|
||||
# Check if file exists in repo (by path or by resolve_local_file)
|
||||
in_repo = False
|
||||
if paths:
|
||||
primary = os.path.join(bios_dir, paths[0])
|
||||
if os.path.exists(primary):
|
||||
in_repo = True
|
||||
if not in_repo:
|
||||
try:
|
||||
fe_check = {"name": name, "sha1": sha1, "md5": md5}
|
||||
local, status = resolve_file(fe_check, db, bios_dir, {})
|
||||
if local and status != "not_found":
|
||||
in_repo = True
|
||||
except (KeyError, OSError):
|
||||
pass
|
||||
print(f" In repo: {'YES' if in_repo else 'NO'}")
|
||||
|
||||
|
||||
def _find_candidate_satisfying_both(
|
||||
file_entry: dict,
|
||||
@@ -231,6 +334,9 @@ def generate_pack(
|
||||
data_registry: dict | None = None,
|
||||
emu_profiles: dict | None = None,
|
||||
target_cores: set[str] | None = None,
|
||||
required_only: bool = False,
|
||||
system_filter: list[str] | None = None,
|
||||
precomputed_extras: list[dict] | None = None,
|
||||
) -> str | None:
|
||||
"""Generate a ZIP pack for a platform.
|
||||
|
||||
@@ -246,7 +352,22 @@ def generate_pack(
|
||||
|
||||
version = config.get("version", config.get("dat_version", ""))
|
||||
version_tag = f"_{version.replace(' ', '')}" if version else ""
|
||||
zip_name = f"{platform_display.replace(' ', '_')}{version_tag}_BIOS_Pack.zip"
|
||||
req_tag = "_Required" if required_only else ""
|
||||
|
||||
sys_tag = ""
|
||||
if system_filter:
|
||||
display_parts = []
|
||||
for sid in system_filter:
|
||||
s = sid.lower().replace("_", "-")
|
||||
for prefix in MANUFACTURER_PREFIXES:
|
||||
if s.startswith(prefix):
|
||||
s = s[len(prefix):]
|
||||
break
|
||||
parts = s.split("-")
|
||||
display_parts.append("_".join(p.title() for p in parts if p))
|
||||
sys_tag = "_" + "_".join(display_parts)
|
||||
|
||||
zip_name = f"{platform_display.replace(' ', '_')}{version_tag}{req_tag}_BIOS_Pack{sys_tag}.zip"
|
||||
zip_path = os.path.join(output_dir, zip_name)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
@@ -279,9 +400,23 @@ def generate_pack(
|
||||
platform_cores=plat_cores,
|
||||
)
|
||||
|
||||
if system_filter:
|
||||
from common import _norm_system_id
|
||||
norm_filter = {_norm_system_id(s) for s in system_filter}
|
||||
filtered = {sid: sys_data for sid, sys_data in pack_systems.items()
|
||||
if sid in system_filter or _norm_system_id(sid) in norm_filter}
|
||||
if not filtered:
|
||||
available = sorted(pack_systems.keys())[:10]
|
||||
print(f" WARNING: no systems matched filter {system_filter} "
|
||||
f"(available: {', '.join(available)})")
|
||||
return None
|
||||
pack_systems = filtered
|
||||
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
for sys_id, system in sorted(pack_systems.items()):
|
||||
for file_entry in system.get("files", []):
|
||||
if required_only and file_entry.get("required") is False:
|
||||
continue
|
||||
dest = _sanitize_path(file_entry.get("destination", file_entry["name"]))
|
||||
if not dest:
|
||||
# EmuDeck-style entries (system:md5 whitelist, no filename).
|
||||
@@ -423,12 +558,19 @@ def generate_pack(
|
||||
# Core requirements: files platform's cores need but YAML doesn't declare
|
||||
if emu_profiles is None:
|
||||
emu_profiles = load_emulator_profiles(emulators_dir)
|
||||
core_files = _collect_emulator_extras(
|
||||
config, emulators_dir, db,
|
||||
seen_destinations, base_dest, emu_profiles, target_cores=target_cores,
|
||||
)
|
||||
if precomputed_extras is not None:
|
||||
core_files = precomputed_extras
|
||||
elif system_filter:
|
||||
core_files = []
|
||||
else:
|
||||
core_files = _collect_emulator_extras(
|
||||
config, emulators_dir, db,
|
||||
seen_destinations, base_dest, emu_profiles, target_cores=target_cores,
|
||||
)
|
||||
core_count = 0
|
||||
for fe in core_files:
|
||||
if required_only and fe.get("required") is False:
|
||||
continue
|
||||
dest = _sanitize_path(fe.get("destination", fe["name"]))
|
||||
if not dest:
|
||||
continue
|
||||
@@ -591,6 +733,7 @@ def generate_emulator_pack(
|
||||
output_dir: str,
|
||||
standalone: bool = False,
|
||||
zip_contents: dict | None = None,
|
||||
required_only: bool = False,
|
||||
) -> str | None:
|
||||
"""Generate a ZIP pack for specific emulator profiles."""
|
||||
all_profiles = load_emulator_profiles(emulators_dir, skip_aliases=False)
|
||||
@@ -710,6 +853,8 @@ def generate_emulator_pack(
|
||||
|
||||
# Pack individual files (skip archived ones)
|
||||
for fe in files:
|
||||
if required_only and fe.get("required") is False:
|
||||
continue
|
||||
if fe.get("archive"):
|
||||
continue
|
||||
|
||||
@@ -801,6 +946,7 @@ def generate_system_pack(
|
||||
output_dir: str,
|
||||
standalone: bool = False,
|
||||
zip_contents: dict | None = None,
|
||||
required_only: bool = False,
|
||||
) -> str | None:
|
||||
"""Generate a ZIP pack for all emulators supporting given system IDs."""
|
||||
profiles = load_emulator_profiles(emulators_dir)
|
||||
@@ -835,7 +981,7 @@ def generate_system_pack(
|
||||
)
|
||||
result = generate_emulator_pack(
|
||||
matching, emulators_dir, db, bios_dir, output_dir,
|
||||
standalone, zip_contents,
|
||||
standalone, zip_contents, required_only=required_only,
|
||||
)
|
||||
if result:
|
||||
# Rename to system-based name
|
||||
@@ -852,6 +998,253 @@ def list_platforms(platforms_dir: str) -> list[str]:
|
||||
return list_registered_platforms(platforms_dir, include_archived=True)
|
||||
|
||||
|
||||
def _system_display_name(system_id: str) -> str:
|
||||
"""Convert system ID to display name for ZIP naming."""
|
||||
s = system_id.lower().replace("_", "-")
|
||||
for prefix in MANUFACTURER_PREFIXES:
|
||||
if s.startswith(prefix):
|
||||
s = s[len(prefix):]
|
||||
break
|
||||
parts = s.split("-")
|
||||
return "_".join(p.title() for p in parts if p)
|
||||
|
||||
|
||||
def _group_systems_by_manufacturer(
|
||||
systems: dict[str, dict],
|
||||
db: dict,
|
||||
bios_dir: str,
|
||||
) -> dict[str, list[str]]:
|
||||
"""Group system IDs by manufacturer for --split --group-by manufacturer."""
|
||||
from common import derive_manufacturer
|
||||
groups: dict[str, list[str]] = {}
|
||||
for sid, sys_data in systems.items():
|
||||
mfr = derive_manufacturer(sid, sys_data)
|
||||
groups.setdefault(mfr, []).append(sid)
|
||||
return groups
|
||||
|
||||
|
||||
def generate_split_packs(
|
||||
platform_name: str,
|
||||
platforms_dir: str,
|
||||
db: dict,
|
||||
bios_dir: str,
|
||||
output_dir: str,
|
||||
group_by: str = "system",
|
||||
emulators_dir: str = "emulators",
|
||||
zip_contents: dict | None = None,
|
||||
data_registry: dict | None = None,
|
||||
emu_profiles: dict | None = None,
|
||||
target_cores: set[str] | None = None,
|
||||
required_only: bool = False,
|
||||
) -> list[str]:
|
||||
"""Generate split packs (one ZIP per system or manufacturer)."""
|
||||
config = load_platform_config(platform_name, platforms_dir)
|
||||
platform_display = config.get("platform", platform_name)
|
||||
split_dir = os.path.join(output_dir, f"{platform_display.replace(' ', '_')}_Split")
|
||||
os.makedirs(split_dir, exist_ok=True)
|
||||
|
||||
systems = config.get("systems", {})
|
||||
|
||||
if group_by == "manufacturer":
|
||||
groups = _group_systems_by_manufacturer(systems, db, bios_dir)
|
||||
else:
|
||||
groups = {_system_display_name(sid): [sid] for sid in systems}
|
||||
|
||||
# Pre-compute core extras once (expensive: scans 260+ emulator profiles)
|
||||
# then distribute per group based on emulator system overlap
|
||||
if emu_profiles is None:
|
||||
emu_profiles = load_emulator_profiles(emulators_dir)
|
||||
base_dest = config.get("base_destination", "")
|
||||
if emu_profiles:
|
||||
all_extras = _collect_emulator_extras(
|
||||
config, emulators_dir, db, set(), base_dest, emu_profiles,
|
||||
target_cores=target_cores,
|
||||
)
|
||||
else:
|
||||
all_extras = []
|
||||
# Map each extra to matching systems via source_emulator.
|
||||
# Index by both profile key AND display name (source_emulator uses display).
|
||||
from common import _norm_system_id
|
||||
emu_system_map: dict[str, set[str]] = {}
|
||||
for name, p in emu_profiles.items():
|
||||
raw = set(p.get("systems", []))
|
||||
norm = {_norm_system_id(s) for s in raw}
|
||||
combined = raw | norm
|
||||
emu_system_map[name] = combined
|
||||
display = p.get("emulator", "")
|
||||
if display and display != name:
|
||||
emu_system_map[display] = combined
|
||||
|
||||
plat_norm = {_norm_system_id(s): s for s in systems}
|
||||
|
||||
results = []
|
||||
for group_name, group_system_ids in sorted(groups.items()):
|
||||
group_sys_set = set(group_system_ids)
|
||||
group_norm = {_norm_system_id(s) for s in group_system_ids}
|
||||
group_match = group_sys_set | group_norm
|
||||
group_extras = [
|
||||
fe for fe in all_extras
|
||||
if emu_system_map.get(fe.get("source_emulator", ""), set()) & group_match
|
||||
]
|
||||
zip_path = generate_pack(
|
||||
platform_name, platforms_dir, db, bios_dir, split_dir,
|
||||
emulators_dir=emulators_dir, zip_contents=zip_contents,
|
||||
data_registry=data_registry, emu_profiles=emu_profiles,
|
||||
target_cores=target_cores, required_only=required_only,
|
||||
system_filter=group_system_ids, precomputed_extras=group_extras,
|
||||
)
|
||||
if zip_path:
|
||||
version = config.get("version", config.get("dat_version", ""))
|
||||
ver_tag = f"_{version.replace(' ', '')}" if version else ""
|
||||
req_tag = "_Required" if required_only else ""
|
||||
safe_group = group_name.replace(" ", "_")
|
||||
new_name = f"{platform_display.replace(' ', '_')}{ver_tag}{req_tag}_{safe_group}_BIOS_Pack.zip"
|
||||
new_path = os.path.join(split_dir, new_name)
|
||||
if new_path != zip_path:
|
||||
os.rename(zip_path, new_path)
|
||||
zip_path = new_path
|
||||
results.append(zip_path)
|
||||
|
||||
# Warn about extras that couldn't be distributed (emulators without systems: field)
|
||||
all_groups_match = set()
|
||||
for group_system_ids in groups.values():
|
||||
group_norm = {_norm_system_id(s) for s in group_system_ids}
|
||||
all_groups_match |= set(group_system_ids) | group_norm
|
||||
undistributed = [
|
||||
fe for fe in all_extras
|
||||
if not emu_system_map.get(fe.get("source_emulator", ""), set()) & all_groups_match
|
||||
]
|
||||
if undistributed:
|
||||
emus = sorted({fe.get("source_emulator", "?") for fe in undistributed})
|
||||
print(f" NOTE: {len(undistributed)} core extras from {len(emus)} emulators "
|
||||
f"not in split packs (missing systems: field in profiles: "
|
||||
f"{', '.join(emus[:5])}{'...' if len(emus) > 5 else ''})")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def generate_md5_pack(
|
||||
hashes: list[tuple[str, str]],
|
||||
db: dict,
|
||||
bios_dir: str,
|
||||
output_dir: str,
|
||||
zip_contents: dict | None = None,
|
||||
platform_name: str | None = None,
|
||||
platforms_dir: str | None = None,
|
||||
emulator_name: str | None = None,
|
||||
emulators_dir: str | None = None,
|
||||
standalone: bool = False,
|
||||
) -> str | None:
|
||||
"""Build a pack from an explicit list of hashes with layout context."""
|
||||
files_db = db.get("files", {})
|
||||
by_md5 = db.get("indexes", {}).get("by_md5", {})
|
||||
by_crc32 = db.get("indexes", {}).get("by_crc32", {})
|
||||
if zip_contents is None:
|
||||
zip_contents = {}
|
||||
|
||||
plat_file_index: dict[str, dict] = {}
|
||||
base_dest = ""
|
||||
plat_display = "Custom"
|
||||
if platform_name and platforms_dir:
|
||||
config = load_platform_config(platform_name, platforms_dir)
|
||||
base_dest = config.get("base_destination", "")
|
||||
plat_display = config.get("platform", platform_name)
|
||||
for _sys_id, system in config.get("systems", {}).items():
|
||||
for fe in system.get("files", []):
|
||||
plat_file_index[fe.get("name", "").lower()] = fe
|
||||
|
||||
emu_pack_structure = None
|
||||
emu_display = ""
|
||||
if emulator_name and emulators_dir:
|
||||
profiles = load_emulator_profiles(emulators_dir, skip_aliases=False)
|
||||
if emulator_name in profiles:
|
||||
profile = profiles[emulator_name]
|
||||
emu_display = profile.get("emulator", emulator_name)
|
||||
emu_pack_structure = profile.get("pack_structure")
|
||||
for fe in profile.get("files", []):
|
||||
plat_file_index[fe.get("name", "").lower()] = fe
|
||||
for alias in fe.get("aliases", []):
|
||||
plat_file_index[alias.lower()] = fe
|
||||
|
||||
context_name = plat_display if platform_name else (emu_display or "Custom")
|
||||
zip_name = f"{context_name.replace(' ', '_')}_Custom_BIOS_Pack.zip"
|
||||
zip_path = os.path.join(output_dir, zip_name)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
packed: list[tuple[str, str]] = []
|
||||
not_in_repo: list[tuple[str, str]] = []
|
||||
not_in_db: list[str] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
for hash_type, hash_val in hashes:
|
||||
sha1 = None
|
||||
if hash_type == "sha1" and hash_val in files_db:
|
||||
sha1 = hash_val
|
||||
elif hash_type == "md5":
|
||||
sha1 = by_md5.get(hash_val)
|
||||
elif hash_type == "crc32":
|
||||
sha1 = by_crc32.get(hash_val)
|
||||
|
||||
if not sha1 or sha1 not in files_db:
|
||||
not_in_db.append(hash_val)
|
||||
continue
|
||||
|
||||
entry = files_db[sha1]
|
||||
name = entry.get("name", "")
|
||||
aliases = entry.get("aliases") or []
|
||||
paths = entry.get("paths") or []
|
||||
|
||||
dest = name
|
||||
matched_fe = None
|
||||
for lookup_name in [name] + aliases:
|
||||
if lookup_name.lower() in plat_file_index:
|
||||
matched_fe = plat_file_index[lookup_name.lower()]
|
||||
break
|
||||
|
||||
if matched_fe:
|
||||
if emulator_name and emu_pack_structure is not None:
|
||||
dest = _resolve_destination(matched_fe, emu_pack_structure, standalone)
|
||||
else:
|
||||
dest = matched_fe.get("destination", matched_fe.get("name", name))
|
||||
elif paths:
|
||||
dest = paths[0]
|
||||
|
||||
if base_dest and not dest.startswith(base_dest):
|
||||
full_dest = f"{base_dest}/{dest}"
|
||||
else:
|
||||
full_dest = dest
|
||||
|
||||
if full_dest in seen:
|
||||
continue
|
||||
seen.add(full_dest)
|
||||
|
||||
fe_for_resolve = {"name": name, "sha1": sha1, "md5": entry.get("md5", "")}
|
||||
local_path, status = resolve_file(fe_for_resolve, db, bios_dir, zip_contents)
|
||||
|
||||
if status == "not_found" or not local_path:
|
||||
not_in_repo.append((name, hash_val))
|
||||
continue
|
||||
|
||||
zf.write(local_path, full_dest)
|
||||
packed.append((name, hash_val))
|
||||
|
||||
total = len(hashes)
|
||||
print(f"\nPacked {len(packed)}/{total} requested files")
|
||||
for name, h in packed:
|
||||
print(f" PACKED: {name} ({h[:16]}...)")
|
||||
for name, h in not_in_repo:
|
||||
print(f" NOT IN REPO: {name} ({h[:16]}...)")
|
||||
for h in not_in_db:
|
||||
print(f" NOT IN DB: {h}")
|
||||
|
||||
if not packed:
|
||||
if os.path.exists(zip_path):
|
||||
os.unlink(zip_path)
|
||||
return None
|
||||
return zip_path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate platform BIOS ZIP packs")
|
||||
parser.add_argument("--platform", "-p", help="Platform name (e.g., retroarch)")
|
||||
@@ -874,8 +1267,19 @@ def main():
|
||||
parser.add_argument("--refresh-data", action="store_true",
|
||||
help="Force re-download all data directories")
|
||||
parser.add_argument("--list", action="store_true", help="List available platforms")
|
||||
parser.add_argument("--required-only", action="store_true",
|
||||
help="Only include required files, skip optional")
|
||||
parser.add_argument("--split", action="store_true",
|
||||
help="Generate one ZIP per system/manufacturer")
|
||||
parser.add_argument("--group-by", choices=["system", "manufacturer"],
|
||||
default="system",
|
||||
help="Grouping for --split (default: system)")
|
||||
parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)")
|
||||
parser.add_argument("--list-targets", action="store_true", help="List available targets for the platform")
|
||||
parser.add_argument("--from-md5",
|
||||
help="Hash(es) to look up or pack (comma-separated)")
|
||||
parser.add_argument("--from-md5-file",
|
||||
help="File with hashes (one per line)")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list:
|
||||
@@ -887,7 +1291,10 @@ def main():
|
||||
list_emulator_profiles(args.emulators_dir)
|
||||
return
|
||||
if args.list_systems:
|
||||
list_system_ids(args.emulators_dir)
|
||||
if args.platform:
|
||||
list_platform_system_ids(args.platform, args.platforms_dir)
|
||||
else:
|
||||
list_system_ids(args.emulators_dir)
|
||||
return
|
||||
if args.list_targets:
|
||||
if not args.platform:
|
||||
@@ -902,18 +1309,68 @@ def main():
|
||||
print(f" {t['name']:30s} {t['architecture']:10s} {t['core_count']:>4d} cores{aliases}")
|
||||
return
|
||||
|
||||
# Mutual exclusion
|
||||
modes = sum(1 for x in (args.platform, args.all, args.emulator, args.system) if x)
|
||||
if modes == 0:
|
||||
parser.error("Specify --platform, --all, --emulator, or --system")
|
||||
if modes > 1:
|
||||
parser.error("--platform, --all, --emulator, and --system are mutually exclusive")
|
||||
if args.standalone and not (args.emulator or args.system):
|
||||
parser.error("--standalone requires --emulator or --system")
|
||||
if args.target and not (args.platform or args.all):
|
||||
# Mode validation
|
||||
has_platform = bool(args.platform)
|
||||
has_all = args.all
|
||||
has_emulator = bool(args.emulator)
|
||||
has_system = bool(args.system)
|
||||
has_from_md5 = bool(args.from_md5 or getattr(args, 'from_md5_file', None))
|
||||
|
||||
if args.from_md5 and getattr(args, 'from_md5_file', None):
|
||||
parser.error("--from-md5 and --from-md5-file are mutually exclusive")
|
||||
if has_from_md5 and has_all:
|
||||
parser.error("--from-md5 requires --platform or --emulator, not --all")
|
||||
if has_from_md5 and has_system:
|
||||
parser.error("--from-md5 and --system are mutually exclusive")
|
||||
if has_from_md5 and args.split:
|
||||
parser.error("--split and --from-md5 are mutually exclusive")
|
||||
|
||||
# --platform/--all and --system can combine (system filters within platform)
|
||||
# --emulator is exclusive with everything else
|
||||
if has_emulator and (has_platform or has_all or has_system):
|
||||
parser.error("--emulator is mutually exclusive with --platform, --all, and --system")
|
||||
if has_platform and has_all:
|
||||
parser.error("--platform and --all are mutually exclusive")
|
||||
if not (has_platform or has_all or has_emulator or has_system or has_from_md5):
|
||||
parser.error("Specify --platform, --all, --emulator, --system, or --from-md5")
|
||||
if args.standalone and not (has_emulator or (has_system and not has_platform and not has_all)):
|
||||
parser.error("--standalone requires --emulator or --system (without --platform)")
|
||||
if args.split and not (has_platform or has_all):
|
||||
parser.error("--split requires --platform or --all")
|
||||
if args.split and has_emulator:
|
||||
parser.error("--split is incompatible with --emulator")
|
||||
if args.group_by != "system" and not args.split:
|
||||
parser.error("--group-by requires --split")
|
||||
if args.target and not (has_platform or has_all):
|
||||
parser.error("--target requires --platform or --all")
|
||||
if args.target and (args.emulator or args.system):
|
||||
parser.error("--target is incompatible with --emulator and --system")
|
||||
if args.target and has_emulator:
|
||||
parser.error("--target is incompatible with --emulator")
|
||||
|
||||
# Hash lookup / pack mode
|
||||
if has_from_md5:
|
||||
if args.from_md5:
|
||||
hashes = parse_hash_input(args.from_md5)
|
||||
else:
|
||||
hashes = parse_hash_file(args.from_md5_file)
|
||||
if not hashes:
|
||||
print("No valid hashes found in input", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
db = load_database(args.db)
|
||||
if not has_platform and not has_emulator:
|
||||
lookup_hashes(hashes, db, args.bios_dir, args.emulators_dir,
|
||||
args.platforms_dir)
|
||||
return
|
||||
zip_contents = build_zip_contents_index(db)
|
||||
result = generate_md5_pack(
|
||||
hashes=hashes, db=db, bios_dir=args.bios_dir,
|
||||
output_dir=args.output_dir, zip_contents=zip_contents,
|
||||
platform_name=args.platform, platforms_dir=args.platforms_dir,
|
||||
emulator_name=args.emulator, emulators_dir=args.emulators_dir,
|
||||
standalone=getattr(args, "standalone", False),
|
||||
)
|
||||
if not result:
|
||||
sys.exit(1)
|
||||
return
|
||||
|
||||
db = load_database(args.db)
|
||||
zip_contents = build_zip_contents_index(db)
|
||||
@@ -923,23 +1380,27 @@ def main():
|
||||
names = [n.strip() for n in args.emulator.split(",") if n.strip()]
|
||||
result = generate_emulator_pack(
|
||||
names, args.emulators_dir, db, args.bios_dir, args.output_dir,
|
||||
args.standalone, zip_contents,
|
||||
args.standalone, zip_contents, required_only=args.required_only,
|
||||
)
|
||||
if not result:
|
||||
sys.exit(1)
|
||||
return
|
||||
|
||||
# System mode
|
||||
if args.system:
|
||||
# System mode (standalone, without platform context)
|
||||
if has_system and not has_platform and not has_all:
|
||||
system_ids = [s.strip() for s in args.system.split(",") if s.strip()]
|
||||
result = generate_system_pack(
|
||||
system_ids, args.emulators_dir, db, args.bios_dir, args.output_dir,
|
||||
args.standalone, zip_contents,
|
||||
args.standalone, zip_contents, required_only=args.required_only,
|
||||
)
|
||||
if not result:
|
||||
sys.exit(1)
|
||||
return
|
||||
|
||||
system_filter = None
|
||||
if args.system:
|
||||
system_filter = [s.strip() for s in args.system.split(",") if s.strip()]
|
||||
|
||||
# Platform mode (existing)
|
||||
if args.all:
|
||||
platforms = list_registered_platforms(
|
||||
@@ -998,13 +1459,25 @@ def main():
|
||||
|
||||
try:
|
||||
tc = target_cores_cache.get(representative) if args.target else None
|
||||
zip_path = generate_pack(
|
||||
representative, args.platforms_dir, db, args.bios_dir, args.output_dir,
|
||||
include_extras=args.include_extras, emulators_dir=args.emulators_dir,
|
||||
zip_contents=zip_contents, data_registry=data_registry,
|
||||
emu_profiles=emu_profiles, target_cores=tc,
|
||||
)
|
||||
if zip_path and variants:
|
||||
if args.split:
|
||||
zip_paths = generate_split_packs(
|
||||
representative, args.platforms_dir, db, args.bios_dir,
|
||||
args.output_dir, group_by=args.group_by,
|
||||
emulators_dir=args.emulators_dir, zip_contents=zip_contents,
|
||||
data_registry=data_registry, emu_profiles=emu_profiles,
|
||||
target_cores=tc, required_only=args.required_only,
|
||||
)
|
||||
print(f" Split into {len(zip_paths)} packs")
|
||||
else:
|
||||
zip_path = generate_pack(
|
||||
representative, args.platforms_dir, db, args.bios_dir, args.output_dir,
|
||||
include_extras=args.include_extras, emulators_dir=args.emulators_dir,
|
||||
zip_contents=zip_contents, data_registry=data_registry,
|
||||
emu_profiles=emu_profiles, target_cores=tc,
|
||||
required_only=args.required_only,
|
||||
system_filter=system_filter,
|
||||
)
|
||||
if not args.split and zip_path and variants:
|
||||
rep_cfg = load_platform_config(representative, args.platforms_dir)
|
||||
ver = rep_cfg.get("version", rep_cfg.get("dat_version", ""))
|
||||
ver_tag = f"_{ver.replace(' ', '')}" if ver else ""
|
||||
@@ -1020,7 +1493,17 @@ def main():
|
||||
# Post-generation: verify all packs + inject manifests + SHA256SUMS
|
||||
if not args.list_emulators and not args.list_systems:
|
||||
print("\nVerifying packs and generating manifests...")
|
||||
all_ok = verify_and_finalize_packs(args.output_dir, db)
|
||||
# Skip platform conformance for filtered/split/custom packs
|
||||
skip_conf = bool(system_filter or args.split)
|
||||
all_ok = verify_and_finalize_packs(args.output_dir, db,
|
||||
skip_conformance=skip_conf)
|
||||
# Also verify split subdirectories
|
||||
if args.split:
|
||||
for entry in os.listdir(args.output_dir):
|
||||
sub = os.path.join(args.output_dir, entry)
|
||||
if os.path.isdir(sub) and entry.endswith("_Split"):
|
||||
ok = verify_and_finalize_packs(sub, db, skip_conformance=True)
|
||||
all_ok = all_ok and ok
|
||||
if not all_ok:
|
||||
print("WARNING: some packs have verification errors")
|
||||
sys.exit(1)
|
||||
@@ -1267,7 +1750,8 @@ def verify_pack_against_platform(
|
||||
|
||||
|
||||
def verify_and_finalize_packs(output_dir: str, db: dict,
|
||||
platforms_dir: str = "platforms") -> bool:
|
||||
platforms_dir: str = "platforms",
|
||||
skip_conformance: bool = False) -> bool:
|
||||
"""Verify all packs, inject manifests, generate SHA256SUMS.
|
||||
|
||||
Two-stage verification:
|
||||
@@ -1307,6 +1791,9 @@ def verify_and_finalize_packs(output_dir: str, db: dict,
|
||||
inject_manifest(zip_path, manifest)
|
||||
|
||||
# Stage 2: platform conformance (extract + verify)
|
||||
# Skipped for filtered/split/custom packs (intentionally partial)
|
||||
if skip_conformance:
|
||||
continue
|
||||
platforms = pack_to_platform.get(name, [])
|
||||
for pname in platforms:
|
||||
(p_ok, total, matched, p_errors,
|
||||
|
||||
@@ -1604,10 +1604,22 @@ def generate_wiki_tools() -> str:
|
||||
"Build platform-specific BIOS ZIP packs.",
|
||||
"",
|
||||
"```bash",
|
||||
"# Full platform packs",
|
||||
"python scripts/generate_pack.py --all --output-dir dist/",
|
||||
"python scripts/generate_pack.py --platform batocera",
|
||||
"python scripts/generate_pack.py --emulator dolphin",
|
||||
"python scripts/generate_pack.py --system atari-lynx",
|
||||
"",
|
||||
"# Granular options",
|
||||
"python scripts/generate_pack.py --platform retroarch --system sony-playstation",
|
||||
"python scripts/generate_pack.py --platform batocera --required-only",
|
||||
"python scripts/generate_pack.py --platform retroarch --split",
|
||||
"python scripts/generate_pack.py --platform retroarch --split --group-by manufacturer",
|
||||
"",
|
||||
"# Hash-based lookup and custom packs",
|
||||
"python scripts/generate_pack.py --from-md5 d8f1206299c48946e6ec5ef96d014eaa",
|
||||
"python scripts/generate_pack.py --platform batocera --from-md5-file missing.txt",
|
||||
"python scripts/generate_pack.py --platform retroarch --list-systems",
|
||||
"```",
|
||||
"",
|
||||
"Packs include platform baseline files plus files required by the platform's cores.",
|
||||
@@ -1615,6 +1627,15 @@ def generate_wiki_tools() -> str:
|
||||
"the tool searches for a variant that satisfies both.",
|
||||
"If none exists, the platform version is kept and the discrepancy is reported.",
|
||||
"",
|
||||
"**Granular options:**",
|
||||
"",
|
||||
"- `--system` with `--platform`: filter to specific systems within a platform pack",
|
||||
"- `--required-only`: exclude optional files, keep only required",
|
||||
"- `--split`: generate one ZIP per system instead of one big pack",
|
||||
"- `--split --group-by manufacturer`: group split packs by manufacturer (Sony, Nintendo, Sega...)",
|
||||
"- `--from-md5`: look up a hash in the database, or build a custom pack with `--platform`/`--emulator`",
|
||||
"- `--from-md5-file`: same, reading hashes from a file (one per line, comments with #)",
|
||||
"",
|
||||
"### cross_reference.py",
|
||||
"",
|
||||
"Compare emulator profiles against platform configs.",
|
||||
|
||||
@@ -1539,6 +1539,522 @@ class TestE2E(unittest.TestCase):
|
||||
self.assertEqual(gt["total"], result["total_files"])
|
||||
self.assertGreaterEqual(gt["with_validation"], 1)
|
||||
|
||||
def test_130_required_only_excludes_optional(self):
|
||||
"""--required-only excludes files with required: false from pack."""
|
||||
from generate_pack import generate_pack
|
||||
output_dir = os.path.join(self.root, "pack_reqonly")
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
# Create a platform with one required and one optional file
|
||||
config = {
|
||||
"platform": "ReqOnlyTest",
|
||||
"verification_mode": "existence",
|
||||
"base_destination": "system",
|
||||
"systems": {
|
||||
"test-sys": {
|
||||
"files": [
|
||||
{"name": "present_req.bin", "destination": "present_req.bin", "required": True},
|
||||
{"name": "present_opt.bin", "destination": "present_opt.bin", "required": False},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
with open(os.path.join(self.platforms_dir, "test_reqonly.yml"), "w") as fh:
|
||||
yaml.dump(config, fh)
|
||||
zip_path = generate_pack(
|
||||
"test_reqonly", self.platforms_dir, self.db, self.bios_dir, output_dir,
|
||||
required_only=True,
|
||||
)
|
||||
self.assertIsNotNone(zip_path)
|
||||
with zipfile.ZipFile(zip_path) as zf:
|
||||
names = zf.namelist()
|
||||
self.assertTrue(any("present_req.bin" in n for n in names))
|
||||
self.assertFalse(any("present_opt.bin" in n for n in names))
|
||||
# Verify _Required tag in filename
|
||||
self.assertIn("_Required_", os.path.basename(zip_path))
|
||||
|
||||
def test_131_required_only_keeps_default_required(self):
|
||||
"""--required-only keeps files with no required field (default = required)."""
|
||||
from generate_pack import generate_pack
|
||||
output_dir = os.path.join(self.root, "pack_reqdef")
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
# File with no required field
|
||||
config = {
|
||||
"platform": "ReqDefTest",
|
||||
"verification_mode": "existence",
|
||||
"base_destination": "system",
|
||||
"systems": {
|
||||
"test-sys": {
|
||||
"files": [
|
||||
{"name": "present_req.bin", "destination": "present_req.bin"},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
with open(os.path.join(self.platforms_dir, "test_reqdef.yml"), "w") as fh:
|
||||
yaml.dump(config, fh)
|
||||
zip_path = generate_pack(
|
||||
"test_reqdef", self.platforms_dir, self.db, self.bios_dir, output_dir,
|
||||
required_only=True,
|
||||
)
|
||||
self.assertIsNotNone(zip_path)
|
||||
with zipfile.ZipFile(zip_path) as zf:
|
||||
names = zf.namelist()
|
||||
self.assertTrue(any("present_req.bin" in n for n in names))
|
||||
|
||||
|
||||
def test_132_platform_system_filter(self):
|
||||
"""--platform + --system filters systems within a platform pack."""
|
||||
from generate_pack import generate_pack
|
||||
output_dir = os.path.join(self.root, "pack_sysfilter")
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
config = {
|
||||
"platform": "SysFilterTest",
|
||||
"verification_mode": "existence",
|
||||
"base_destination": "system",
|
||||
"systems": {
|
||||
"system-a": {
|
||||
"files": [
|
||||
{"name": "present_req.bin", "destination": "present_req.bin"},
|
||||
],
|
||||
},
|
||||
"system-b": {
|
||||
"files": [
|
||||
{"name": "present_opt.bin", "destination": "present_opt.bin"},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
with open(os.path.join(self.platforms_dir, "test_sysfilter.yml"), "w") as fh:
|
||||
yaml.dump(config, fh)
|
||||
zip_path = generate_pack(
|
||||
"test_sysfilter", self.platforms_dir, self.db, self.bios_dir, output_dir,
|
||||
system_filter=["system-a"],
|
||||
)
|
||||
self.assertIsNotNone(zip_path)
|
||||
with zipfile.ZipFile(zip_path) as zf:
|
||||
names = zf.namelist()
|
||||
self.assertTrue(any("present_req.bin" in n for n in names))
|
||||
self.assertFalse(any("present_opt.bin" in n for n in names))
|
||||
|
||||
def test_133_platform_system_filter_normalized(self):
|
||||
"""_norm_system_id normalization matches with manufacturer prefix."""
|
||||
from common import _norm_system_id
|
||||
self.assertEqual(
|
||||
_norm_system_id("sony-playstation"),
|
||||
_norm_system_id("playstation"),
|
||||
)
|
||||
|
||||
def test_134_list_systems_platform_context(self):
|
||||
"""list_platform_system_ids lists systems from a platform YAML."""
|
||||
from common import list_platform_system_ids
|
||||
import io
|
||||
config = {
|
||||
"platform": "ListSysTest",
|
||||
"verification_mode": "existence",
|
||||
"systems": {
|
||||
"alpha-sys": {
|
||||
"files": [
|
||||
{"name": "a.bin", "destination": "a.bin"},
|
||||
],
|
||||
},
|
||||
"beta-sys": {
|
||||
"files": [
|
||||
{"name": "b1.bin", "destination": "b1.bin"},
|
||||
{"name": "b2.bin", "destination": "b2.bin"},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
with open(os.path.join(self.platforms_dir, "test_listsys.yml"), "w") as fh:
|
||||
yaml.dump(config, fh)
|
||||
captured = io.StringIO()
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = captured
|
||||
try:
|
||||
list_platform_system_ids("test_listsys", self.platforms_dir)
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
output = captured.getvalue()
|
||||
self.assertIn("alpha-sys", output)
|
||||
self.assertIn("beta-sys", output)
|
||||
self.assertIn("1 file", output)
|
||||
self.assertIn("2 files", output)
|
||||
|
||||
|
||||
def test_135_split_by_system(self):
|
||||
"""--split generates one ZIP per system in a subdirectory."""
|
||||
import tempfile
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
plat_dir = os.path.join(tmpdir, "platforms")
|
||||
os.makedirs(plat_dir)
|
||||
bios_dir = os.path.join(tmpdir, "bios", "Test")
|
||||
os.makedirs(os.path.join(bios_dir, "SysA"))
|
||||
os.makedirs(os.path.join(bios_dir, "SysB"))
|
||||
emu_dir = os.path.join(tmpdir, "emulators")
|
||||
os.makedirs(emu_dir)
|
||||
out_dir = os.path.join(tmpdir, "dist")
|
||||
|
||||
file_a = os.path.join(bios_dir, "SysA", "bios_a.bin")
|
||||
file_b = os.path.join(bios_dir, "SysB", "bios_b.bin")
|
||||
with open(file_a, "wb") as f:
|
||||
f.write(b"system_a")
|
||||
with open(file_b, "wb") as f:
|
||||
f.write(b"system_b")
|
||||
|
||||
from common import compute_hashes
|
||||
ha = compute_hashes(file_a)
|
||||
hb = compute_hashes(file_b)
|
||||
|
||||
db = {
|
||||
"files": {
|
||||
ha["sha1"]: {"name": "bios_a.bin", "md5": ha["md5"],
|
||||
"sha1": ha["sha1"], "sha256": ha["sha256"],
|
||||
"path": file_a,
|
||||
"paths": [file_a]},
|
||||
hb["sha1"]: {"name": "bios_b.bin", "md5": hb["md5"],
|
||||
"sha1": hb["sha1"], "sha256": hb["sha256"],
|
||||
"path": file_b,
|
||||
"paths": [file_b]},
|
||||
},
|
||||
"indexes": {
|
||||
"by_md5": {ha["md5"]: ha["sha1"], hb["md5"]: hb["sha1"]},
|
||||
"by_name": {"bios_a.bin": [ha["sha1"]], "bios_b.bin": [hb["sha1"]]},
|
||||
"by_crc32": {}, "by_path_suffix": {},
|
||||
},
|
||||
}
|
||||
|
||||
registry = {"platforms": {"splitplat": {"status": "active"}}}
|
||||
with open(os.path.join(plat_dir, "_registry.yml"), "w") as f:
|
||||
yaml.dump(registry, f)
|
||||
plat_cfg = {
|
||||
"platform": "SplitTest",
|
||||
"verification_mode": "existence",
|
||||
"systems": {
|
||||
"test-system-a": {"files": [{"name": "bios_a.bin", "sha1": ha["sha1"]}]},
|
||||
"test-system-b": {"files": [{"name": "bios_b.bin", "sha1": hb["sha1"]}]},
|
||||
},
|
||||
}
|
||||
with open(os.path.join(plat_dir, "splitplat.yml"), "w") as f:
|
||||
yaml.dump(plat_cfg, f)
|
||||
|
||||
from generate_pack import generate_split_packs
|
||||
from common import build_zip_contents_index, load_emulator_profiles
|
||||
zip_contents = build_zip_contents_index(db)
|
||||
emu_profiles = load_emulator_profiles(emu_dir)
|
||||
|
||||
zip_paths = generate_split_packs(
|
||||
"splitplat", plat_dir, db, os.path.join(tmpdir, "bios"), out_dir,
|
||||
emulators_dir=emu_dir, zip_contents=zip_contents,
|
||||
emu_profiles=emu_profiles, group_by="system",
|
||||
)
|
||||
self.assertEqual(len(zip_paths), 2)
|
||||
|
||||
# Check subdirectory exists
|
||||
split_dir = os.path.join(out_dir, "SplitTest_Split")
|
||||
self.assertTrue(os.path.isdir(split_dir))
|
||||
|
||||
# Verify each ZIP contains only its system's files
|
||||
for zp in zip_paths:
|
||||
with zipfile.ZipFile(zp) as zf:
|
||||
names = zf.namelist()
|
||||
basename = os.path.basename(zp)
|
||||
if "System_A" in basename:
|
||||
self.assertIn("bios_a.bin", names)
|
||||
self.assertNotIn("bios_b.bin", names)
|
||||
elif "System_B" in basename:
|
||||
self.assertIn("bios_b.bin", names)
|
||||
self.assertNotIn("bios_a.bin", names)
|
||||
|
||||
|
||||
def test_136_derive_manufacturer(self):
|
||||
"""derive_manufacturer extracts manufacturer correctly."""
|
||||
from common import derive_manufacturer
|
||||
# From system ID prefix
|
||||
self.assertEqual(derive_manufacturer("sony-playstation", {}), "Sony")
|
||||
self.assertEqual(derive_manufacturer("nintendo-snes", {}), "Nintendo")
|
||||
self.assertEqual(derive_manufacturer("sega-saturn", {}), "Sega")
|
||||
self.assertEqual(derive_manufacturer("atari-5200", {}), "Atari")
|
||||
# From explicit manufacturer field
|
||||
self.assertEqual(
|
||||
derive_manufacturer("3do", {"manufacturer": "Panasonic|GoldStar"}),
|
||||
"Panasonic",
|
||||
)
|
||||
# Various = skip to prefix check, then Other
|
||||
self.assertEqual(derive_manufacturer("arcade", {"manufacturer": "Various"}), "Other")
|
||||
# Fallback
|
||||
self.assertEqual(derive_manufacturer("dos", {}), "Other")
|
||||
|
||||
def test_137_group_systems_by_manufacturer(self):
|
||||
"""_group_systems_by_manufacturer groups correctly."""
|
||||
from generate_pack import _group_systems_by_manufacturer
|
||||
systems = {
|
||||
"sony-playstation": {"files": [{"name": "a.bin"}]},
|
||||
"sony-psp": {"files": [{"name": "b.bin"}]},
|
||||
"nintendo-snes": {"files": [{"name": "c.bin"}]},
|
||||
"arcade": {"manufacturer": "Various", "files": [{"name": "d.bin"}]},
|
||||
}
|
||||
groups = _group_systems_by_manufacturer(systems, {}, "")
|
||||
self.assertIn("Sony", groups)
|
||||
self.assertEqual(sorted(groups["Sony"]), ["sony-playstation", "sony-psp"])
|
||||
self.assertIn("Nintendo", groups)
|
||||
self.assertEqual(groups["Nintendo"], ["nintendo-snes"])
|
||||
self.assertIn("Other", groups)
|
||||
self.assertEqual(groups["Other"], ["arcade"])
|
||||
|
||||
|
||||
def test_138_parse_hash_input(self):
|
||||
"""parse_hash_input handles various formats."""
|
||||
from generate_pack import parse_hash_input
|
||||
# Plain MD5
|
||||
result = parse_hash_input("d8f1206299c48946e6ec5ef96d014eaa")
|
||||
self.assertEqual(result, [("md5", "d8f1206299c48946e6ec5ef96d014eaa")])
|
||||
# Comma-separated
|
||||
result = parse_hash_input("d8f1206299c48946e6ec5ef96d014eaa,d8f1206299c48946e6ec5ef96d014eab")
|
||||
self.assertEqual(len(result), 2)
|
||||
# SHA1
|
||||
sha1 = "a" * 40
|
||||
result = parse_hash_input(sha1)
|
||||
self.assertEqual(result, [("sha1", sha1)])
|
||||
# CRC32
|
||||
result = parse_hash_input("abcd1234")
|
||||
self.assertEqual(result, [("crc32", "abcd1234")])
|
||||
|
||||
def test_139_parse_hash_file(self):
|
||||
"""parse_hash_file handles comments, empty lines, various formats."""
|
||||
from generate_pack import parse_hash_file
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
||||
f.write("# PS1 BIOS files\n")
|
||||
f.write("\n")
|
||||
f.write("d8f1206299c48946e6ec5ef96d014eaa\n")
|
||||
f.write("d8f1206299c48946e6ec5ef96d014eab scph5501.bin\n")
|
||||
f.write("scph5502.bin d8f1206299c48946e6ec5ef96d014eac OK\n")
|
||||
tmp_path = f.name
|
||||
try:
|
||||
result = parse_hash_file(tmp_path)
|
||||
self.assertEqual(len(result), 3)
|
||||
self.assertTrue(all(t == "md5" for t, _ in result))
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
|
||||
def test_140_lookup_hashes_found(self):
|
||||
"""lookup_hashes returns file info for known hashes."""
|
||||
import io
|
||||
import contextlib
|
||||
from generate_pack import lookup_hashes
|
||||
db = {
|
||||
"files": {
|
||||
"sha1abc": {
|
||||
"name": "test.bin", "md5": "md5abc",
|
||||
"sha1": "sha1abc", "sha256": "sha256abc",
|
||||
"paths": ["Mfr/Console/test.bin"],
|
||||
"aliases": ["alt.bin"],
|
||||
},
|
||||
},
|
||||
"indexes": {
|
||||
"by_md5": {"md5abc": "sha1abc"},
|
||||
"by_crc32": {},
|
||||
},
|
||||
}
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
lookup_hashes([("md5", "md5abc")], db, "bios", "emulators", "platforms")
|
||||
output = buf.getvalue()
|
||||
self.assertIn("test.bin", output)
|
||||
self.assertIn("sha1abc", output)
|
||||
self.assertIn("alt.bin", output)
|
||||
|
||||
def test_141_lookup_hashes_not_found(self):
|
||||
"""lookup_hashes reports unknown hashes."""
|
||||
import io
|
||||
import contextlib
|
||||
from generate_pack import lookup_hashes
|
||||
db = {"files": {}, "indexes": {"by_md5": {}, "by_crc32": {}}}
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
lookup_hashes([("md5", "unknown123" + "0" * 22)], db, "bios", "emulators", "platforms")
|
||||
output = buf.getvalue()
|
||||
self.assertIn("NOT FOUND", output)
|
||||
|
||||
|
||||
def test_142_from_md5_platform_pack(self):
|
||||
"""--from-md5 with --platform generates correctly laid out ZIP."""
|
||||
import tempfile
|
||||
import json
|
||||
import zipfile
|
||||
import yaml
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
plat_dir = os.path.join(tmpdir, "platforms")
|
||||
os.makedirs(plat_dir)
|
||||
bios_dir = os.path.join(tmpdir, "bios", "Sony", "PS1")
|
||||
os.makedirs(bios_dir)
|
||||
emu_dir = os.path.join(tmpdir, "emulators")
|
||||
os.makedirs(emu_dir)
|
||||
out_dir = os.path.join(tmpdir, "dist")
|
||||
|
||||
bios_file = os.path.join(bios_dir, "scph5501.bin")
|
||||
with open(bios_file, "wb") as f:
|
||||
f.write(b"ps1_bios_content")
|
||||
from common import compute_hashes
|
||||
h = compute_hashes(bios_file)
|
||||
|
||||
db = {
|
||||
"files": {
|
||||
h["sha1"]: {
|
||||
"name": "scph5501.bin", "md5": h["md5"],
|
||||
"sha1": h["sha1"], "sha256": h["sha256"],
|
||||
"path": bios_file,
|
||||
"paths": ["Sony/PS1/scph5501.bin"],
|
||||
},
|
||||
},
|
||||
"indexes": {
|
||||
"by_md5": {h["md5"]: h["sha1"]},
|
||||
"by_name": {"scph5501.bin": [h["sha1"]]},
|
||||
"by_crc32": {}, "by_path_suffix": {},
|
||||
},
|
||||
}
|
||||
|
||||
registry = {"platforms": {"testplat": {"status": "active"}}}
|
||||
with open(os.path.join(plat_dir, "_registry.yml"), "w") as f:
|
||||
yaml.dump(registry, f)
|
||||
plat_cfg = {
|
||||
"platform": "TestPlat",
|
||||
"verification_mode": "md5",
|
||||
"base_destination": "bios",
|
||||
"systems": {
|
||||
"sony-playstation": {
|
||||
"files": [
|
||||
{"name": "scph5501.bin", "md5": h["md5"],
|
||||
"destination": "scph5501.bin"},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
with open(os.path.join(plat_dir, "testplat.yml"), "w") as f:
|
||||
yaml.dump(plat_cfg, f)
|
||||
|
||||
from generate_pack import generate_md5_pack
|
||||
from common import build_zip_contents_index
|
||||
zip_contents = build_zip_contents_index(db)
|
||||
|
||||
zip_path = generate_md5_pack(
|
||||
hashes=[("md5", h["md5"])],
|
||||
db=db, bios_dir=bios_dir, output_dir=out_dir,
|
||||
zip_contents=zip_contents,
|
||||
platform_name="testplat", platforms_dir=plat_dir,
|
||||
)
|
||||
self.assertIsNotNone(zip_path)
|
||||
with zipfile.ZipFile(zip_path) as zf:
|
||||
names = zf.namelist()
|
||||
self.assertIn("bios/scph5501.bin", names)
|
||||
self.assertIn("Custom", os.path.basename(zip_path))
|
||||
|
||||
def test_143_from_md5_not_in_repo(self):
|
||||
"""--from-md5 reports files in DB but missing from repo."""
|
||||
import tempfile
|
||||
import io
|
||||
import contextlib
|
||||
from generate_pack import generate_md5_pack
|
||||
|
||||
db = {
|
||||
"files": {
|
||||
"sha1known": {
|
||||
"name": "missing.bin", "md5": "md5known" + "0" * 25,
|
||||
"sha1": "sha1known", "sha256": "sha256known",
|
||||
"path": "/nonexistent/missing.bin",
|
||||
"paths": ["Test/missing.bin"],
|
||||
},
|
||||
},
|
||||
"indexes": {
|
||||
"by_md5": {"md5known" + "0" * 25: "sha1known"},
|
||||
"by_crc32": {},
|
||||
},
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
out_dir = os.path.join(tmpdir, "dist")
|
||||
bios_dir = os.path.join(tmpdir, "bios")
|
||||
os.makedirs(bios_dir)
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
result = generate_md5_pack(
|
||||
hashes=[("md5", "md5known" + "0" * 25)],
|
||||
db=db, bios_dir=bios_dir, output_dir=out_dir,
|
||||
zip_contents={},
|
||||
)
|
||||
output = buf.getvalue()
|
||||
self.assertIn("NOT IN REPO", output)
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
def test_144_invalid_split_emulator(self):
|
||||
"""--split + --emulator is rejected."""
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
["python", "scripts/generate_pack.py", "--emulator", "test", "--split"],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("error", result.stderr.lower())
|
||||
|
||||
def test_145_invalid_from_md5_all(self):
|
||||
"""--from-md5 + --all is rejected."""
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
["python", "scripts/generate_pack.py", "--all", "--from-md5", "abc123" + "0" * 26],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
|
||||
def test_146_invalid_from_md5_system(self):
|
||||
"""--from-md5 + --system is rejected."""
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
["python", "scripts/generate_pack.py", "--system", "psx", "--from-md5", "abc123" + "0" * 26],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
|
||||
def test_147_invalid_group_by_without_split(self):
|
||||
"""--group-by without --split is rejected."""
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
["python", "scripts/generate_pack.py", "--platform", "retroarch", "--group-by", "manufacturer"],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
|
||||
def test_148_valid_platform_system(self):
|
||||
"""--platform + --system is accepted (not rejected at validation stage)."""
|
||||
import argparse
|
||||
sys.path.insert(0, "scripts")
|
||||
# Build the same parser as generate_pack.main()
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--platform", "-p")
|
||||
parser.add_argument("--all", action="store_true")
|
||||
parser.add_argument("--emulator", "-e")
|
||||
parser.add_argument("--system", "-s")
|
||||
parser.add_argument("--standalone", action="store_true")
|
||||
parser.add_argument("--split", action="store_true")
|
||||
parser.add_argument("--group-by", choices=["system", "manufacturer"], default="system")
|
||||
parser.add_argument("--target", "-t")
|
||||
parser.add_argument("--from-md5")
|
||||
parser.add_argument("--from-md5-file")
|
||||
parser.add_argument("--required-only", action="store_true")
|
||||
args = parser.parse_args(["--platform", "retroarch", "--system", "psx"])
|
||||
|
||||
# Replicate validation logic from main()
|
||||
has_platform = bool(args.platform)
|
||||
has_all = args.all
|
||||
has_emulator = bool(args.emulator)
|
||||
has_system = bool(args.system)
|
||||
has_from_md5 = bool(args.from_md5 or args.from_md5_file)
|
||||
|
||||
# These should NOT raise
|
||||
self.assertFalse(has_emulator and (has_platform or has_all or has_system))
|
||||
self.assertFalse(has_platform and has_all)
|
||||
self.assertTrue(has_platform or has_all or has_emulator or has_system or has_from_md5)
|
||||
# --platform + --system is a valid combination
|
||||
self.assertTrue(has_platform and has_system)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -51,10 +51,22 @@ Verification modes per platform:
|
||||
Build platform-specific BIOS ZIP packs.
|
||||
|
||||
```bash
|
||||
# Full platform packs
|
||||
python scripts/generate_pack.py --all --output-dir dist/
|
||||
python scripts/generate_pack.py --platform batocera
|
||||
python scripts/generate_pack.py --emulator dolphin
|
||||
python scripts/generate_pack.py --system atari-lynx
|
||||
|
||||
# Granular options
|
||||
python scripts/generate_pack.py --platform retroarch --system sony-playstation
|
||||
python scripts/generate_pack.py --platform batocera --required-only
|
||||
python scripts/generate_pack.py --platform retroarch --split
|
||||
python scripts/generate_pack.py --platform retroarch --split --group-by manufacturer
|
||||
|
||||
# Hash-based lookup and custom packs
|
||||
python scripts/generate_pack.py --from-md5 d8f1206299c48946e6ec5ef96d014eaa
|
||||
python scripts/generate_pack.py --platform batocera --from-md5-file missing.txt
|
||||
python scripts/generate_pack.py --platform retroarch --list-systems
|
||||
```
|
||||
|
||||
Packs include platform baseline files plus files required by the platform's cores.
|
||||
@@ -62,6 +74,15 @@ When a file passes platform verification but fails emulator validation,
|
||||
the tool searches for a variant that satisfies both.
|
||||
If none exists, the platform version is kept and the discrepancy is reported.
|
||||
|
||||
**Granular options:**
|
||||
|
||||
- `--system` with `--platform`: filter to specific systems within a platform pack
|
||||
- `--required-only`: exclude optional files, keep only required
|
||||
- `--split`: generate one ZIP per system instead of one big pack
|
||||
- `--split --group-by manufacturer`: group split packs by manufacturer (Sony, Nintendo, Sega...)
|
||||
- `--from-md5`: look up a hash in the database, or build a custom pack with `--platform`/`--emulator`
|
||||
- `--from-md5-file`: same, reading hashes from a file (one per line, comments with #)
|
||||
|
||||
### cross_reference.py
|
||||
|
||||
Compare emulator profiles against platform configs.
|
||||
|
||||
Reference in New Issue
Block a user