mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-20 10:23:16 -05:00
simple64 picks the PAL PIF for the European and Australian country codes, and New Zealand carts carry one of them. Citra MMJ's AUS font archives serve the region sold in Australia and New Zealand alike. Tagged australia, a leaf New Zealand is not under, --region new-zealand dropped them. They are tagged oceania, as every other PAL file is.
1118 lines
45 KiB
Python
1118 lines
45 KiB
Python
"""Region vocabulary, hierarchy, and rank resolution."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import io
|
|
import os
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
|
|
|
|
import common # noqa: E402
|
|
import region # noqa: E402
|
|
from packextras import emulator_region_drops # noqa: E402
|
|
|
|
|
|
class TestVocabulary(unittest.TestCase):
|
|
def test_world_is_a_region(self):
|
|
self.assertIn(region.WORLD, region.REGIONS)
|
|
|
|
def test_super_regions_and_members_are_all_regions(self):
|
|
for parent, members in region.REGION_TREE.items():
|
|
self.assertIn(parent, region.REGIONS)
|
|
for member in members:
|
|
self.assertIn(member, region.REGIONS)
|
|
|
|
def test_hierarchy_is_two_levels(self):
|
|
for members in region.REGION_TREE.values():
|
|
for member in members:
|
|
self.assertNotIn(member, region.REGION_TREE)
|
|
|
|
def test_no_member_has_two_parents(self):
|
|
seen: set[str] = set()
|
|
for members in region.REGION_TREE.values():
|
|
for member in members:
|
|
self.assertNotIn(member, seen)
|
|
seen.add(member)
|
|
|
|
|
|
class TestComparable(unittest.TestCase):
|
|
def test_equal(self):
|
|
self.assertTrue(region.comparable("japan", "japan"))
|
|
|
|
def test_parent_matches_child(self):
|
|
self.assertTrue(region.comparable("europe", "france"))
|
|
|
|
def test_child_matches_parent(self):
|
|
self.assertTrue(region.comparable("france", "europe"))
|
|
|
|
def test_siblings_do_not_match(self):
|
|
self.assertFalse(region.comparable("france", "germany"))
|
|
|
|
def test_disjoint_super_regions_do_not_match(self):
|
|
self.assertFalse(region.comparable("europe", "asia"))
|
|
|
|
def test_saturn_fused_slug_is_under_asia(self):
|
|
self.assertTrue(region.comparable("asia", "asia-ntsc"))
|
|
|
|
|
|
class TestNormalizeDeclared(unittest.TestCase):
|
|
def test_none_is_empty(self):
|
|
self.assertEqual(region.normalize_declared(None), set())
|
|
|
|
def test_list_passes_through(self):
|
|
self.assertEqual(region.normalize_declared(["japan"]), {"japan"})
|
|
|
|
def test_legacy_signal_string_maps_to_geography(self):
|
|
self.assertEqual(region.normalize_declared("NTSC-J"), {"japan"})
|
|
self.assertEqual(region.normalize_declared("NTSC-U"), {"north-america"})
|
|
self.assertEqual(region.normalize_declared("PAL"), {"europe"})
|
|
|
|
def test_legacy_auto_and_world_map_to_world(self):
|
|
self.assertEqual(region.normalize_declared("Auto"), {region.WORLD})
|
|
self.assertEqual(region.normalize_declared("World"), {region.WORLD})
|
|
|
|
def test_unknown_value_raises(self):
|
|
with self.assertRaises(ValueError):
|
|
region.normalize_declared("atlantis")
|
|
|
|
|
|
class TestParseRequested(unittest.TestCase):
|
|
def test_single_alias(self):
|
|
self.assertEqual(region.parse_requested("us"), ["north-america"])
|
|
|
|
def test_order_is_preserved(self):
|
|
self.assertEqual(
|
|
region.parse_requested("us,eu,jp"),
|
|
["north-america", "europe", "japan"],
|
|
)
|
|
|
|
def test_duplicates_collapse_keeping_first_position(self):
|
|
self.assertEqual(
|
|
region.parse_requested("us,usa,eu"), ["north-america", "europe"]
|
|
)
|
|
|
|
def test_whitespace_and_case_tolerated(self):
|
|
self.assertEqual(
|
|
region.parse_requested(" US , Eu "), ["north-america", "europe"]
|
|
)
|
|
|
|
def test_unknown_region_raises(self):
|
|
with self.assertRaises(ValueError):
|
|
region.parse_requested("us,atlantis")
|
|
|
|
def test_empty_raises(self):
|
|
with self.assertRaises(ValueError):
|
|
region.parse_requested(" ")
|
|
|
|
|
|
class TestRank(unittest.TestCase):
|
|
def test_exact_match_is_zero(self):
|
|
self.assertEqual(region.rank({"north-america"}, ["north-america"]), 0)
|
|
|
|
def test_second_choice_is_one(self):
|
|
self.assertEqual(region.rank({"europe"}, ["north-america", "europe"]), 1)
|
|
|
|
def test_no_match_is_lowest(self):
|
|
self.assertEqual(region.rank({"japan"}, ["north-america", "europe"]), 2)
|
|
|
|
def test_child_matches_requested_parent(self):
|
|
self.assertEqual(region.rank({"france"}, ["europe"]), 0)
|
|
|
|
def test_parent_matches_requested_child(self):
|
|
self.assertEqual(region.rank({"europe"}, ["france"]), 0)
|
|
|
|
def test_best_of_several_declared_regions_wins(self):
|
|
self.assertEqual(
|
|
region.rank({"japan", "north-america"}, ["north-america", "japan"]), 0
|
|
)
|
|
|
|
def test_empty_declared_is_lowest(self):
|
|
self.assertEqual(region.rank(set(), ["north-america"]), 1)
|
|
|
|
|
|
class TestRegionTag(unittest.TestCase):
|
|
def test_single(self):
|
|
self.assertEqual(region.region_tag(["north-america"]), "NorthAmerica")
|
|
|
|
def test_multiple_joined(self):
|
|
self.assertEqual(
|
|
region.region_tag(["north-america", "japan"]), "NorthAmerica_Japan"
|
|
)
|
|
|
|
|
|
class TestRegionIndex(unittest.TestCase):
|
|
def setUp(self):
|
|
self.profiles = {
|
|
"dolphin": {
|
|
"files": [
|
|
{
|
|
"name": "IPL.bin",
|
|
"path": "GC/USA/IPL.bin",
|
|
"region": ["north-america"],
|
|
},
|
|
{
|
|
"name": "IPL.bin",
|
|
"path": "GC/EUR/IPL.bin",
|
|
"region": ["europe"],
|
|
},
|
|
{
|
|
"name": "IPL.bin",
|
|
"path": "GC/JAP/IPL.bin",
|
|
"region": ["japan"],
|
|
},
|
|
{"name": "dsp_rom.bin"},
|
|
]
|
|
},
|
|
"beetle_psx": {
|
|
"files": [
|
|
{"name": "scph5501.bin", "region": "NTSC-U"},
|
|
{"name": "psxonpsp660.bin", "region": "Auto"},
|
|
]
|
|
},
|
|
"swanstation": {
|
|
"files": [{"name": "scph5501.bin", "region": "NTSC-U"}]
|
|
},
|
|
"launcher_profile": {
|
|
"type": "launcher",
|
|
"files": [{"name": "ignored.bin", "region": ["japan"]}],
|
|
},
|
|
}
|
|
self.index = region.build_region_index(self.profiles)
|
|
|
|
def test_path_keyed_entries_stay_separate(self):
|
|
self.assertEqual(
|
|
region.lookup_regions(self.index, "GC/USA/IPL.bin", "IPL.bin"),
|
|
{"north-america"},
|
|
)
|
|
self.assertEqual(
|
|
region.lookup_regions(self.index, "GC/JAP/IPL.bin", "IPL.bin"),
|
|
{"japan"},
|
|
)
|
|
|
|
def test_bare_name_lookup_unions_ambiguous_entries(self):
|
|
self.assertEqual(
|
|
region.lookup_regions(self.index, "", "IPL.bin"),
|
|
{"north-america", "europe", "japan"},
|
|
)
|
|
|
|
def test_destination_suffix_bridges_platform_entry(self):
|
|
self.assertEqual(
|
|
region.lookup_regions(
|
|
self.index, "dolphin-emu/GC/EUR/IPL.bin", "IPL.bin"
|
|
),
|
|
{"europe"},
|
|
)
|
|
|
|
def test_untagged_file_has_no_regions(self):
|
|
self.assertEqual(
|
|
region.lookup_regions(self.index, "dsp_rom.bin", "dsp_rom.bin"), set()
|
|
)
|
|
|
|
def test_unknown_file_has_no_regions(self):
|
|
self.assertEqual(
|
|
region.lookup_regions(self.index, "nope.bin", "nope.bin"), set()
|
|
)
|
|
|
|
def test_legacy_string_values_are_normalised(self):
|
|
self.assertEqual(
|
|
region.lookup_regions(self.index, "scph5501.bin", "scph5501.bin"),
|
|
{"north-america"},
|
|
)
|
|
self.assertEqual(
|
|
region.lookup_regions(self.index, "psxonpsp660.bin", "psxonpsp660.bin"),
|
|
{region.WORLD},
|
|
)
|
|
|
|
def test_two_profiles_agreeing_merge(self):
|
|
entry = self.index["scph5501.bin"]
|
|
self.assertEqual(entry["regions"], {"north-america"})
|
|
self.assertEqual(entry["emulators"], ["beetle_psx", "swanstation"])
|
|
|
|
def test_launcher_profiles_are_skipped(self):
|
|
self.assertEqual(
|
|
region.lookup_regions(self.index, "ignored.bin", "ignored.bin"), set()
|
|
)
|
|
|
|
def test_unknown_value_names_the_profile_and_file(self):
|
|
with self.assertRaises(ValueError) as ctx:
|
|
region.build_region_index(
|
|
{"badcore": {"files": [{"name": "x.bin", "region": ["atlantis"]}]}}
|
|
)
|
|
self.assertIn("badcore", str(ctx.exception))
|
|
self.assertIn("x.bin", str(ctx.exception))
|
|
|
|
|
|
class TestStandaloneLayoutIsIndexed(unittest.TestCase):
|
|
"""GC/EUR/IPL.bin, Dolphin's standalone path, was looked up by its bare
|
|
name and got the union of five regions: never reduced in a Batocera
|
|
pack nor in --emulator dolphin --standalone."""
|
|
|
|
def test_the_standalone_path_carries_its_own_regions(self):
|
|
import slot
|
|
|
|
profiles = {"dolphin": {"files": [
|
|
{"name": "IPL.bin", "path": "dolphin-emu/Sys/GC/USA/IPL.bin",
|
|
"standalone_path": "GC/USA/IPL.bin", "region": ["north-america"]},
|
|
{"name": "IPL.bin", "path": "dolphin-emu/Sys/GC/EUR/IPL.bin",
|
|
"standalone_path": "GC/EUR/IPL.bin", "region": ["europe"]},
|
|
]}}
|
|
index = region.build_region_index(profiles)
|
|
self.assertEqual(region.lookup_regions(index, "GC/EUR/IPL.bin", "IPL.bin"), {"europe"})
|
|
self.assertIn("GC/EUR/IPL.bin", slot.build_slot_index(profiles))
|
|
|
|
def test_an_alias_carries_the_region_of_its_file(self):
|
|
"""scph5552.bin, the name beetle_psx also opens for its European
|
|
BIOS, stayed in a --region jp pack once scph5502.bin went."""
|
|
index = region.build_region_index({"beetle_psx": {"files": [
|
|
{"name": "scph5502.bin", "aliases": ["scph5552.bin"],
|
|
"region": ["europe", "oceania"]},
|
|
]}})
|
|
self.assertEqual(
|
|
region.lookup_regions(index, "scph5552.bin", "scph5552.bin"),
|
|
{"europe", "oceania"},
|
|
)
|
|
|
|
|
|
class TestResolveRegionDrops(unittest.TestCase):
|
|
def setUp(self):
|
|
self.index = region.build_region_index(
|
|
{
|
|
"psx": {
|
|
"files": [
|
|
{"name": "scph5501.bin", "region": ["north-america"]},
|
|
{"name": "scph5500.bin", "region": ["japan"]},
|
|
{"name": "scph5502.bin", "region": ["europe"]},
|
|
{"name": "psxonpsp660.bin", "region": ["world"]},
|
|
{"name": "gamepad.cfg"},
|
|
]
|
|
},
|
|
"fds": {"files": [{"name": "disksys.rom", "region": ["japan"]}]},
|
|
"o2em": {"files": [{"name": "c52.bin", "region": ["france"]}]},
|
|
}
|
|
)
|
|
|
|
def _drops(self, groups, requested):
|
|
return region.resolve_region_drops(groups, self.index, requested)
|
|
|
|
def test_a_lone_file_never_vetoes_its_system(self):
|
|
"""Dolphin's GC/EUR/IPL.bin came back as a core extra alone in its
|
|
group, and that group's keep cancelled the drop the GameCube group
|
|
decided: the full pack kept what --source platform dropped."""
|
|
groups = {
|
|
"psx": [("scph5501.bin", "scph5501.bin"), ("scph5502.bin", "scph5502.bin")],
|
|
"core:file:scph5502.bin": [("scph5502.bin", "scph5502.bin")],
|
|
}
|
|
self.assertEqual(self._drops(groups, ["north-america"]), {"scph5502.bin"})
|
|
alone = {"core:file:scph5502.bin": [("scph5502.bin", "scph5502.bin")]}
|
|
self.assertEqual(self._drops(alone, ["north-america"]), set())
|
|
|
|
def test_a_missing_match_never_displaces_a_held_file(self):
|
|
"""Grouped so the report can withdraw it, a missing file must not
|
|
take a held one's place: a Japanese request with only the American
|
|
BIOS held kept nothing for the PlayStation."""
|
|
groups = {"psx": [("scph5501.bin", "scph5501.bin"),
|
|
("scph5500.bin", "scph5500.bin")]}
|
|
held = {"scph5501.bin"}
|
|
drop = region.resolve_region_drops
|
|
self.assertEqual(drop(groups, self.index, ["japan"], held), set())
|
|
self.assertEqual(
|
|
drop(groups, self.index, ["japan", "north-america"], held), set()
|
|
)
|
|
self.assertEqual(
|
|
drop(groups, self.index, ["north-america"], held), {"scph5500.bin"}
|
|
)
|
|
self.assertEqual(drop(groups, self.index, ["japan"]), {"scph5501.bin"})
|
|
|
|
def test_a_missing_world_file_never_displaces_a_held_one(self):
|
|
groups = {"psx": [("scph5500.bin", "scph5500.bin"),
|
|
("psxonpsp660.bin", "psxonpsp660.bin")]}
|
|
self.assertEqual(
|
|
region.resolve_region_drops(
|
|
groups, self.index, ["europe"], {"scph5500.bin"}
|
|
),
|
|
set(),
|
|
)
|
|
|
|
def test_a_world_file_the_code_ranks_lower_keeps_the_regional_ones(self):
|
|
"""DuckStation looks for its PS1 images (5) before the world PS2 ones
|
|
(100): asked for Brazil, it kept only the PS2 fallbacks."""
|
|
index = region.build_region_index({"duckstation": {"files": [
|
|
{"name": "scph5501.bin", "region": ["north-america"], "priority": 5},
|
|
{"name": "ps2_scph30001.bin", "region": ["north-america"], "priority": 150},
|
|
{"name": "ps2_scph10000.bin", "region": ["world"], "priority": 100},
|
|
]}})
|
|
groups = {"psx": [(n, n) for n in ("scph5501.bin", "ps2_scph30001.bin",
|
|
"ps2_scph10000.bin")]}
|
|
self.assertEqual(
|
|
region.resolve_region_drops(groups, index, ["brazil"]), {"ps2_scph30001.bin"}
|
|
)
|
|
|
|
def test_without_ranks_the_world_file_still_wins(self):
|
|
groups = {"psx": [("scph5501.bin", "scph5501.bin"),
|
|
("psxonpsp660.bin", "psxonpsp660.bin")]}
|
|
self.assertEqual(self._drops(groups, ["brazil"]), {"scph5501.bin"})
|
|
|
|
def test_best_rank_wins_and_siblings_drop(self):
|
|
groups = {
|
|
"psx": [
|
|
("scph5501.bin", "scph5501.bin"),
|
|
("scph5500.bin", "scph5500.bin"),
|
|
("scph5502.bin", "scph5502.bin"),
|
|
]
|
|
}
|
|
self.assertEqual(
|
|
self._drops(groups, ["north-america"]),
|
|
{"scph5500.bin", "scph5502.bin"},
|
|
)
|
|
|
|
def test_priority_order_selects_second_choice(self):
|
|
groups = {
|
|
"psx": [
|
|
("scph5500.bin", "scph5500.bin"),
|
|
("scph5502.bin", "scph5502.bin"),
|
|
]
|
|
}
|
|
self.assertEqual(
|
|
self._drops(groups, ["north-america", "europe", "japan"]),
|
|
{"scph5500.bin"},
|
|
)
|
|
|
|
def test_group_with_no_match_is_kept_whole(self):
|
|
groups = {"fds": [("disksys.rom", "disksys.rom")]}
|
|
self.assertEqual(self._drops(groups, ["north-america"]), set())
|
|
|
|
def test_world_never_competes(self):
|
|
groups = {
|
|
"psx": [
|
|
("scph5501.bin", "scph5501.bin"),
|
|
("scph5500.bin", "scph5500.bin"),
|
|
("psxonpsp660.bin", "psxonpsp660.bin"),
|
|
]
|
|
}
|
|
self.assertEqual(self._drops(groups, ["north-america"]), {"scph5500.bin"})
|
|
|
|
def test_untagged_files_never_drop(self):
|
|
groups = {
|
|
"psx": [
|
|
("scph5501.bin", "scph5501.bin"),
|
|
("gamepad.cfg", "gamepad.cfg"),
|
|
("scph5500.bin", "scph5500.bin"),
|
|
]
|
|
}
|
|
self.assertEqual(self._drops(groups, ["north-america"]), {"scph5500.bin"})
|
|
|
|
def test_child_region_satisfies_requested_parent(self):
|
|
groups = {"o2em": [("c52.bin", "c52.bin")]}
|
|
self.assertEqual(self._drops(groups, ["europe"]), set())
|
|
|
|
def test_destination_kept_by_any_group_is_not_dropped(self):
|
|
groups = {
|
|
"psx": [
|
|
("scph5501.bin", "scph5501.bin"),
|
|
("scph5500.bin", "scph5500.bin"),
|
|
],
|
|
"psx-import": [("scph5500.bin", "scph5500.bin")],
|
|
}
|
|
self.assertEqual(self._drops(groups, ["north-america"]), set())
|
|
|
|
def test_no_requested_regions_drops_nothing(self):
|
|
groups = {"psx": [("scph5500.bin", "scph5500.bin")]}
|
|
self.assertEqual(self._drops(groups, []), set())
|
|
|
|
|
|
class TestFallbackGroups(unittest.TestCase):
|
|
def setUp(self):
|
|
self.index = region.build_region_index(
|
|
{
|
|
"psx": {
|
|
"files": [
|
|
{"name": "scph5501.bin", "region": ["north-america"]}
|
|
]
|
|
},
|
|
"fds": {"files": [{"name": "disksys.rom", "region": ["japan"]}]},
|
|
}
|
|
)
|
|
|
|
def test_only_unmatched_groups_are_reported(self):
|
|
groups = {
|
|
"psx": [("scph5501.bin", "scph5501.bin")],
|
|
"fds": [("disksys.rom", "disksys.rom")],
|
|
}
|
|
self.assertEqual(
|
|
region.fallback_groups(groups, self.index, ["north-america"]), ["fds"]
|
|
)
|
|
|
|
def test_an_untagged_bios_answers_every_region(self):
|
|
"""VICE's generic kernal is untagged beside its Japanese one: the C64
|
|
was reported as having no BIOS for North America."""
|
|
groups = {"fds": [("disksys.rom", "disksys.rom"), ("generic.rom", "generic.rom")]}
|
|
self.assertEqual(region.fallback_groups(groups, self.index, ["north-america"]), [])
|
|
|
|
def test_a_file_no_system_claims_is_no_fallback(self):
|
|
"""Dolphin's GC/EUR/IPL.bin, alone in its group, named the GameCube
|
|
in the pack README as lacking a North American BIOS it carries."""
|
|
groups = {"Dolphin:file:GC/EUR/IPL.bin": [("GC/EUR/IPL.bin", "IPL.bin")],
|
|
"fds": [("disksys.rom", "disksys.rom")]}
|
|
self.assertEqual(
|
|
region.fallback_groups(groups, self.index, ["north-america"]), ["fds"]
|
|
)
|
|
|
|
def test_the_readme_names_a_slot_as_a_reader_does(self):
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
|
from packreadme import _group_label
|
|
|
|
self.assertEqual(_group_label("PicoDrive:variant:megacd"), "megacd (PicoDrive)")
|
|
self.assertEqual(_group_label("sega-mega-cd"), "sega-mega-cd")
|
|
|
|
def test_no_requested_regions_reports_nothing(self):
|
|
groups = {"fds": [("disksys.rom", "disksys.rom")]}
|
|
self.assertEqual(region.fallback_groups(groups, self.index, []), [])
|
|
|
|
|
|
class OneCodeOneRegionAnswer(unittest.TestCase):
|
|
"""mednafen, beetle_saturn, saturn-emu and mednafen_stv read the same
|
|
ss.cpp, where area E (Australia included) selects the PAL BIOS: only
|
|
two of them answered oceania. CLK and Kronos are pinned here too."""
|
|
|
|
PROFILES = os.path.join(os.path.dirname(__file__), "..", "emulators")
|
|
|
|
def test_the_mednafen_saturn_profiles_agree(self):
|
|
profiles = common.load_emulator_profiles(self.PROFILES)
|
|
seen: dict[str, dict[str, frozenset]] = {}
|
|
for name in ("mednafen", "beetle_saturn", "saturn-emu", "mednafen_stv"):
|
|
for entry in profiles[name]["files"]:
|
|
if entry["name"] in ("mpr-17933.bin", "sega_100a.bin"):
|
|
seen.setdefault(entry["name"], {})[name] = frozenset(
|
|
entry.get("region") or ()
|
|
)
|
|
for file_name, tags in seen.items():
|
|
with self.subTest(file=file_name):
|
|
self.assertEqual(len(set(tags.values())), 1, tags)
|
|
self.assertIn("oceania", next(iter(tags.values())))
|
|
|
|
def test_export_cartridges_and_allowed_regions(self):
|
|
profiles = common.load_emulator_profiles(self.PROFILES)
|
|
bios_sms = next(e for e in profiles["clk"]["files"] if e["name"] == "bios.sms")
|
|
self.assertIn("oceania", bios_sms["region"])
|
|
tagged = {
|
|
e["name"]: e.get("region") for e in profiles["kronos"]["files"]
|
|
if e.get("archive") == "stvbios.zip" and e["name"].startswith("epr-")
|
|
}
|
|
self.assertTrue(all(tagged.values()), tagged)
|
|
|
|
|
|
class AustraliaReadsThePalIpl(unittest.TestCase):
|
|
"""Dolphin files the Australian country code U under Region::PAL
|
|
(Source/Core/DiscIO/Enums.cpp), whose IPL is GC/EUR. Tagged [europe]
|
|
alone, `--region australia` dropped it and kept the American one."""
|
|
|
|
def test_the_pal_ipl_answers_oceania(self):
|
|
profiles = common.load_emulator_profiles(
|
|
os.path.join(os.path.dirname(__file__), "..", "emulators")
|
|
)
|
|
for name in ("dolphin", "primehack", "ishiiruka", "triforce"):
|
|
with self.subTest(profile=name):
|
|
pal = [f for f in profiles[name]["files"]
|
|
if "/EUR/" in str(f.get("path", ""))]
|
|
self.assertTrue(pal)
|
|
self.assertTrue(all("oceania" in f.get("region", []) for f in pal))
|
|
|
|
|
|
class TestProfileConformance(unittest.TestCase):
|
|
"""Every region: value in the repo is a list of canonical slugs."""
|
|
|
|
def setUp(self):
|
|
import glob
|
|
|
|
import yaml
|
|
|
|
self.repo_profiles = sorted(
|
|
glob.glob(
|
|
os.path.join(os.path.dirname(__file__), "..", "emulators", "*.yml")
|
|
)
|
|
)
|
|
if not self.repo_profiles:
|
|
self.skipTest("emulators/ not present")
|
|
self.yaml = yaml
|
|
|
|
def test_all_region_values_are_canonical_lists(self):
|
|
offenders: list[str] = []
|
|
for path in self.repo_profiles:
|
|
with open(path, encoding="utf-8") as fh:
|
|
profile = self.yaml.safe_load(fh) or {}
|
|
for f in profile.get("files") or []:
|
|
if not isinstance(f, dict) or "region" not in f:
|
|
continue
|
|
raw = f["region"]
|
|
if not isinstance(raw, list):
|
|
offenders.append(
|
|
f"{os.path.basename(path)}: {f.get('name')} not a list"
|
|
)
|
|
continue
|
|
for value in raw:
|
|
if value not in region.REGIONS:
|
|
offenders.append(
|
|
f"{os.path.basename(path)}: {f.get('name')} -> {value!r}"
|
|
)
|
|
self.assertEqual(offenders, [], "\n".join(offenders))
|
|
|
|
def test_index_builds_over_every_repo_profile(self):
|
|
profiles = {}
|
|
for path in self.repo_profiles:
|
|
with open(path, encoding="utf-8") as fh:
|
|
profiles[os.path.basename(path)[:-4]] = self.yaml.safe_load(fh) or {}
|
|
index = region.build_region_index(profiles)
|
|
self.assertGreater(len(index), 0)
|
|
|
|
|
|
class TestSchemaMatchesModule(unittest.TestCase):
|
|
def test_schema_enum_equals_module_regions(self):
|
|
import json
|
|
|
|
path = os.path.join(
|
|
os.path.dirname(__file__), "..", "schemas", "emulator.schema.json"
|
|
)
|
|
if not os.path.exists(path):
|
|
self.skipTest("schema not present")
|
|
with open(path, encoding="utf-8") as fh:
|
|
schema = json.load(fh)
|
|
node = schema["properties"]["files"]["items"]["properties"]["region"]
|
|
self.assertEqual(set(node["items"]["enum"]), set(region.REGIONS))
|
|
|
|
|
|
class TestVerifyModesHonourRegion(unittest.TestCase):
|
|
"""verify.py must apply --region in every mode, or refuse it.
|
|
|
|
It silently ignored the flag in --emulator and --system mode: the report
|
|
was identical with and without, so a filtered pack could not be checked.
|
|
"""
|
|
|
|
def _run(self, *args: str) -> str:
|
|
"""The report, from a run that succeeded.
|
|
|
|
A run that crashed produced an empty stdout, different from the
|
|
plain report, and the test read the difference as narrowing.
|
|
"""
|
|
import subprocess
|
|
|
|
repo = os.path.join(os.path.dirname(__file__), "..")
|
|
completed = subprocess.run(
|
|
[sys.executable, "scripts/verify.py", *args],
|
|
capture_output=True, text=True, cwd=repo, timeout=900, check=False,
|
|
)
|
|
self.assertEqual(completed.returncode, 0, completed.stderr[-2000:])
|
|
self.assertTrue(completed.stdout.strip(), "an empty report is not a report")
|
|
return completed.stdout
|
|
|
|
def test_emulator_mode_narrows(self):
|
|
plain = self._run("--emulator", "duckstation")
|
|
if "duckstation" not in plain:
|
|
self.skipTest("duckstation profile not present")
|
|
filtered = self._run("--emulator", "duckstation", "--region", "us")
|
|
self.assertNotEqual(plain, filtered)
|
|
|
|
def test_system_mode_narrows(self):
|
|
plain = self._run("--system", "sony-playstation")
|
|
filtered = self._run("--system", "sony-playstation", "--region", "us")
|
|
self.assertNotEqual(plain, filtered)
|
|
|
|
|
|
class TestReportAndBuilderNarrowTogether(unittest.TestCase):
|
|
"""A region run must withdraw the same files from report and pack.
|
|
|
|
The two sides grouped their candidates separately: the builder grouped the
|
|
platform files and the core extras, the report only the platform files.
|
|
So the report kept every core extra a region run withdraws from the pack
|
|
-on recalbox --region us, 59 files it described as covered.
|
|
"""
|
|
|
|
def _fixture(self):
|
|
import hashlib
|
|
import tempfile
|
|
|
|
tmp = tempfile.TemporaryDirectory()
|
|
root = Path(tmp.name)
|
|
(root / "emulators").mkdir()
|
|
db = {"files": {}, "indexes": {
|
|
"by_name": {}, "by_md5": {}, "by_sha256": {},
|
|
"by_crc32": {}, "by_path_suffix": {},
|
|
}}
|
|
# Two regional alternatives of one system, both held locally.
|
|
for fname, tag in (("us.bin", b"US BYTES"), ("jp.bin", b"JP BYTES")):
|
|
blob = root / fname
|
|
blob.write_bytes(tag)
|
|
sha1 = hashlib.sha1(tag).hexdigest()
|
|
db["files"][sha1] = {
|
|
"path": str(blob), "name": fname, "size": len(tag),
|
|
"sha1": sha1, "md5": hashlib.md5(tag).hexdigest(),
|
|
"sha256": hashlib.sha256(tag).hexdigest(), "crc32": "00000000",
|
|
}
|
|
db["indexes"]["by_name"][fname] = [sha1]
|
|
db["indexes"]["by_md5"][db["files"][sha1]["md5"]] = sha1
|
|
(root / "emulators" / "demo.yml").write_text(
|
|
"emulator: demo\n"
|
|
"type: libretro\n"
|
|
"display_name: Demo\n"
|
|
"systems: [demo-system]\n"
|
|
"cores: [demo]\n"
|
|
"files:\n"
|
|
" - name: us.bin\n"
|
|
" system: demo-system\n"
|
|
" region: [north-america]\n"
|
|
" required: true\n"
|
|
" - name: jp.bin\n"
|
|
" system: demo-system\n"
|
|
" region: [japan]\n"
|
|
" required: true\n"
|
|
)
|
|
config = {
|
|
"platform": "Demo",
|
|
"verification_mode": "existence",
|
|
"cores": ["demo"],
|
|
"base_destination": "",
|
|
"systems": {},
|
|
}
|
|
return tmp, root, db, config
|
|
|
|
def test_the_report_withdraws_what_the_builder_withdraws(self):
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
|
|
import common
|
|
import generate_pack as builder
|
|
import verify as reporter
|
|
|
|
tmp, root, db, config = self._fixture()
|
|
try:
|
|
common._emulator_profiles_cache.clear()
|
|
profiles = common.load_emulator_profiles(str(root / "emulators"))
|
|
groups, extra_dests = builder.platform_region_groups(
|
|
config, config["systems"], str(root / "emulators"),
|
|
db, "", profiles,
|
|
)
|
|
drops = region.resolve_region_drops(
|
|
groups, region.build_region_index(profiles), ["north-america"]
|
|
)
|
|
self.assertEqual(
|
|
{d for d in drops}, {"jp.bin"},
|
|
"the builder must drop the Japanese alternative, not the US one",
|
|
)
|
|
|
|
plain = reporter.verify_platform(
|
|
config, db, str(root / "emulators"), profiles,
|
|
)
|
|
narrowed = reporter.verify_platform(
|
|
config, db, str(root / "emulators"), profiles,
|
|
regions=["north-america"],
|
|
)
|
|
before = {u["name"] for u in plain["undeclared_files"]}
|
|
after = {u["name"] for u in narrowed["undeclared_files"]}
|
|
self.assertEqual(before, {"us.bin", "jp.bin"})
|
|
self.assertEqual(
|
|
before - after,
|
|
{d for d in drops},
|
|
"the report must withdraw exactly the builder's drop set",
|
|
)
|
|
finally:
|
|
common._emulator_profiles_cache.clear()
|
|
tmp.cleanup()
|
|
|
|
def test_a_missing_regional_core_file_is_withdrawn_too(self):
|
|
"""A missing platform file is grouped and withdrawn under --region, but a
|
|
missing core file was never grouped and stayed "required missing"
|
|
for a region the request excludes."""
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
|
|
import common
|
|
import verify as reporter
|
|
|
|
tmp, root, db, config = self._fixture()
|
|
try:
|
|
jp = next(k for k, v in db["files"].items() if v["name"] == "jp.bin")
|
|
del db["files"][jp]
|
|
del db["indexes"]["by_name"]["jp.bin"]
|
|
common._emulator_profiles_cache.clear()
|
|
profiles = common.load_emulator_profiles(str(root / "emulators"))
|
|
plain = reporter.verify_platform(
|
|
config, db, str(root / "emulators"), profiles,
|
|
)
|
|
narrowed = reporter.verify_platform(
|
|
config, db, str(root / "emulators"), profiles,
|
|
regions=["north-america"],
|
|
)
|
|
missing = [u["name"] for u in plain["undeclared_files"] if not u["in_repo"]]
|
|
self.assertEqual(missing, ["jp.bin"])
|
|
self.assertEqual(
|
|
{u["name"] for u in narrowed["undeclared_files"]}, {"us.bin"}
|
|
)
|
|
finally:
|
|
common._emulator_profiles_cache.clear()
|
|
tmp.cleanup()
|
|
|
|
def test_the_report_names_the_fallback_groups(self):
|
|
"""The pack README lists the systems kept whole because nothing
|
|
answers the request. verify said all OK and named none."""
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
|
|
import common
|
|
import verify as reporter
|
|
|
|
tmp, root, db, config = self._fixture()
|
|
try:
|
|
common._emulator_profiles_cache.clear()
|
|
profiles = common.load_emulator_profiles(str(root / "emulators"))
|
|
result = reporter.verify_platform(
|
|
config, db, str(root / "emulators"), profiles,
|
|
regions=["brazil"],
|
|
)
|
|
self.assertEqual(result["region_fallbacks"], ["demo-system"])
|
|
out = io.StringIO()
|
|
with contextlib.redirect_stdout(out):
|
|
reporter.print_platform_result(result, ["Demo"])
|
|
self.assertIn("all kept (1): demo-system", out.getvalue())
|
|
finally:
|
|
common._emulator_profiles_cache.clear()
|
|
tmp.cleanup()
|
|
|
|
def test_one_grouping_pass_serves_both_sides(self):
|
|
"""A second hand-rolled grouping is how the two drifted apart.
|
|
|
|
Every module is read, not only the two that once held a copy: the
|
|
builder was split into several modules and a grouping can move with
|
|
it. packextras.py holds the two shared passes, platform and emulator.
|
|
"""
|
|
scripts = Path(__file__).resolve().parent.parent / "scripts"
|
|
for path in sorted(scripts.rglob("*.py")):
|
|
if path.name == "packextras.py":
|
|
continue
|
|
with self.subTest(module=path.name):
|
|
self.assertNotIn(
|
|
"region_groups.setdefault(", path.read_text(encoding="utf-8"),
|
|
"region grouping belongs to packextras: platform_region_groups "
|
|
"or emulator_region_drops",
|
|
)
|
|
|
|
def test_the_verifier_reads_the_grouping_where_it_lives(self):
|
|
"""Imported through the builder's CLI module, the verdict of a
|
|
--region run depended on that module compiling, and seven scripts
|
|
entered the site workflow's trigger paths for one function."""
|
|
source = (Path(__file__).resolve().parent.parent / "scripts" / "verify.py").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
self.assertNotIn("from generate_pack import platform_region_groups", source)
|
|
self.assertIn("from packextras import platform_region_groups", source)
|
|
|
|
def test_emulator_report_keeps_what_the_emulator_pack_keeps(self):
|
|
"""O2EM: --region us left the Videopac BIOSes in the pack, not in the report."""
|
|
repo = Path(__file__).resolve().parent.parent
|
|
if not (repo / "emulators" / "o2em.yml").is_file():
|
|
self.skipTest("no o2em profile")
|
|
from verify import verify_emulator
|
|
from common import load_database
|
|
|
|
db = load_database(str(repo / "database.json"))
|
|
full = verify_emulator(["o2em"], str(repo / "emulators"), db)
|
|
narrowed = verify_emulator(["o2em"], str(repo / "emulators"), db, regions=["north-america"])
|
|
names = {d["name"] for d in narrowed["details"]}
|
|
for kept in ("c52.bin", "g7400.bin", "jopac.bin"):
|
|
self.assertIn(kept, names, "a system with no candidate in the region falls back")
|
|
self.assertEqual(full["total_files"], narrowed["total_files"])
|
|
|
|
def test_every_grouping_call_carries_the_target(self):
|
|
"""Integrity grouped every core's extras while the builder grouped the target's.
|
|
|
|
Under --target with --region, a fallback the pack kept was withdrawn
|
|
from the check: 14 Batocera rk3568 files were never verified.
|
|
"""
|
|
import ast
|
|
|
|
scripts = Path(__file__).resolve().parent.parent / "scripts"
|
|
for path in sorted(scripts.rglob("*.py")):
|
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
|
for func in ast.walk(tree):
|
|
if not isinstance(func, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
continue
|
|
params = {a.arg for a in func.args.args + func.args.kwonlyargs}
|
|
if "target_cores" not in params:
|
|
continue
|
|
for call in ast.walk(func):
|
|
if (
|
|
isinstance(call, ast.Call)
|
|
and getattr(call.func, "id", None) == "platform_region_groups"
|
|
):
|
|
with self.subTest(module=path.name, function=func.name):
|
|
self.assertIn(
|
|
"target_cores", {k.arg for k in call.keywords}
|
|
)
|
|
|
|
def test_pal_console_region_reaches_oceania(self):
|
|
"""PSX cores pick these BIOSes for the PAL console region, not for Europe alone.
|
|
|
|
DuckStation's ConsoleRegion::PAL, Beetle PSX, SwanStation, PCSX ReARMed's
|
|
EU slot and Rustation all serve Australian and New Zealand discs from the
|
|
same images. Annotated [europe] only, --region australia dropped them.
|
|
"""
|
|
import yaml
|
|
|
|
repo = Path(__file__).resolve().parent.parent
|
|
for emu in ("duckstation", "beetle_psx", "swanstation", "pcsx_rearmed", "rustation"):
|
|
profile = yaml.safe_load((repo / "emulators" / f"{emu}.yml").read_text())
|
|
for entry in profile.get("files", []):
|
|
regions = entry.get("region") or []
|
|
if "europe" in regions:
|
|
with self.subTest(emulator=emu, name=entry.get("name")):
|
|
self.assertIn("oceania", regions)
|
|
|
|
|
|
|
|
class RegionalRequestsKeepWhatTheCodeLoads(unittest.TestCase):
|
|
"""A --region request never drops the file the core opens for it.
|
|
|
|
mednafen_pce_fast tagged option-selected cards with territories, beetle_psx
|
|
tagged its override BIOS [world], mednafen tagged the ST-V NA BIOS for
|
|
America alone and the PAL PSX BIOS for Europe alone.
|
|
"""
|
|
|
|
CASES = (
|
|
("mednafen_pce_fast", ["north-america"], "syscard3.pce"),
|
|
("beetle_psx", ["brazil"], "scph5501.bin"),
|
|
("mednafen", ["south-korea", "europe"], "epr-17952a.ic8"),
|
|
("mednafen", ["australia", "north-america"], "scph5502.bin"),
|
|
("bluemsx", ["south-korea"], "Machines/Shared Roms/KANJI.rom"),
|
|
("bluemsx", ["south-korea"], "Machines/Shared Roms/MSXKANJI.rom"),
|
|
("bluemsx", ["japan"], "Machines/Shared Roms/HANGUL.rom"),
|
|
("ace-dl", ["france"], "private/firmware/OS6128_UK.rom"),
|
|
("ace-dl", ["spain"], "private/firmware/OS664_UK.rom"),
|
|
("ace-dl", ["spain"], "private/firmware/OS464_FR.rom"),
|
|
("ace-dl", ["denmark"], "private/firmware/XMEM_FW_3_15_UK.rom"),
|
|
("ace-dl", ["spain"], "private/hacker/hacker481fr.rom"),
|
|
("ace-dl", ["france"], "private/firmware/Amstrad_s4.cpr"),
|
|
("duckstation", ["brazil"], "scph5501.bin"),
|
|
("duckstation", ["south-korea"], "scph5500.bin"),
|
|
)
|
|
|
|
def test_each_request_keeps_its_file(self):
|
|
profiles = common.load_emulator_profiles(
|
|
os.path.join(os.path.dirname(__file__), "..", "emulators")
|
|
)
|
|
for name, regions, kept in self.CASES:
|
|
with self.subTest(profile=name, regions=regions):
|
|
drops = emulator_region_drops([(name, profiles[name])], False, regions)
|
|
self.assertNotIn(kept, drops)
|
|
|
|
|
|
|
|
class FontRomsCarryNoTerritory(unittest.TestCase):
|
|
"""A CJK font ROM is a language, not a territory. blueMSX tagged KANJI.rom
|
|
and MSXKANJI.rom [japan], which a --region south-korea request withdrew
|
|
from the generic MSX2+ machine whose config.ini loads both."""
|
|
|
|
def test_no_kanji_or_hangul_font_declares_a_region(self):
|
|
profiles = common.load_emulator_profiles(
|
|
os.path.join(os.path.dirname(__file__), "..", "emulators")
|
|
)
|
|
tagged = [
|
|
f"{name}:{entry['name']}"
|
|
for name, profile in profiles.items()
|
|
for entry in profile.get("files", [])
|
|
if any(word in entry.get("name", "").lower() for word in ("kanji", "hangul"))
|
|
and entry.get("region")
|
|
]
|
|
self.assertEqual(tagged, [])
|
|
|
|
class EmulatorModeKeepsAHeldFile(unittest.TestCase):
|
|
"""Platform mode never lets a missing file displace a held one. Emulator
|
|
mode did. A held scph5501.bin (north-america) was withdrawn under
|
|
--region jp for a Japanese image the collection lacks, and the pack
|
|
shipped no PlayStation BIOS."""
|
|
|
|
def test_a_missing_match_does_not_withdraw_the_held_file(self):
|
|
import hashlib # noqa: PLC0415
|
|
import shutil # noqa: PLC0415
|
|
import tempfile # noqa: PLC0415
|
|
|
|
root = Path(tempfile.mkdtemp(dir=Path(__file__).resolve().parents[1] / "tmp"))
|
|
self.addCleanup(shutil.rmtree, root, True)
|
|
held = root / "scph5501.bin"
|
|
held.write_bytes(b"us bios")
|
|
sha1 = hashlib.sha1(b"us bios").hexdigest()
|
|
db = {"files": {sha1: {"path": str(held), "name": held.name, "sha1": sha1}},
|
|
"indexes": {"by_name": {held.name: [sha1]}, "by_md5": {},
|
|
"by_crc32": {}, "by_path_suffix": {}}}
|
|
profile = {"systems": ["sony-playstation"], "files": [
|
|
{"name": "scph5501.bin", "region": ["north-america"]},
|
|
{"name": "scph5500.bin", "region": ["japan"]},
|
|
]}
|
|
selected = [("psx", profile)]
|
|
self.assertEqual(
|
|
emulator_region_drops(selected, False, ["japan"], db=db), set()
|
|
)
|
|
# Without the collection to ask, the old reading stands.
|
|
self.assertEqual(
|
|
emulator_region_drops(selected, False, ["japan"]), {"scph5501.bin"}
|
|
)
|
|
|
|
|
|
|
|
class UnknownRegionSaysWhatToType(unittest.TestCase):
|
|
"""`unknown region: 'uss'` named nothing to type instead."""
|
|
|
|
def test_the_build_scripts_suggest_and_list_short_forms(self):
|
|
import region
|
|
|
|
with self.assertRaises(ValueError) as caught:
|
|
region.parse_requested("uss")
|
|
message = str(caught.exception)
|
|
self.assertIn("Did you mean: us", message)
|
|
self.assertIn("Short forms: jp", message)
|
|
|
|
def test_the_installer_says_the_same(self):
|
|
import importlib.util
|
|
|
|
spec = importlib.util.spec_from_file_location(
|
|
"install_under_test", Path(__file__).resolve().parents[1] / "install.py"
|
|
)
|
|
install = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(install)
|
|
with self.assertRaises(ValueError) as caught:
|
|
install.canonical_region("europ")
|
|
self.assertIn("Did you mean: europe", str(caught.exception))
|
|
self.assertIn("Short forms:", str(caught.exception))
|
|
|
|
|
|
|
|
class GenesisPlusGxUsBiosServesEurope(unittest.TestCase):
|
|
"""sms_cart_region_detect returns REGION_USA for any export header
|
|
outside the PAL CRC list, European cartridges included, and REGION_USA
|
|
loads bios_U.sms. Tagged north-america only, --region eu,jp dropped it."""
|
|
|
|
def test_eu_jp_keeps_bios_u(self):
|
|
from pathlib import Path
|
|
|
|
from common import load_emulator_profiles
|
|
from region import build_region_index, parse_requested, resolve_region_drops
|
|
|
|
root = Path(__file__).resolve().parents[1]
|
|
profiles = load_emulator_profiles(str(root / "emulators"))
|
|
for name in ("genesis_plus_gx", "genesis_plus_gx_wide"):
|
|
with self.subTest(profile=name):
|
|
index = build_region_index({name: profiles[name]})
|
|
group = {"sega-mastersystem": [
|
|
(f"bios/{n}", n) for n in ("bios_U.sms", "bios_E.sms", "bios_J.sms")
|
|
]}
|
|
drops = resolve_region_drops(
|
|
group, index, parse_requested("eu,jp"), None
|
|
)
|
|
self.assertNotIn("bios/bios_U.sms", drops)
|
|
|
|
|
|
|
|
def _real_drops(profile: str, members: list[str], requested: str) -> set[str]:
|
|
from pathlib import Path
|
|
|
|
from common import load_emulator_profiles
|
|
from region import build_region_index, parse_requested, resolve_region_drops
|
|
|
|
root = Path(__file__).resolve().parents[1]
|
|
profiles = load_emulator_profiles(str(root / "emulators"))
|
|
index = build_region_index({profile: profiles[profile]})
|
|
group = {"g": [(m, m.rsplit("/", 1)[-1]) for m in members]}
|
|
return resolve_region_drops(group, index, parse_requested(requested), None)
|
|
|
|
|
|
class MegaCdUsBiosIsTheDefaultBranch(unittest.TestCase):
|
|
"""GPGX and PicoDrive load the US Mega CD BIOS for any disc whose
|
|
security byte is neither 0x64 nor 0xa1, Brazilian discs included:
|
|
--region brazil,jp dropped it and those discs found no BIOS."""
|
|
|
|
def test_brazil_keeps_the_us_bios(self):
|
|
cases = {
|
|
"genesis_plus_gx": ["bios_CD_U.bin", "bios_CD_E.bin", "bios_CD_J.bin"],
|
|
"genesis_plus_gx_wide": ["bios_CD_U.bin", "bios_CD_E.bin", "bios_CD_J.bin"],
|
|
"picodrive": ["us_scd2_9306.bin", "eu_mcd2_9306.bin", "jp_mcd2_921222.bin"],
|
|
}
|
|
for profile, members in cases.items():
|
|
with self.subTest(profile=profile):
|
|
drops = _real_drops(profile, members, "brazil,jp")
|
|
self.assertNotIn(members[0], drops)
|
|
|
|
|
|
class KoreanGameCubeDiscsReadTheJapaneseIpl(unittest.TestCase):
|
|
"""Dolphin's ToGameCubeRegion maps NTSC-K to NTSC-J, so a Korean disc
|
|
boots from GC/JAP/IPL.bin; --region south-korea,us dropped it."""
|
|
|
|
def test_korea_keeps_the_jap_ipl(self):
|
|
for profile in ("dolphin", "dolphin-mmjr", "dolphin-mmjr2", "ishiiruka",
|
|
"primehack", "triforce"):
|
|
with self.subTest(profile=profile):
|
|
from common import load_emulator_profiles
|
|
|
|
entries = [
|
|
f for f in load_emulator_profiles("emulators")[profile]["files"]
|
|
if "JAP/" in (f.get("path") or "")
|
|
]
|
|
self.assertTrue(entries)
|
|
for entry in entries:
|
|
self.assertIn("south-korea", entry.get("region") or [])
|
|
|
|
|
|
class ANameNoCodeOpensCarriesNoRegion(unittest.TestCase):
|
|
"""Beetle PSX never opens scph5903.bin: its Japanese list and scan want
|
|
scph5500.bin. Tagged japan, --region jp shipped a 1 MB file the core
|
|
does not read and --region us removed it, on a selection that does not
|
|
exist."""
|
|
|
|
def test_scph5903_is_unregioned(self):
|
|
from common import load_emulator_profiles
|
|
|
|
entry = next(
|
|
f for f in load_emulator_profiles("emulators")["beetle_psx"]["files"]
|
|
if f["name"] == "scph5903.bin"
|
|
)
|
|
self.assertNotIn("region", entry)
|
|
|
|
|
|
class MegaCdAmericasIsOneCode(unittest.TestCase):
|
|
"""A Sega CD disc carries one region code for all the Americas, so every
|
|
emulator picking the US BIOS by that code picks it for a Brazilian disc
|
|
too. jgenesis, md-emu and ares tagged it north-america alone, and a
|
|
--region brazil pack dropped the BIOS those discs boot with."""
|
|
|
|
def test_a_us_mega_cd_bios_includes_latin_america(self):
|
|
import yaml
|
|
|
|
root = Path(__file__).resolve().parents[1]
|
|
narrow = []
|
|
for path in sorted((root / "emulators").glob("*.yml")):
|
|
profile = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
for entry in profile.get("files") or []:
|
|
system = str(entry.get("system", ""))
|
|
regions = entry.get("region") or []
|
|
if system in ("sega-megacd", "mega-cd") and "north-america" in regions \
|
|
and "latin-america" not in regions:
|
|
narrow.append(f"{path.stem}: {entry.get('name')}")
|
|
self.assertEqual(narrow, [])
|
|
|
|
|
|
class NoMarketStopsAtAustralia(unittest.TestCase):
|
|
"""simple64 tagged its PAL PIF [europe, australia]. New Zealand is not
|
|
under australia, so --region new-zealand kept only the NTSC PIF, though
|
|
New Zealand carts carry the European or Australian PAL codes."""
|
|
|
|
def test_no_profile_tags_australia_alone(self):
|
|
import yaml
|
|
|
|
root = Path(__file__).resolve().parents[1]
|
|
tagged = [
|
|
f"{path.stem}: {entry.get('name')}"
|
|
for path in sorted((root / "emulators").glob("*.yml"))
|
|
for entry in (yaml.safe_load(path.read_text(encoding="utf-8")) or {}).get("files") or []
|
|
if "australia" in (entry.get("region") or [])
|
|
and "new-zealand" not in (entry.get("region") or [])
|
|
]
|
|
self.assertEqual(tagged, [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|