Coverage for src/ph/platforms.py: 93.5%
44 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 08:17 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 08:17 +0000
1"""EmulationStation platform metadata and ROM-folder mappings."""
3from collections.abc import Iterable, Sequence
4from pathlib import Path
6from ph.compatibility import is_unsupported_system
7from ph.models import Platform
8from ph.targets import LinuxTarget
11def _platform(
12 name: str,
13 slug: str,
14 alias: str,
15 folder: str,
16 code: str = "",
17 *alternate_folders: str,
18) -> Platform:
19 return Platform(name, slug, code, alias, folder, alternate_folders)
22# The initial folder profile follows DarkOS EmulationStation. Console
23# families outside the supported handheld profile are intentionally omitted.
24# Empty service codes use all-platform search while preserving the ROM destination.
25DARKOS_PLATFORMS: tuple[Platform, ...] = (
26 Platform("All platforms", "all", "", "ALL"),
27 _platform("3DO", "3do", "3DO", "3do"),
28 _platform("Adventure Vision", "adventure-vision", "ADV", "advision"),
29 _platform("American Laser Games", "american-laser-games", "ALG", "alg"),
30 _platform("Amiga", "amiga", "AMIGA", "amiga"),
31 _platform("Amiga CD32", "amiga-cd32", "CD32", "amigacd32"),
32 _platform("Amstrad CPC", "amstrad-cpc", "CPC", "amstradcpc"),
33 _platform("Amstrad GX4000", "amstrad-gx4000", "GX4000", "gx4000"),
34 _platform("Apple II", "apple-ii", "APPLE2", "apple2"),
35 _platform("Apple Macintosh", "apple-macintosh", "MAC", "vmac"),
36 _platform("Aquaplus P/ECE", "aquaplus-piece", "PIECE", "piece"),
37 _platform("Arcade", "arcade", "ARCADE", "arcade"),
38 _platform("Arduboy", "arduboy", "ARDUBOY", "arduboy"),
39 _platform("Astrocade", "astrocade", "ASTRO", "astrocde"),
40 _platform("Atomiswave", "atomiswave", "ATOM", "atomiswave"),
41 _platform("Atari 800", "atari-800", "A800", "atari800"),
42 _platform("Atari 2600", "atari-2600", "A2600", "atari2600", "Atari2600"),
43 _platform("Atari 5200", "atari-5200", "A5200", "atari5200", "Atari5200"),
44 _platform("Atari 7800", "atari-7800", "A7800", "atari7800", "Atari7800"),
45 _platform("Atari Jaguar", "atari-jaguar", "JAG", "atarijaguar", "Jaguar"),
46 _platform("Atari Lynx", "atari-lynx", "LYNX", "atarilynx", "Lynx"),
47 _platform("Atari ST", "atari-st", "AST", "atarist"),
48 _platform("Atari XEGS", "atari-xegs", "XEGS", "atarixegs"),
49 _platform("ColecoVision", "colecovision", "COLECO", "coleco"),
50 _platform("Commodore 16", "commodore-16", "C16", "c16"),
51 _platform("Commodore 64 / PET", "commodore-64", "C64", "c64"),
52 _platform("Commodore 128", "commodore-128", "C128", "c128"),
53 _platform("Commodore VIC-20", "commodore-vic-20", "VIC20", "vic20"),
54 _platform("Capcom Play System I", "cps-1", "CPS1", "cps1"),
55 _platform("Capcom Play System II", "cps-2", "CPS2", "cps2"),
56 _platform("Capcom Play System III", "cps-3", "CPS3", "cps3"),
57 _platform("Daphne", "daphne", "DAPHNE", "daphne"),
58 _platform("Doom", "doom", "DOOM", "doom"),
59 _platform("Dreamcast", "dreamcast", "DC", "dreamcast", "Dreamcast"),
60 _platform("Dreamcast VMU", "dreamcast-vmu", "VMU", "vmu"),
61 _platform("EasyRPG", "easyrpg", "EASYRPG", "easyrpg"),
62 _platform("Enterprise 64/128", "enterprise", "EP64", "enterprise"),
63 _platform("Fairchild Channel F", "fairchild-channel-f", "CHF", "channelf"),
64 _platform("Famicom Disk System", "famicom-disk-system", "FDS", "fds"),
65 _platform("Game Boy", "game-boy", "GB", "gb", "GB"),
66 _platform("Game Boy Advance", "game-boy-advance", "GBA", "gba", "GBA"),
67 _platform("Game & Watch", "game-and-watch", "GW", "gameandwatch"),
68 _platform("Game Boy Color", "game-boy-color", "GBC", "gbc", "GBC"),
69 _platform("Game Gear", "game-gear", "GG", "gamegear", "GG"),
70 _platform("Genesis / Mega Drive", "genesis", "GEN", "megadrive", "Genesis", "genesis"),
71 _platform("Intellivision", "intellivision", "INTV", "intellivision"),
72 _platform("LÖVE", "love2d", "LOVE", "love2d"),
73 _platform("LowRes NX", "lowres-nx", "LRNX", "lowresnx"),
74 _platform("MAME 2003", "mame-2003", "MAME03", "mame2003"),
75 _platform("MAME 2010", "mame-2010", "MAME", "mame"),
76 _platform("Master System", "master-system", "SMS", "mastersystem", "SMS"),
77 _platform("Mega Drive MSU", "megadrive-msu", "MSUMD", "msumd"),
78 _platform("Mega Duck", "mega-duck", "MDUCK", "megaduck"),
79 _platform("Microvision", "microvision", "MV", "mv"),
80 _platform("MSX", "msx", "MSX", "msx"),
81 _platform("MSX2", "msx2", "MSX2", "msx2"),
82 _platform("Naomi", "naomi", "NAOMI", "naomi"),
83 _platform("Neo Geo", "neo-geo", "NEOGEO", "neogeo"),
84 _platform("Neo Geo CD", "neo-geo-cd", "NGCD", "neogeocd"),
85 _platform("Neo Geo Pocket", "neo-geo-pocket", "NGP", "ngp"),
86 _platform("Neo Geo Pocket Color", "neo-geo-pocket-color", "NGPC", "ngpc"),
87 _platform("Nintendo 64", "nintendo-64", "N64", "n64", "N64"),
88 _platform("Nintendo 64DD", "nintendo-64dd", "N64DD", "n64dd"),
89 _platform("Nintendo DS", "nintendo-ds", "NDS", "nds", "DS"),
90 _platform("Nintendo Entertainment System", "nintendo", "NES", "nes", "NES", "famicom"),
91 _platform("Odyssey²", "odyssey-2", "O2", "odyssey2"),
92 _platform("OpenBOR", "openbor", "OPENBOR", "openbor"),
93 _platform("Palm OS", "palm-os", "PALM", "palm"),
94 _platform("PC-98", "pc-98", "PC98", "pc98"),
95 _platform("PC / MS-DOS", "dos", "DOS", "dos"),
96 _platform("PC Engine / TurboGrafx-16", "pc-engine", "PCE", "pcengine", "TG16", "turbografx"),
97 _platform(
98 "PC Engine CD / TurboGrafx-CD",
99 "pc-engine-cd",
100 "PCECD",
101 "pcenginecd",
102 "TGCD",
103 "turbografxcd",
104 ),
105 _platform("PC-FX", "pc-fx", "PCFX", "pcfx"),
106 _platform("PICO-8", "pico-8", "PICO8", "pico-8/carts"),
107 _platform("PlayStation", "playstation", "PS1", "psx", "PS1"),
108 _platform("PlayStation Portable", "ps-portable", "PSP", "psp", "PSP"),
109 _platform("PSP Minis", "psp-minis", "PSPM", "pspminis"),
110 _platform("Pokémon Mini", "pokemon-mini", "PKMINI", "pokemonmini"),
111 _platform("PuzzleScript", "puzzlescript", "PUZZLE", "puzzlescript"),
112 _platform("Satellaview", "satellaview", "BSX", "satellaview"),
113 _platform("ScummVM", "scummvm", "SCUMM", "scummvm"),
114 _platform("Sega 32X", "sega-32x", "32X", "sega32x", "32X"),
115 _platform("Sega CD", "sega-cd", "SCD", "segacd", "SegaCD"),
116 _platform("Sega Pico", "sega-pico", "PICO", "pico"),
117 _platform("Sega Saturn", "saturn", "SAT", "saturn", "Saturn"),
118 _platform("SG-1000", "sg-1000", "SG1K", "sg-1000"),
119 _platform("Sharp X1", "sharp-x1", "X1", "x1"),
120 _platform("Sharp X68000", "sharp-x68000", "X68K", "x68000"),
121 _platform("Solarus", "solarus", "SOLARUS", "solarus"),
122 _platform("SuFami Turbo", "sufami-turbo", "SUFAMI", "sufami"),
123 _platform("Super Cassette Vision", "super-cassette-vision", "SCV", "scv"),
124 _platform("Super Game Boy", "super-game-boy", "SGB", "sgb"),
125 _platform("SuperGrafx", "supergrafx", "SGFX", "supergrafx"),
126 _platform("Super Nintendo", "super-nintendo", "SNES", "snes", "SNES", "sfc"),
127 _platform("Super Nintendo MSU1", "super-nintendo-msu1", "MSU1", "snesmsu1"),
128 _platform("Super Nintendo Hacks", "super-nintendo-hacks", "SNESH", "snes-hacks"),
129 _platform("Tandy Color Computer 3", "coco-3", "COCO3", "coco3"),
130 _platform("Thomson", "thomson", "THOM", "thomson"),
131 _platform("TIC-80", "tic-80", "TIC80", "tic80"),
132 _platform("TI-99/4A", "ti-99", "TI99", "ti99"),
133 _platform("Uzebox", "uzebox", "UZE", "uzebox"),
134 _platform("Vectrex", "vectrex", "VECT", "vectrex"),
135 _platform("Videopac", "videopac", "VIDPAC", "videopac"),
136 _platform("Videoton TV-Computer", "videoton-tvc", "TVC", "tvc"),
137 _platform("Vircon32", "vircon32", "V32", "vircon32"),
138 _platform("Virtual Boy", "virtual-boy", "VB", "virtualboy", "VB"),
139 _platform("ONScripter", "onscripter", "ONS", "onscripter"),
140 _platform("WASM-4", "wasm-4", "WASM4", "wasm4"),
141 _platform("Watara Supervision", "watara-supervision", "SUPERV", "supervision"),
142 _platform("Wolfenstein", "wolfenstein", "WOLF", "wolf"),
143 _platform("WonderSwan", "wonderswan", "WS", "wonderswan"),
144 _platform("WonderSwan Color", "wonderswan-color", "WSC", "wonderswancolor"),
145 _platform("ZX81", "zx81", "ZX81", "zx81"),
146 _platform("ZX Spectrum", "zx-spectrum", "ZXS", "zxspectrum"),
147 _platform("Ports", "ports", "PORTS", "ports"),
148)
150NON_PLATFORM_DIRECTORIES = {
151 "backup",
152 "bios",
153 "bgmusic",
154 "downloads",
155 "launchimages",
156 "lost+found",
157 "movies",
158 "screensavers",
159 "splash",
160 "themes",
161 "tools",
162 "videos",
163}
166def discover_platforms(
167 roms_directories: Iterable[Path], known: Sequence[Platform] = DARKOS_PLATFORMS
168) -> tuple[Platform, ...]:
169 """Add image-specific ROM folders found on SD1 or SD2 to the known set."""
171 discovered = list(known)
172 known_roots = {
173 folder.split("/", maxsplit=1)[0].casefold()
174 for platform in known
175 for folder in platform.rom_folders
176 }
177 custom_names: set[str] = set()
178 for root in roms_directories:
179 try:
180 folders = tuple(path for path in root.iterdir() if path.is_dir())
181 except OSError:
182 continue
183 for folder in folders:
184 folded = folder.name.casefold()
185 if (
186 folded.startswith(".")
187 or folded in known_roots
188 or folded in NON_PLATFORM_DIRECTORIES
189 or is_unsupported_system(folder.name)
190 ):
191 continue
192 custom_names.add(folder.name)
193 for name in sorted(custom_names, key=str.casefold):
194 discovered.append(
195 Platform(
196 name=f"Detected: {name}",
197 slug=f"detected-{name.casefold()}",
198 code="",
199 alias=name,
200 rom_folder=name,
201 )
202 )
203 return tuple(discovered)
206def _build_aliases() -> dict[str, Platform]:
207 aliases: dict[str, Platform] = {}
208 for platform in DARKOS_PLATFORMS:
209 values = (
210 platform.name,
211 platform.slug,
212 platform.alias,
213 platform.code,
214 *platform.rom_folders,
215 )
216 for value in values:
217 if value:
218 aliases[value.casefold()] = platform
219 return aliases
222_ALIASES = _build_aliases()
225def platform_catalogue(target: LinuxTarget) -> tuple[Platform, ...]:
226 """Return the ROM-folder catalogue owned by an OS target profile."""
228 if target.platform_profile == "darkos":
229 return DARKOS_PLATFORMS
230 raise ValueError(f"No platform catalogue is registered for {target.display_name}.")
233def resolve_platform(
234 value: str,
235 known: Sequence[Platform] = DARKOS_PLATFORMS,
236) -> Platform | None:
237 """Resolve a slug, code, short alias, or ROM folder case-insensitively."""
239 normalized = value.strip().casefold()
240 if known is DARKOS_PLATFORMS: 240 ↛ 242line 240 didn't jump to line 242 because the condition on line 240 was always true
241 return _ALIASES.get(normalized)
242 return next(
243 (
244 platform
245 for platform in known
246 if normalized
247 in {
248 platform.name.casefold(),
249 platform.slug.casefold(),
250 platform.alias.casefold(),
251 platform.code.casefold(),
252 *(folder.casefold() for folder in platform.rom_folders),
253 }
254 ),
255 None,
256 )