From 1699caaca25eac635d4a3f5083b4ef27eae81082 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 16 Oct 2026 19:22:45 +0200 Subject: [PATCH] fix: pick the archive holding a core's members --- install/batocera.json | 74 ++++++++++++++++---------------- install/bizhawk.json | 82 ++++++++++++++++++------------------ install/lakka.json | 48 ++++++++++++--------- install/recalbox.json | 62 +++++++++++++++++---------- install/retroarch.json | 48 ++++++++++++--------- install/retrobat.json | 66 ++++++++++++++--------------- install/retrodeck.json | 38 ++++++++--------- install/retropie.json | 48 ++++++++++++--------- install/rocknix.json | 80 +++++++++++++++++++++++------------ install/romm.json | 14 +++--- scripts/common.py | 45 ++++++++++++++++++++ scripts/generate_pack.py | 18 +++----- scripts/packextras.py | 6 +++ scripts/verify.py | 15 ++----- scripts/ziptools.py | 21 +++++++++ tests/test_archive_choice.py | 66 +++++++++++++++++++++++++++++ 16 files changed, 461 insertions(+), 270 deletions(-) diff --git a/install/batocera.json b/install/batocera.json index 6bd8eeaf..454ce15f 100644 --- a/install/batocera.json +++ b/install/batocera.json @@ -5,7 +5,7 @@ "platform": "batocera", "display_name": "Batocera", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "bios", "detect": [ { @@ -238,9 +238,9 @@ } ], "total_files": 12860, - "total_size": 8104439093, + "total_size": 8105178356, "pack_files": 12862, - "pack_size": 8104439093, + "pack_size": 8105178356, "total_omitted": 0, "omitted_files": [], "core_names": { @@ -89544,8 +89544,18 @@ "repo_path": "bios/Arcade/MAME/pgm.zip", "cores": [ "mame", - "mame2003_plus", - "fbneo" + "mame2003_plus" + ], + "alternatives": [ + { + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", + "cores": [ + "fbneo" + ] + } ] }, { @@ -90830,7 +90840,8 @@ "size": 57375, "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ - "mame" + "mame", + "fbneo" ], "systems": [ "3do", @@ -90858,17 +90869,6 @@ "trs80", "tutor", "vgmplay" - ], - "alternatives": [ - { - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", - "cores": [ - "fbneo" - ] - } ] }, { @@ -135873,10 +135873,10 @@ }, { "dest": "neocdz.zip", - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo" ], @@ -136861,10 +136861,10 @@ }, { "dest": "fbneo/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ], @@ -137150,10 +137150,10 @@ }, { "dest": "fbneo/neocdz.zip", - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo" ], @@ -137163,10 +137163,10 @@ }, { "dest": "fbneo/neogeo.zip", - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo" ] @@ -137193,10 +137193,10 @@ }, { "dest": "fbneo/pgm.zip", - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", "cores": [ "fbneo" ] diff --git a/install/bizhawk.json b/install/bizhawk.json index 91f3eeba..c55dc318 100644 --- a/install/bizhawk.json +++ b/install/bizhawk.json @@ -5,7 +5,7 @@ "platform": "bizhawk", "display_name": "BizHawk", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "Firmware", "detect": [ { @@ -21,9 +21,9 @@ ], "standalone_copies": [], "total_files": 949, - "total_size": 2585301231, + "total_size": 2586040494, "pack_files": 951, - "pack_size": 2585301231, + "pack_size": 2586040494, "total_omitted": 0, "omitted_files": [], "core_names": { @@ -5051,10 +5051,10 @@ ], "alternatives": [ { - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo" ] @@ -5068,8 +5068,18 @@ "size": 2094636, "repo_path": "bios/Arcade/MAME/pgm.zip", "cores": [ - "mame", - "fbneo" + "mame" + ], + "alternatives": [ + { + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", + "cores": [ + "fbneo" + ] + } ] }, { @@ -5779,21 +5789,11 @@ "size": 57375, "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ - "mame" + "mame", + "fbneo" ], "systems": [ "3do" - ], - "alternatives": [ - { - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", - "cores": [ - "fbneo" - ] - } ] }, { @@ -11586,10 +11586,10 @@ }, { "dest": "neocdz.zip", - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo" ] @@ -11925,10 +11925,10 @@ }, { "dest": "fbneo/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ], @@ -12241,20 +12241,20 @@ }, { "dest": "fbneo/neocdz.zip", - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo" ] }, { "dest": "fbneo/neogeo.zip", - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo" ] @@ -12281,10 +12281,10 @@ }, { "dest": "fbneo/pgm.zip", - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", "cores": [ "fbneo" ] diff --git a/install/lakka.json b/install/lakka.json index 0f6cdbe7..66878cda 100644 --- a/install/lakka.json +++ b/install/lakka.json @@ -5,7 +5,7 @@ "platform": "lakka", "display_name": "Lakka", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "system", "detect": [ { @@ -17,9 +17,9 @@ ], "standalone_copies": [], "total_files": 5869, - "total_size": 6160093184, + "total_size": 6160101174, "pack_files": 8512, - "pack_size": 6200625711, + "pack_size": 6200633701, "total_omitted": 0, "omitted_files": [], "core_names": { @@ -1515,10 +1515,10 @@ ], "alternatives": [ { - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo", "fbneo_neogeo" @@ -1560,10 +1560,10 @@ ], "alternatives": [ { - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", "cores": [ "fbneo" ] @@ -1855,10 +1855,10 @@ ], "alternatives": [ { - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo", "fbneo_neogeo" @@ -33517,7 +33517,15 @@ "mame2015", "mame2016", "mamearcade", - "ume2015", + "ume2015" + ] + }, + { + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", + "cores": [ "fbneo" ] } @@ -69445,10 +69453,10 @@ }, { "dest": "fbneo/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ], diff --git a/install/recalbox.json b/install/recalbox.json index bff745b2..a3cf86da 100644 --- a/install/recalbox.json +++ b/install/recalbox.json @@ -5,7 +5,7 @@ "platform": "recalbox", "display_name": "Recalbox", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "bios", "detect": [ { @@ -238,9 +238,9 @@ } ], "total_files": 3378, - "total_size": 4261450320, + "total_size": 4262186697, "pack_files": 3380, - "pack_size": 4261450320, + "pack_size": 4262186697, "total_omitted": 1, "omitted_files": [ { @@ -6960,8 +6960,18 @@ "mame2003_plus", "mame2010", "mame2015", - "mame2016", - "fbneo" + "mame2016" + ], + "alternatives": [ + { + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", + "cores": [ + "fbneo" + ] + } ] }, { @@ -28509,7 +28519,15 @@ "cores": [ "mame2010", "mame2015", - "mame2016", + "mame2016" + ] + }, + { + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", + "cores": [ "fbneo" ] } @@ -40000,10 +40018,10 @@ }, { "dest": "fbneo/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ], @@ -40285,10 +40303,10 @@ }, { "dest": "fbneo/neocdz.zip", - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo" ], @@ -40298,10 +40316,10 @@ }, { "dest": "fbneo/neogeo.zip", - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo" ] @@ -40328,10 +40346,10 @@ }, { "dest": "fbneo/pgm.zip", - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", "cores": [ "fbneo" ] diff --git a/install/retroarch.json b/install/retroarch.json index 5e8ee541..f99b3b11 100644 --- a/install/retroarch.json +++ b/install/retroarch.json @@ -5,7 +5,7 @@ "platform": "retroarch", "display_name": "RetroArch", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "system", "detect": [ { @@ -256,9 +256,9 @@ } ], "total_files": 5869, - "total_size": 6160093184, + "total_size": 6160101174, "pack_files": 8512, - "pack_size": 6200625711, + "pack_size": 6200633701, "total_omitted": 0, "omitted_files": [], "core_names": { @@ -1754,10 +1754,10 @@ ], "alternatives": [ { - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo", "fbneo_neogeo" @@ -1799,10 +1799,10 @@ ], "alternatives": [ { - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", "cores": [ "fbneo" ] @@ -2094,10 +2094,10 @@ ], "alternatives": [ { - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo", "fbneo_neogeo" @@ -33756,7 +33756,15 @@ "mame2015", "mame2016", "mamearcade", - "ume2015", + "ume2015" + ] + }, + { + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", + "cores": [ "fbneo" ] } @@ -69684,10 +69692,10 @@ }, { "dest": "fbneo/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ], diff --git a/install/retrobat.json b/install/retrobat.json index a64c4c2f..574e6042 100644 --- a/install/retrobat.json +++ b/install/retrobat.json @@ -5,7 +5,7 @@ "platform": "retrobat", "display_name": "RetroBat", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "bios", "detect": [ { @@ -242,9 +242,9 @@ } ], "total_files": 12418, - "total_size": 8400903401, + "total_size": 8401639778, "pack_files": 12420, - "pack_size": 8400903401, + "pack_size": 8401639778, "total_omitted": 0, "omitted_files": [], "core_names": { @@ -85281,8 +85281,18 @@ "repo_path": "bios/Arcade/MAME/pgm.zip", "cores": [ "mame", - "mame2003_plus", - "fbneo" + "mame2003_plus" + ], + "alternatives": [ + { + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", + "cores": [ + "fbneo" + ] + } ] }, { @@ -86570,7 +86580,8 @@ "size": 57375, "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ - "mame" + "mame", + "fbneo" ], "systems": [ "3do", @@ -86600,17 +86611,6 @@ "tutor", "vc4000", "vg5k" - ], - "alternatives": [ - { - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", - "cores": [ - "fbneo" - ] - } ] }, { @@ -130540,10 +130540,10 @@ }, { "dest": "fbneo/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ], @@ -130829,10 +130829,10 @@ }, { "dest": "fbneo/neocdz.zip", - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo" ], @@ -130842,10 +130842,10 @@ }, { "dest": "fbneo/neogeo.zip", - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo" ] @@ -130872,10 +130872,10 @@ }, { "dest": "fbneo/pgm.zip", - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", "cores": [ "fbneo" ] diff --git a/install/retrodeck.json b/install/retrodeck.json index 1ec23cdc..892a7023 100644 --- a/install/retrodeck.json +++ b/install/retrodeck.json @@ -5,7 +5,7 @@ "platform": "retrodeck", "display_name": "RetroDECK", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "", "detect": [ { @@ -238,9 +238,9 @@ } ], "total_files": 7808, - "total_size": 8497729539, + "total_size": 8498465916, "pack_files": 7810, - "pack_size": 8497729539, + "pack_size": 8498465916, "total_omitted": 1, "omitted_files": [ { @@ -88515,10 +88515,10 @@ }, { "dest": "bios/fbneo/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ], @@ -88816,10 +88816,10 @@ }, { "dest": "bios/fbneo/neocdz.zip", - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo", "fbneo_neogeo" @@ -88830,10 +88830,10 @@ }, { "dest": "bios/fbneo/neogeo.zip", - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo", "fbneo_neogeo" @@ -88861,10 +88861,10 @@ }, { "dest": "bios/fbneo/pgm.zip", - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", "cores": [ "fbneo" ] diff --git a/install/retropie.json b/install/retropie.json index b719ca9d..e93cc43a 100644 --- a/install/retropie.json +++ b/install/retropie.json @@ -5,7 +5,7 @@ "platform": "retropie", "display_name": "RetroPie", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "BIOS", "detect": [ { @@ -16,9 +16,9 @@ ], "standalone_copies": [], "total_files": 9906, - "total_size": 8494364415, + "total_size": 8494372405, "pack_files": 12549, - "pack_size": 8534896942, + "pack_size": 8534904932, "total_omitted": 0, "omitted_files": [], "core_names": { @@ -1532,10 +1532,10 @@ ], "alternatives": [ { - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo", "fbneo_neogeo" @@ -1577,10 +1577,10 @@ ], "alternatives": [ { - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", "cores": [ "fbneo" ] @@ -1872,10 +1872,10 @@ ], "alternatives": [ { - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo", "fbneo_neogeo" @@ -71344,7 +71344,15 @@ "mame2015", "mame2016", "mamearcade", - "ume2015", + "ume2015" + ] + }, + { + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", + "cores": [ "fbneo" ] } @@ -109359,10 +109367,10 @@ }, { "dest": "fbneo/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ], diff --git a/install/rocknix.json b/install/rocknix.json index 01ac74d8..e7deb727 100644 --- a/install/rocknix.json +++ b/install/rocknix.json @@ -5,7 +5,7 @@ "platform": "rocknix", "display_name": "ROCKNIX", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "bios", "detect": [ { @@ -18,9 +18,9 @@ ], "standalone_copies": [], "total_files": 5799, - "total_size": 6306907354, + "total_size": 6307643731, "pack_files": 5801, - "pack_size": 6306907354, + "pack_size": 6307643731, "total_omitted": 0, "omitted_files": [], "core_names": { @@ -19415,7 +19415,15 @@ "mame2015", "mame2016", "mamearcade", - "ume2015", + "ume2015" + ] + }, + { + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", + "cores": [ "fbneo" ] } @@ -19442,10 +19450,10 @@ ], "alternatives": [ { - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo" ] @@ -23480,8 +23488,18 @@ "mame2015", "mame2016", "mamearcade", - "ume2015", - "fbneo" + "ume2015" + ], + "alternatives": [ + { + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", + "cores": [ + "fbneo" + ] + } ] }, { @@ -24695,7 +24713,15 @@ "mame2015", "mame2016", "mamearcade", - "ume2015", + "ume2015" + ] + }, + { + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", + "cores": [ "fbneo" ] } @@ -60170,10 +60196,10 @@ }, { "dest": "fbneo/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ] @@ -60440,10 +60466,10 @@ }, { "dest": "fbneo/neocdz.zip", - "sha1": "9cab31cfe7eacb6871d36cee105d87b90fd85b64", - "sha256": "d828241905d390c5de4867646296f615c95bca2f5d8f7e81d50e08717373a821", - "size": 1181333, - "repo_path": "bios/Arcade/Arcade/neocdz.zip", + "sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6", + "sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114", + "size": 1184219, + "repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be", "cores": [ "fbneo", "fbneo_neogeo" @@ -60451,10 +60477,10 @@ }, { "dest": "fbneo/neogeo.zip", - "sha1": "deb62b0074b8cae4f162c257662136733cfc76ad", - "sha256": "bf05cc848e403f8ae457bfe26faa8135cf4725cfff956fa030f5c0a00a26adcc", - "size": 1859335, - "repo_path": "bios/Arcade/Arcade/neogeo.zip", + "sha1": "420194cc1171adb1920d2ff05e3b7b9ae2b73942", + "sha256": "7eb6e948f095d669f4bcdd22dbb58f6e3da4f3cca5ffe92ea42ebca8396cadac", + "size": 2438734, + "repo_path": "bios/Arcade/Arcade/.variants/neogeo.zip.9d7bbba3", "cores": [ "fbneo", "fbneo_neogeo" @@ -60482,10 +60508,10 @@ }, { "dest": "fbneo/pgm.zip", - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "sha256": "b8603d9021cf9508152fe8ce23c411bb631b24b61d24a26fdd41e273ab925fe5", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", + "sha1": "2b61f36b22933f3bec237818ecc959efb016d652", + "sha256": "5582eae58a8f23b7e203f01b56e26e0450e7bab0ceb01bc65b151df6bf532555", + "size": 2240738, + "repo_path": "bios/Arcade/Arcade/pgm.zip", "cores": [ "fbneo" ] diff --git a/install/romm.json b/install/romm.json index ef4cd65f..384c2f80 100644 --- a/install/romm.json +++ b/install/romm.json @@ -5,7 +5,7 @@ "platform": "romm", "display_name": "RomM", "version": "1.0", - "generated": "2026-10-16T12:33:18Z", + "generated": "2026-10-16T16:55:49Z", "base_destination": "bios", "detect": [ { @@ -16,9 +16,9 @@ ], "standalone_copies": [], "total_files": 1094, - "total_size": 1458687902, + "total_size": 1458695892, "pack_files": 1096, - "pack_size": 1458687902, + "pack_size": 1458695892, "total_omitted": 58, "omitted_files": [ { @@ -14710,10 +14710,10 @@ }, { "dest": "colecovision/atarisy1.zip", - "sha1": "4fb1f00681d2540b476add2def2bbf07ca3e3993", - "sha256": "1cf61e6b54589ed9bf2b5f319c2b56de205384d6726b7a0fae79213071f3cfb5", - "size": 49385, - "repo_path": "bios/Arcade/Arcade/atarisy1.zip", + "sha1": "0c9bc4b948bd1816ebe13e2acb97931cbcc6ea15", + "sha256": "ae8c41b3d0107c6f0634730267af569a51e49f5df1bdb2c1e6c591bb6251b7d3", + "size": 57375, + "repo_path": "bios/Arcade/MAME/atarisy1.zip", "cores": [ "fbneo" ], diff --git a/scripts/common.py b/scripts/common.py index 35d3a468..34d1fd7d 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -599,6 +599,48 @@ def _rank_tail_candidates( ) +def archive_members(archive: str, files: list[dict]) -> list[dict]: + """The members a profile reads inside *archive*, with what it requires.""" + return [ + {"crc32": str(f["crc32"]).lower(), "required": bool(f.get("required", True))} + for f in files + if f.get("archive") == archive and f.get("crc32") + ] + + +def archive_entry(archive: str, files: list[dict]) -> dict: + """The entry a profile's archive resolves through. + + Its own declaration when it has one (it carries the container's hashes), + and the members it reads inside, so a copy of the name that lacks a ROM + the core requires is not taken for the archive. + """ + own = next( + (f for f in files if f.get("name") == archive and not f.get("archive")), + {"name": archive}, + ) + members = archive_members(archive, files) + return {**own, "members": members} if members else own + + +def _holding_members(paths: list[str], members: list[dict]) -> list[str]: + """The same-named archives holding the most required members, then the most members. + + FBNeo reads neocd.bin and 000-lo.lo inside neocdz.zip; the primary copy, + a set without 000-lo.lo, won on the name while a variant held both. + """ + def held(path: str) -> tuple[int, int]: + crcs = zip_member_crcs(path) + return ( + sum(1 for m in members if m["required"] and m["crc32"] in crcs), + sum(1 for m in members if m["crc32"] in crcs), + ) + + scores = {path: held(path) for path in paths} + best = max(scores.values(), default=(0, 0)) + return [path for path in paths if scores[path] == best] + + def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str]: """Same-named candidates, the likeliest first. @@ -910,6 +952,8 @@ def resolve_local_file( if candidates: if zipped_file: candidates = [p for p in candidates if ".zip" in os.path.basename(p)] + if file_entry.get("members"): + candidates = _holding_members(candidates, file_entry["members"]) candidates = _by_affinity(candidates, file_entry, dest_hint) primary = [p for p in candidates if "/.variants/" not in p] if primary or candidates: @@ -2010,6 +2054,7 @@ from ziptools import ( # noqa: E402,F401 safe_extract_zip, _zip_contents_cache, _BOUNDED_RATIO_METHODS, + zip_member_crcs, ) from artifacts import ( # noqa: E402,F401 write_if_changed, diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index d699715f..0291085a 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -31,6 +31,7 @@ sys.path.insert(0, os.path.dirname(__file__)) from common import resolve_platform_cores as _platform_cores from common import ( apply_target_overrides, + archive_entry, profiles_for_systems, write_text_atomic, artifact_lock, @@ -1299,20 +1300,11 @@ def generate_emulator_pack( if _has_path_conflict(archive_dest, seen_destinations, seen_parents): continue - # Prefer the profile's own entry for the archive (carries - # hashes for exact resolution when several dumps share a name) - archive_entry = next( - ( - f - for f in files - if f.get("name") == archive_name and not f.get("archive") - ), - {"name": archive_name}, - ) - if required_only and _archive_is_optional(archive_entry, archive_name, files): + own_entry = archive_entry(archive_name, files) + if required_only and _archive_is_optional(own_entry, archive_name, files): continue local_path, status = resolve_file( - archive_entry, + own_entry, db, bios_dir, zip_contents, @@ -1321,7 +1313,7 @@ def generate_emulator_pack( ) if local_path and status not in ("not_found",): if local_path.endswith(".zip"): - _add_zip_to_pack(local_path, archive_dest, zf, archive_entry) + _add_zip_to_pack(local_path, archive_dest, zf, own_entry) else: _add_pack_member(zf, local_path, archive_dest) seen_destinations.add(archive_dest) diff --git a/scripts/packextras.py b/scripts/packextras.py index 41d0e0bc..9cb87158 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -7,6 +7,7 @@ this module builds so the report and the pack withdraw the same files.""" from __future__ import annotations from common import PROFILE_IDENTITY_FIELDS +from common import archive_members from common import declared_root, expand_platform_declared_names, platform_declarations from common import load_emulator_profiles from common import resolution_is_hash_exact @@ -497,6 +498,9 @@ def _archive_prefix_extras( "region": archive_entry.get("region"), "variant_group": archive_entry.get("variant_group"), } + members = archive_members(archive_name, profile.get("files", [])) + if members: + extra["members"] = members extra["claim_order"] = (3, 0, emu_name) if _take(extra, full_dest, seen_dests, taken): extras.append(extra) @@ -774,6 +778,8 @@ def _report_extra(u: dict, name: str, dest: str) -> dict: "region": u.get("region"), "variant_group": u.get("variant_group"), } + if u.get("members"): + extra["members"] = u["members"] # Keep every reproducible identity constraint carried by the source # profile. Some profiles expose CRC32/SHA-256 only through their # validation block; dropping those fields here would let a same-named diff --git a/scripts/verify.py b/scripts/verify.py index 40343b85..d0864c38 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -35,6 +35,8 @@ import slots sys.path.insert(0, os.path.dirname(__file__)) from common import ( + archive_entry, + archive_members, profiles_for_systems, PROFILE_IDENTITY_FIELDS, list_available_targets, @@ -577,6 +579,7 @@ def find_undeclared_files( "expected": {}, "archive_file_count": 0, "archive_required_count": 0, + "members": archive_members(archive, profile.get("files", [])), } entry = archive_entries[archive_key] entry["archive_file_count"] += 1 @@ -1523,18 +1526,8 @@ def verify_emulator( archive = file_entry.get("archive") if archive and archive not in seen_archives: seen_archives.add(archive) - # Prefer the profile's own entry for the archive (carries - # hashes for exact resolution when several dumps share a name) - archive_entry = next( - ( - f - for f in files - if f.get("name") == archive and not f.get("archive") - ), - {"name": archive}, - ) local_path, resolve_status = resolve_local_file( - archive_entry, + archive_entry(archive, files), db, zip_contents, data_dir_registry=data_registry, diff --git a/scripts/ziptools.py b/scripts/ziptools.py index 0deb8c6f..fb848988 100644 --- a/scripts/ziptools.py +++ b/scripts/ziptools.py @@ -5,6 +5,7 @@ capping a real romset: the largest pack is already several gigabytes.""" from __future__ import annotations +import functools import hashlib import os import re @@ -36,6 +37,26 @@ def check_inside_zip(container: str, file_name: str, expected_md5: str) -> str: except (zipfile.BadZipFile, OSError, KeyError): return "error" +@functools.lru_cache(maxsize=4096) +def _member_crcs(path: str, mtime: float) -> frozenset[str]: + try: + with zipfile.ZipFile(path) as archive: + return frozenset( + format(info.CRC, "08x") for info in archive.infolist() if not info.is_dir() + ) + except (zipfile.BadZipFile, OSError): + return frozenset() + + +def zip_member_crcs(path: str) -> frozenset[str]: + """CRC-32 of each member, read from the central directory.""" + try: + mtime = os.path.getmtime(path) + except OSError: + return frozenset() + return _member_crcs(path, mtime) + + _zip_contents_cache: tuple[frozenset[tuple[str, float]], dict] | None = None def build_zip_contents_index(db: dict, max_entry_size: int = 512 * 1024 * 1024) -> dict: diff --git a/tests/test_archive_choice.py b/tests/test_archive_choice.py index 46421a1a..55593009 100644 --- a/tests/test_archive_choice.py +++ b/tests/test_archive_choice.py @@ -9,10 +9,12 @@ from __future__ import annotations import hashlib import os +import shutil import sys import tempfile import unittest import zipfile +import zlib from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] @@ -123,5 +125,69 @@ class IntegrityChecksEveryMember(unittest.TestCase): ["r1.bin not found inside ZIP", "r2.bin not found inside ZIP"]) +class TheArchiveHoldingTheCoreMembers(unittest.TestCase): + """FBNeo reads neocd.bin and 000-lo.lo inside neocdz.zip. The primary + copy has no 000-lo.lo; a variant holds both. Resolved by name, every + pack shipped the primary at fbneo/neocdz.zip and the core could not + start a Neo Geo CD game.""" + + def setUp(self): + directory = tempfile.mkdtemp(dir=REPO_ROOT / "tmp") + self.addCleanup(shutil.rmtree, directory, True) + root = Path(directory) + self.roms = {"neocd.bin": b"bios" * 8, "000-lo.lo": b"zoom" * 8} + files, by_name = {}, {} + for path, members in ( + (root / "bios/neocdz.zip", ["neocd.bin"]), + (root / "bios/.variants/neocdz.zip.ba20a7be", ["neocd.bin", "000-lo.lo"]), + ): + path.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(path, "w") as zf: + for member in members: + zf.writestr(member, self.roms[member]) + sha1 = hashlib.sha1(path.read_bytes()).hexdigest() + files[sha1] = {"path": str(path), "name": "neocdz.zip", "sha1": sha1} + by_name.setdefault("neocdz.zip", []).append(sha1) + self.db = {"files": files, "indexes": {"by_name": by_name, "by_md5": {}, + "by_path_suffix": {}, "by_crc32": {}}} + self.profile_files = [ + {"name": name, "archive": "neocdz.zip", "required": True, + "crc32": format(zlib.crc32(data), "08x")} + for name, data in self.roms.items() + ] + + def test_the_copy_with_every_required_member_wins(self): + from common import archive_entry, resolve_local_file + + path, status = resolve_local_file( + archive_entry("neocdz.zip", self.profile_files), self.db + ) + self.assertTrue(path.endswith(".variants/neocdz.zip.ba20a7be")) + self.assertEqual(status, "name_exact") + + def test_without_members_the_primary_stays(self): + from common import resolve_local_file + + path, _status = resolve_local_file({"name": "neocdz.zip"}, self.db) + self.assertTrue(path.endswith("bios/neocdz.zip")) + + def test_the_extras_carry_the_members(self): + from packextras import _report_extra + + members = [{"crc32": "5a86cff2", "required": True}] + extra = _report_extra( + {"name": "neocdz.zip", "archive": "neocdz.zip", "members": members}, + "neocdz.zip", "fbneo/neocdz.zip", + ) + self.assertEqual(extra["members"], members) + + def test_verify_and_the_pack_read_one_entry(self): + for module in ("verify.py", "generate_pack.py"): + source = (REPO_ROOT / "scripts" / module).read_text() + with self.subTest(module=module): + self.assertIn("archive_entry(", source) + self.assertNotIn('{"name": archive', source) + + if __name__ == "__main__": unittest.main()