mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 13:33:24 -05:00
fix: judge a core file by the cores that read it
This commit is contained in:
1 parent
840f9ceaf6
commit
f5a5d6f4bd
14 files changed
+308
-65
No files matched your search
@@ -4837,10 +4837,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "sysdata/otp.bin",
|
"dest": "sysdata/otp.bin",
|
||||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||||
"size": 256,
|
"size": 256,
|
||||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||||
"cores": [
|
"cores": [
|
||||||
"azahar"
|
"azahar"
|
||||||
]
|
]
|
||||||
@@ -47899,10 +47899,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "GC/dsp_rom.bin",
|
"dest": "GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1840,10 +1840,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "sysdata/otp.bin",
|
"dest": "sysdata/otp.bin",
|
||||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||||
"size": 256,
|
"size": 256,
|
||||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||||
"cores": [
|
"cores": [
|
||||||
"azahar"
|
"azahar"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1036,10 +1036,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "sysdata/otp.bin",
|
"dest": "sysdata/otp.bin",
|
||||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||||
"size": 256,
|
"size": 256,
|
||||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||||
"cores": [
|
"cores": [
|
||||||
"azahar"
|
"azahar"
|
||||||
],
|
],
|
||||||
@@ -1270,10 +1270,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "GC/dsp_rom.bin",
|
"dest": "GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
+6
-6
@@ -6110,10 +6110,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "sysdata/otp.bin",
|
"dest": "sysdata/otp.bin",
|
||||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||||
"size": 256,
|
"size": 256,
|
||||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||||
"cores": [
|
"cores": [
|
||||||
"azahar"
|
"azahar"
|
||||||
]
|
]
|
||||||
@@ -61637,10 +61637,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "GC/dsp_rom.bin",
|
"dest": "GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -9824,10 +9824,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "GC/dsp_rom.bin",
|
"dest": "GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -6349,10 +6349,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "sysdata/otp.bin",
|
"dest": "sysdata/otp.bin",
|
||||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||||
"size": 256,
|
"size": 256,
|
||||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||||
"cores": [
|
"cores": [
|
||||||
"azahar"
|
"azahar"
|
||||||
]
|
]
|
||||||
@@ -61876,10 +61876,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "GC/dsp_rom.bin",
|
"dest": "GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4813,10 +4813,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "sysdata/otp.bin",
|
"dest": "sysdata/otp.bin",
|
||||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||||
"size": 256,
|
"size": 256,
|
||||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||||
"cores": [
|
"cores": [
|
||||||
"azahar"
|
"azahar"
|
||||||
]
|
]
|
||||||
@@ -47905,10 +47905,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "GC/dsp_rom.bin",
|
"dest": "GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -22689,10 +22689,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "bios/sysdata/otp.bin",
|
"dest": "bios/sysdata/otp.bin",
|
||||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||||
"size": 256,
|
"size": 256,
|
||||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||||
"cores": [
|
"cores": [
|
||||||
"azahar"
|
"azahar"
|
||||||
],
|
],
|
||||||
@@ -25535,10 +25535,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "bios/GC/dsp_rom.bin",
|
"dest": "bios/GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -6423,10 +6423,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "sysdata/otp.bin",
|
"dest": "sysdata/otp.bin",
|
||||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||||
"size": 256,
|
"size": 256,
|
||||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||||
"cores": [
|
"cores": [
|
||||||
"azahar"
|
"azahar"
|
||||||
]
|
]
|
||||||
@@ -101392,10 +101392,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "GC/dsp_rom.bin",
|
"dest": "GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -801,10 +801,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "sysdata/otp.bin",
|
"dest": "sysdata/otp.bin",
|
||||||
"sha1": "f0332b7252e9c01b30bf5d5a63ceb22dc47968aa",
|
"sha1": "49489245b400f62c62faf1736b7f0a70714d3c49",
|
||||||
"sha256": "93c2e20cc377f2cb0fa9a2ee58c39be22e00baa94b890545e585e53b3eb4039f",
|
"sha256": "e2f1810ceff3d1fd4f1c08ffc1f4d47aa8efca551edd24cd5686205c71a3b13b",
|
||||||
"size": 256,
|
"size": 256,
|
||||||
"repo_path": "bios/Nintendo/3DS/Citra/sysdata/otp.bin",
|
"repo_path": "bios/Nintendo/3DS/otp.bin",
|
||||||
"cores": [
|
"cores": [
|
||||||
"azahar"
|
"azahar"
|
||||||
]
|
]
|
||||||
@@ -2779,10 +2779,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "GC/dsp_rom.bin",
|
"dest": "GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
+3
-3
@@ -6472,10 +6472,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dest": "ngc/GC/dsp_rom.bin",
|
"dest": "ngc/GC/dsp_rom.bin",
|
||||||
"sha1": "f4f683a49d7eb4155566f793f2c1c27e90159992",
|
"sha1": "3c6cc6e04fdd0b2a392d7a6ed769455444846be7",
|
||||||
"sha256": "7f33e6600722791ccee5fd15d4330af8d8f74165f7d1ded6738684c0294aaffe",
|
"sha256": "49d987ee1eab29a157425b82d54516957a81e1bac247c8834e494642605c3e8c",
|
||||||
"size": 8192,
|
"size": 8192,
|
||||||
"repo_path": "bios/Nintendo/GameCube/Sys/GC/dsp_rom.bin",
|
"repo_path": "bios/Nintendo/GameCube/.variants/dsp_rom.bin.3c6cc6e0",
|
||||||
"cores": [
|
"cores": [
|
||||||
"dolphin"
|
"dolphin"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1068,6 +1068,16 @@ def generate_pack(
|
|||||||
)
|
)
|
||||||
if status in ("not_found", "external", "user_provided") or not local_path:
|
if status in ("not_found", "external", "user_provided") or not local_path:
|
||||||
continue
|
continue
|
||||||
|
# The emulator's own check picks the copy, as for a declared file
|
||||||
|
# and in the emulator pack: the name led RetroArch's GC/dsp_rom.bin
|
||||||
|
# to Dolphin's obsolete v0.3.1 free ROM, beside Nintendo's coef
|
||||||
|
# ROM, a pair Dolphin warns about at every start.
|
||||||
|
local_path, disagreement = validated_choice(
|
||||||
|
fe, local_path, db, validation_index, bios_dir, None, dest,
|
||||||
|
validation_owners,
|
||||||
|
)
|
||||||
|
if disagreement:
|
||||||
|
core_discrepancies.append(f"{full_dest} -{disagreement}")
|
||||||
if status == "hash_mismatch":
|
if status == "hash_mismatch":
|
||||||
# The core's declared hash comes from its source, the local dump
|
# The core's declared hash comes from its source, the local dump
|
||||||
# is what the collection holds. Shipping it keeps the emulator
|
# is what the collection holds. Shipping it keeps the emulator
|
||||||
@@ -3100,6 +3110,8 @@ def _manifest_core_entries(
|
|||||||
pack_only_sizes: list[int],
|
pack_only_sizes: list[int],
|
||||||
selection: SelectionIndex,
|
selection: SelectionIndex,
|
||||||
required_only: bool = False,
|
required_only: bool = False,
|
||||||
|
validation_index: dict | None = None,
|
||||||
|
owners: dict | None = None,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Add the files a platform's cores need but its list does not name.
|
"""Add the files a platform's cores need but its list does not name.
|
||||||
|
|
||||||
@@ -3153,6 +3165,10 @@ def _manifest_core_entries(
|
|||||||
[source_emu] if source_emu else [],
|
[source_emu] if source_emu else [],
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
# The copy the emulator accepts, as the pack ships it.
|
||||||
|
local_path, _disagreement = validated_choice(
|
||||||
|
fe, local_path, db, validation_index or {}, bios_dir, None, dest, owners,
|
||||||
|
)
|
||||||
|
|
||||||
source_emu = fe.get("source_profile") or fe.get("source_emulator", "")
|
source_emu = fe.get("source_profile") or fe.get("source_emulator", "")
|
||||||
|
|
||||||
@@ -3479,7 +3495,7 @@ def generate_manifest(
|
|||||||
zip_contents, offline, region_drops, case_insensitive,
|
zip_contents, offline, region_drops, case_insensitive,
|
||||||
seen_destinations, seen_lower, seen_parents, manifest_files,
|
seen_destinations, seen_lower, seen_parents, manifest_files,
|
||||||
omitted_by_destination, record_omission, pack_only_sizes, selection,
|
omitted_by_destination, record_omission, pack_only_sizes, selection,
|
||||||
required_only,
|
required_only, manifest_validation, manifest_owners,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Phase 3: data directories. The installer does not fetch them, so they
|
# Phase 3: data directories. The installer does not fetch them, so they
|
||||||
|
|||||||
+118
-6
@@ -56,6 +56,84 @@ def _parse_validation(validation: list | dict | None) -> list[str]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _entry_rule(emu_name: str, f: dict, checks: set[str]) -> dict:
|
||||||
|
"""One profile entry's own checks, kept apart from the merged view.
|
||||||
|
|
||||||
|
Merged by name, Azahar's crypto check on the 3DS otp.bin judged Cemu's
|
||||||
|
1 KiB Wii U otp.bin, and the variant search handed Cemu the 3DS file.
|
||||||
|
"""
|
||||||
|
def values(field: str) -> set[str]:
|
||||||
|
raw = f.get(field)
|
||||||
|
if not raw or field not in checks:
|
||||||
|
return set()
|
||||||
|
items = raw if isinstance(raw, list) else str(raw).split(",")
|
||||||
|
out = set()
|
||||||
|
for item in items:
|
||||||
|
norm = str(item).strip().lower()
|
||||||
|
if field == "crc32" and norm.startswith("0x"):
|
||||||
|
norm = norm[2:]
|
||||||
|
if norm:
|
||||||
|
out.add(norm)
|
||||||
|
return out
|
||||||
|
|
||||||
|
raw_size = f.get("size") if "size" in checks else None
|
||||||
|
adler = f.get("known_hash_adler32") or f.get("adler32")
|
||||||
|
adler_values = adler if isinstance(adler, list) else [adler] if adler else []
|
||||||
|
return {
|
||||||
|
"emulator": emu_name,
|
||||||
|
"checks": set(checks),
|
||||||
|
"sizes": set(raw_size if isinstance(raw_size, list) else [raw_size])
|
||||||
|
- {None},
|
||||||
|
"min_size": f.get("min_size") if "size" in checks else None,
|
||||||
|
"max_size": f.get("max_size") if "size" in checks else None,
|
||||||
|
"crc32": values("crc32"),
|
||||||
|
"md5": values("md5"),
|
||||||
|
"sha1": values("sha1"),
|
||||||
|
"sha256": values("sha256"),
|
||||||
|
"adler32": {
|
||||||
|
str(v).lower().removeprefix("0x") for v in adler_values if v
|
||||||
|
},
|
||||||
|
"adler32_byteswap": bool(f.get("adler32_byteswap")),
|
||||||
|
"crypto": {c for c in checks if c in _CRYPTO_CHECKS},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _rule_failure(
|
||||||
|
rule: dict, local_path: str, filename: str, bios_dir: str, hashes: dict,
|
||||||
|
) -> str | None:
|
||||||
|
"""Why one entry's checks reject a file, or None when they accept it."""
|
||||||
|
actual_size = os.path.getsize(local_path)
|
||||||
|
if rule["sizes"] and actual_size not in rule["sizes"]:
|
||||||
|
accepted = ",".join(str(s) for s in sorted(rule["sizes"]))
|
||||||
|
return f"size mismatch: got {actual_size}, accepted [{accepted}]"
|
||||||
|
if rule["min_size"] is not None and actual_size < rule["min_size"]:
|
||||||
|
return f"size too small: min {rule['min_size']}, got {actual_size}"
|
||||||
|
if rule["max_size"] is not None and actual_size > rule["max_size"]:
|
||||||
|
return f"size too large: max {rule['max_size']}, got {actual_size}"
|
||||||
|
for hash_type in ("crc32", "md5", "sha1", "sha256"):
|
||||||
|
if rule[hash_type]:
|
||||||
|
if not hashes:
|
||||||
|
hashes.update(compute_hashes(local_path))
|
||||||
|
if hashes[hash_type].lower() not in rule[hash_type]:
|
||||||
|
accepted = ",".join(sorted(rule[hash_type]))
|
||||||
|
return f"{hash_type} mismatch: got {hashes[hash_type]}, accepted [{accepted}]"
|
||||||
|
if rule["adler32"]:
|
||||||
|
if rule["adler32_byteswap"]:
|
||||||
|
actual = _adler32_byteswapped(local_path)
|
||||||
|
else:
|
||||||
|
if not hashes:
|
||||||
|
hashes.update(compute_hashes(local_path))
|
||||||
|
actual = hashes["adler32"].lower()
|
||||||
|
if actual not in rule["adler32"]:
|
||||||
|
accepted = ",".join(sorted(rule["adler32"]))
|
||||||
|
return f"adler32 mismatch: got 0x{actual}, accepted [{accepted}]"
|
||||||
|
if rule["crypto"]:
|
||||||
|
from crypto_verify import check_crypto_validation
|
||||||
|
|
||||||
|
return check_crypto_validation(local_path, filename, bios_dir)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
||||||
"""Build per-filename validation rules from emulator profiles.
|
"""Build per-filename validation rules from emulator profiles.
|
||||||
|
|
||||||
@@ -98,7 +176,9 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
|||||||
"crypto_only": set(),
|
"crypto_only": set(),
|
||||||
"emulators": set(),
|
"emulators": set(),
|
||||||
"per_emulator": {},
|
"per_emulator": {},
|
||||||
|
"rules": [],
|
||||||
}
|
}
|
||||||
|
index[fname]["rules"].append(_entry_rule(emu_name, f, checks))
|
||||||
index[fname]["emulators"].add(emu_name)
|
index[fname]["emulators"].add(emu_name)
|
||||||
index[fname]["checks"].update(checks)
|
index[fname]["checks"].update(checks)
|
||||||
# Track non-reproducible crypto checks
|
# Track non-reproducible crypto checks
|
||||||
@@ -143,8 +223,8 @@ def _build_validation_index(profiles: dict) -> dict[str, dict]:
|
|||||||
# Adler32 -stored as known_hash_adler32 field (not in validation: list
|
# Adler32 -stored as known_hash_adler32 field (not in validation: list
|
||||||
# for Dolphin, but support it in both forms for future profiles)
|
# for Dolphin, but support it in both forms for future profiles)
|
||||||
adler_val = f.get("known_hash_adler32") or f.get("adler32")
|
adler_val = f.get("known_hash_adler32") or f.get("adler32")
|
||||||
if adler_val:
|
for value in adler_val if isinstance(adler_val, list) else [adler_val] if adler_val else []:
|
||||||
norm = adler_val.lower()
|
norm = str(value).lower()
|
||||||
if norm.startswith("0x"):
|
if norm.startswith("0x"):
|
||||||
norm = norm[2:]
|
norm = norm[2:]
|
||||||
index[fname]["adler32"].add(norm)
|
index[fname]["adler32"].add(norm)
|
||||||
@@ -233,12 +313,18 @@ def check_file_validation(
|
|||||||
filename: str,
|
filename: str,
|
||||||
validation_index: dict[str, dict],
|
validation_index: dict[str, dict],
|
||||||
bios_dir: str = "bios",
|
bios_dir: str = "bios",
|
||||||
|
emulators: set[str] | None = None,
|
||||||
) -> tuple[str, list[str]] | None:
|
) -> tuple[str, list[str]] | None:
|
||||||
"""Check emulator-level validation on a resolved file.
|
"""Check emulator-level validation on a resolved file.
|
||||||
|
|
||||||
Supports: size (exact/min/max), crc32, md5, sha1, adler32,
|
Supports: size (exact/min/max), crc32, md5, sha1, adler32,
|
||||||
signature (RSA-2048 PKCS1v15 SHA256), crypto (AES-128-CBC + SHA256).
|
signature (RSA-2048 PKCS1v15 SHA256), crypto (AES-128-CBC + SHA256).
|
||||||
|
|
||||||
|
With ``emulators``, the cores that read the file's destination, each of
|
||||||
|
them must accept it through one of its own entries for the name, the
|
||||||
|
way each runs its own check. Without, the checks of every core naming
|
||||||
|
the file are merged.
|
||||||
|
|
||||||
Returns None if all checks pass or no validation applies.
|
Returns None if all checks pass or no validation applies.
|
||||||
Returns (reason, emulators) tuple on failure, where *emulators*
|
Returns (reason, emulators) tuple on failure, where *emulators*
|
||||||
lists only those cores whose check actually failed.
|
lists only those cores whose check actually failed.
|
||||||
@@ -246,6 +332,23 @@ def check_file_validation(
|
|||||||
entry = validation_index.get(filename)
|
entry = validation_index.get(filename)
|
||||||
if not entry:
|
if not entry:
|
||||||
return None
|
return None
|
||||||
|
if emulators is not None:
|
||||||
|
hashes: dict = {}
|
||||||
|
failures: dict[str, str] = {}
|
||||||
|
for emu in sorted(emulators):
|
||||||
|
rules = [r for r in entry.get("rules", []) if r["emulator"] == emu]
|
||||||
|
if not rules:
|
||||||
|
continue
|
||||||
|
reasons = [
|
||||||
|
_rule_failure(rule, local_path, filename, bios_dir, hashes)
|
||||||
|
for rule in rules
|
||||||
|
]
|
||||||
|
if all(reasons):
|
||||||
|
failures[emu] = reasons[0]
|
||||||
|
if not failures:
|
||||||
|
return None
|
||||||
|
first = sorted(failures)[0]
|
||||||
|
return failures[first], sorted(failures)
|
||||||
checks = entry["checks"]
|
checks = entry["checks"]
|
||||||
pe = entry.get("per_emulator", {})
|
pe = entry.get("per_emulator", {})
|
||||||
|
|
||||||
@@ -366,6 +469,7 @@ def find_validated_variant(
|
|||||||
validation_index: dict,
|
validation_index: dict,
|
||||||
bios_dir: str = "bios",
|
bios_dir: str = "bios",
|
||||||
platform_digest: str | None = None,
|
platform_digest: str | None = None,
|
||||||
|
emulators: set[str] | None = None,
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""A held file the emulator's own checks accept, in place of current_path.
|
"""A held file the emulator's own checks accept, in place of current_path.
|
||||||
|
|
||||||
@@ -424,7 +528,9 @@ def find_validated_variant(
|
|||||||
seen.add(real)
|
seen.add(real)
|
||||||
if accepted and str(entry.get(platform_digest, "")).lower() not in accepted:
|
if accepted and str(entry.get(platform_digest, "")).lower() not in accepted:
|
||||||
continue
|
continue
|
||||||
if check_file_validation(path, fname, validation_index, bios_dir) is None:
|
if check_file_validation(
|
||||||
|
path, fname, validation_index, bios_dir, emulators
|
||||||
|
) is None:
|
||||||
return path
|
return path
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -575,18 +681,24 @@ def validated_choice(
|
|||||||
return local_path, None
|
return local_path, None
|
||||||
name = file_entry.get("name", "")
|
name = file_entry.get("name", "")
|
||||||
rules = validation_index.get(name)
|
rules = validation_index.get(name)
|
||||||
|
judges: set[str] | None = None
|
||||||
if rules and owners and destination:
|
if rules and owners and destination:
|
||||||
owning = _owners_of(destination, name, owners)
|
owning = _owners_of(destination, name, owners)
|
||||||
if owning and not owning & set(rules["emulators"]):
|
if not owning & set(rules["emulators"]):
|
||||||
|
# No core that checks this name reads this destination: PC-98's
|
||||||
|
# bios.rom rules have no say over roms/ibmpcjr/bios.rom.
|
||||||
return local_path, None
|
return local_path, None
|
||||||
|
# The cores that read this destination judge it, each by its own
|
||||||
|
# entries.
|
||||||
|
judges = owning & set(rules["emulators"])
|
||||||
check = check_file_validation(
|
check = check_file_validation(
|
||||||
local_path, file_entry.get("name", ""), validation_index, bios_dir
|
local_path, file_entry.get("name", ""), validation_index, bios_dir, judges
|
||||||
)
|
)
|
||||||
if not check:
|
if not check:
|
||||||
return local_path, None
|
return local_path, None
|
||||||
better = find_validated_variant(
|
better = find_validated_variant(
|
||||||
file_entry, db, local_path, validation_index, bios_dir,
|
file_entry, db, local_path, validation_index, bios_dir,
|
||||||
platform_digest=platform_digest,
|
platform_digest=platform_digest, emulators=judges,
|
||||||
)
|
)
|
||||||
if better:
|
if better:
|
||||||
return better, None
|
return better, None
|
||||||
|
|||||||
@@ -97,8 +97,123 @@ class ValidatedChoice(unittest.TestCase):
|
|||||||
for call in ast.walk(node)
|
for call in ast.walk(node)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
self.assertLessEqual({"generate_pack", "generate_manifest"}, callers)
|
self.assertLessEqual(
|
||||||
|
{"generate_pack", "generate_manifest", "_manifest_core_entries"}, callers
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_core_extras_are_judged_too(self):
|
||||||
|
"""The baseline loop judged a platform's own files; the core-extras loop
|
||||||
|
shipped whatever the name found. Both loops live in generate_pack."""
|
||||||
|
tree = ast.parse((REPO_ROOT / "scripts" / "generate_pack.py").read_text(encoding="utf-8"))
|
||||||
|
builder = next(
|
||||||
|
node for node in ast.walk(tree)
|
||||||
|
if isinstance(node, ast.FunctionDef) and node.name == "generate_pack"
|
||||||
|
)
|
||||||
|
calls = [
|
||||||
|
call for call in ast.walk(builder)
|
||||||
|
if isinstance(call, ast.Call) and getattr(call.func, "id", None) == "validated_choice"
|
||||||
|
]
|
||||||
|
self.assertGreaterEqual(len(calls), 2)
|
||||||
|
|
||||||
|
|
||||||
|
class ManifestsServeWhatTheCoreAccepts(unittest.TestCase):
|
||||||
|
"""RetroArch's GC/dsp_rom.bin was Dolphin's obsolete v0.3.1 free ROM while
|
||||||
|
Nintendo's dump, which Dolphin's check accepts, sat in .variants/."""
|
||||||
|
|
||||||
|
def test_no_served_core_file_fails_its_check_beside_a_passing_variant(self):
|
||||||
|
import json # noqa: PLC0415
|
||||||
|
|
||||||
|
from common import load_emulator_profiles, load_platform_config # noqa: PLC0415
|
||||||
|
from generate_pack import _platform_cores # noqa: PLC0415
|
||||||
|
from validation import check_file_validation # noqa: PLC0415
|
||||||
|
|
||||||
|
manifest_path = REPO_ROOT / "install" / "retroarch.json"
|
||||||
|
if not (REPO_ROOT / "database.json").is_file() or not manifest_path.is_file():
|
||||||
|
self.skipTest("database.json or the manifest is not built")
|
||||||
|
db = json.loads((REPO_ROOT / "database.json").read_text(encoding="utf-8"))
|
||||||
|
profiles = load_emulator_profiles(str(REPO_ROOT / "emulators"))
|
||||||
|
config = load_platform_config("retroarch", str(REPO_ROOT / "platforms"))
|
||||||
|
cores = {n: profiles[n] for n in _platform_cores(config, profiles)}
|
||||||
|
index = _build_validation_index(cores)
|
||||||
|
owners = destination_owners(cores)
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
wrong = []
|
||||||
|
for entry in manifest["files"]:
|
||||||
|
name = entry["dest"].rsplit("/", 1)[-1]
|
||||||
|
path = entry.get("repo_path")
|
||||||
|
if not entry.get("cores") or not path or name not in index:
|
||||||
|
continue
|
||||||
|
local = str(REPO_ROOT / path)
|
||||||
|
if not Path(local).is_file() or not check_file_validation(local, name, index):
|
||||||
|
continue
|
||||||
|
chosen, _ = validated_choice(
|
||||||
|
{"name": name}, local, db, index, str(REPO_ROOT / "bios"), None,
|
||||||
|
entry["dest"], owners,
|
||||||
|
)
|
||||||
|
if chosen != local:
|
||||||
|
wrong.append(entry["dest"])
|
||||||
|
self.assertEqual(wrong, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class EachReaderJudgesWithItsOwnRules(unittest.TestCase):
|
||||||
|
"""Merged by name, Azahar's 256-byte 3DS otp.bin rule judged Cemu's 1 KiB
|
||||||
|
Wii U otp.bin, and the variant search gave Cemu the 3DS file."""
|
||||||
|
|
||||||
|
def test_rules_of_another_emulator_do_not_judge(self):
|
||||||
|
from validation import check_file_validation # noqa: PLC0415
|
||||||
|
|
||||||
|
index = _build_validation_index({
|
||||||
|
"azahar": {"files": [{"name": "otp.bin", "path": "sysdata/otp.bin",
|
||||||
|
"size": 256, "validation": ["size"]}]},
|
||||||
|
"cemu": {"files": [{"name": "otp.bin", "path": "Cemu/otp.bin",
|
||||||
|
"size": 1024, "validation": ["size"]}]},
|
||||||
|
})
|
||||||
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
||||||
|
wiiu = Path(tmp) / "otp.bin"
|
||||||
|
wiiu.write_bytes(b"w" * 1024)
|
||||||
|
self.assertIsNone(check_file_validation(str(wiiu), "otp.bin", index, tmp, {"cemu"}))
|
||||||
|
self.assertIsNotNone(check_file_validation(str(wiiu), "otp.bin", index, tmp, {"azahar"}))
|
||||||
|
|
||||||
|
def test_one_of_an_emulators_entries_is_enough(self):
|
||||||
|
from validation import check_file_validation # noqa: PLC0415
|
||||||
|
|
||||||
|
index = _build_validation_index({"raze": {"files": [
|
||||||
|
{"name": "SW.GRP", "size": 100, "validation": ["size"]},
|
||||||
|
{"name": "SW.GRP", "size": 200, "validation": ["size"]},
|
||||||
|
]}})
|
||||||
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
||||||
|
grp = Path(tmp) / "SW.GRP"
|
||||||
|
grp.write_bytes(b"s" * 200)
|
||||||
|
self.assertIsNone(check_file_validation(str(grp), "SW.GRP", index, tmp, {"raze"}))
|
||||||
|
|
||||||
|
|
||||||
|
class DolphinForksJudgeTheirOwnDspTable(unittest.TestCase):
|
||||||
|
"""VerifyRoms (Source/Core/Core/DSP/DSPCore.cpp) accepts, without a prompt,
|
||||||
|
Nintendo's pair and the newest free pair its table knows: v0.4 in
|
||||||
|
Dolphin, PrimeHack, MMJR2 and Triforce, v0.3.1 in MMJR and Ishiiruka,
|
||||||
|
whose table stops there. Adler-32 over the byte-swapped words."""
|
||||||
|
|
||||||
|
FREE_V04 = REPO_ROOT / "data" / "dolphin-sys" / "GC" / "dsp_rom.bin"
|
||||||
|
FREE_V031 = REPO_ROOT / "bios" / "Nintendo" / "GameCube" / "Sys" / "GC" / "dsp_rom.bin"
|
||||||
|
|
||||||
|
def test_each_fork_accepts_its_own_free_rom(self):
|
||||||
|
from common import load_emulator_profiles # noqa: PLC0415
|
||||||
|
from validation import check_file_validation # noqa: PLC0415
|
||||||
|
|
||||||
|
if not self.FREE_V04.is_file() or not self.FREE_V031.is_file():
|
||||||
|
self.skipTest("free DSP ROMs not present")
|
||||||
|
profiles = load_emulator_profiles(str(REPO_ROOT / "emulators"))
|
||||||
|
expect = {"dolphin": "v04", "primehack": "v04", "dolphin-mmjr2": "v04",
|
||||||
|
"triforce": "v04", "dolphin-mmjr": "v031", "ishiiruka": "v031"}
|
||||||
|
for name, accepted in expect.items():
|
||||||
|
index = _build_validation_index({name: profiles[name]})
|
||||||
|
with self.subTest(emulator=name):
|
||||||
|
for label, path in (("v04", self.FREE_V04), ("v031", self.FREE_V031)):
|
||||||
|
verdict = check_file_validation(
|
||||||
|
str(path), "dsp_rom.bin", index, "bios", {name}
|
||||||
|
)
|
||||||
|
self.assertEqual(verdict is None, label == accepted, label)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
Reference in new issue
Block a user