mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-21 11:19:11 -05:00
The scraper now records, from configgen at the pinned tag, the names each Recalbox launcher tries and where it hands them: the Hatari TOS cascades (standalone, hatari and hatarib cores), GSplus, Amiberry kickstarts, the PCSX2 region choice, the Dolphin IPL presence test, xemu, the mupen64plus 64DD IPL, the Mini vMac boot disk and the o2em_bios option. The system_directory libretroRetroarch gives each core, and the per-system overrides after it, are passes_path records.
1569 lines
69 KiB
Python
1569 lines
69 KiB
Python
#!/usr/bin/env python3
|
|
"""Recalbox's launcher layer, read from configgen with ast.
|
|
|
|
The blocks below are copied from Recalbox 10.1.1 (projects/configgen) and
|
|
placed at the lines they hold there, so the source_ref an extraction writes
|
|
is checked against the tag itself.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
import unittest
|
|
from collections.abc import Callable
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
import yaml
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
|
|
from scraper import recalbox_scraper # noqa: E402
|
|
from scraper.recalbox_launchers import ( # noqa: E402
|
|
ALL_CORES,
|
|
AMIBERRY_CONFIG,
|
|
AMIBERRY_KICKSTARTS,
|
|
AMIBERRY_SUBSYSTEMS,
|
|
CONFIGGEN,
|
|
CORES,
|
|
DOLPHIN,
|
|
FILES_MODULE,
|
|
GSPLUS,
|
|
HATARI,
|
|
LAUNCHER,
|
|
LIBRETRO_GENERATOR,
|
|
MUPEN_CONFIG,
|
|
PCSX2,
|
|
RETROARCH,
|
|
SOURCE_FILES,
|
|
XEMU,
|
|
Configgen,
|
|
Launcher,
|
|
LauncherShapeError,
|
|
amiberry,
|
|
content_files,
|
|
core_assets,
|
|
core_file_options,
|
|
dolphin,
|
|
extract_launchers,
|
|
gsplus,
|
|
hatari,
|
|
hatari_libretro,
|
|
mupen64plus,
|
|
pcsx2,
|
|
system_directories,
|
|
xemu,
|
|
)
|
|
|
|
from tests.test_launchers import placed # noqa: E402
|
|
|
|
# recalboxFiles.py:1-12, 173-174
|
|
FILES_MODULE_BLOCKS = {
|
|
1: """#!/usr/bin/env python
|
|
HOME_INIT = '/recalbox/share_init/system/'
|
|
HOME = '/recalbox/share/system'
|
|
CONF = HOME + '/configs'
|
|
SAVES = '/recalbox/share/saves'
|
|
SAVES_INIT = '/recalbox/share_init/saves'
|
|
SCREENSHOTS = '/recalbox/share/screenshots'
|
|
BIOS = '/recalbox/share/bios'
|
|
BIOS_INIT = '/recalbox/share_init/bios'
|
|
OVERLAYS = '/recalbox/share/overlays'
|
|
RECALBOX_OVERLAYS = '/recalbox/share_init/overlays'
|
|
RECALBOX_240P_OVERLAYS = '/recalbox/share_init/240poverlays'""",
|
|
173: """minivmacRomFile = '/tmp/minivmac.cmd'
|
|
minivmacOsFile = BIOS + '/macintosh/MinivMacBootv2.dsk'""",
|
|
}
|
|
|
|
# emulatorlauncher.py:10-19, 29-34, 47-52, 65-67, 80-82, 125-127, 131-136
|
|
LAUNCHER_BLOCKS = {
|
|
10: '''def getGenerator(emulator):
|
|
|
|
# We use a if...elif...else structure in order to instantiate the strict minimum required for an emulator
|
|
# and minimize static imports that are very time consuming, specially on low-end boards
|
|
if emulator == "advancemame":
|
|
module = __import__("configgen.generators.advancemame.advMameGenerator", fromlist=["AdvMameGenerator"])
|
|
generatorClass = getattr(module, "AdvMameGenerator")
|
|
elif emulator == "amiberry":
|
|
module = __import__("configgen.generators.amiberry.amiberryGenerator", fromlist=["AmiberryGenerator"])
|
|
generatorClass = getattr(module, "AmiberryGenerator")''',
|
|
29: ''' elif emulator == "dolphin":
|
|
module = __import__("configgen.generators.dolphin.dolphinGenerator", fromlist=["DolphinGenerator"])
|
|
generatorClass = getattr(module, "DolphinGenerator")
|
|
elif emulator == "dolphin-gui":
|
|
module = __import__("configgen.generators.dolphin.dolphinGenerator", fromlist=["DolphinGenerator"])
|
|
generatorClass = getattr(module, "DolphinGenerator")''',
|
|
47: ''' elif emulator == "gsplus":
|
|
module = __import__("configgen.generators.gsplus.gsplusGenerator", fromlist=["GSplusGenerator"])
|
|
generatorClass = getattr(module, "GSplusGenerator")
|
|
elif emulator == "hatari":
|
|
module = __import__("configgen.generators.hatari.hatariGenerator", fromlist=["HatariGenerator"])
|
|
generatorClass = getattr(module, "HatariGenerator")''',
|
|
65: ''' elif emulator == "mupen64plus":
|
|
module = __import__("configgen.generators.mupen.mupenGenerator", fromlist=["MupenGenerator"])
|
|
generatorClass = getattr(module, "MupenGenerator")''',
|
|
80: ''' elif emulator == "pcsx2":
|
|
module = __import__("configgen.generators.pcsx2.pcsx2Generator", fromlist=["Pcsx2Generator"])
|
|
generatorClass = getattr(module, "Pcsx2Generator")''',
|
|
125: ''' elif emulator == "xemu":
|
|
module = __import__("configgen.generators.xemu.xemuGenerator", fromlist=["XemuGenerator"])
|
|
generatorClass = getattr(module, "XemuGenerator")''',
|
|
131: ''' else:
|
|
print("Missing generator for {}".format(emulator))
|
|
raise ValueError
|
|
pass
|
|
|
|
return generatorClass()''',
|
|
}
|
|
|
|
# libretroRetroarch.py:1-11, 251-253, 421-529
|
|
RETROARCH_BLOCKS = {
|
|
1: '''from typing import List
|
|
|
|
import configgen.recalboxFiles as recalboxFiles
|
|
from configgen.Emulator import Emulator
|
|
from configgen.controllers.controller import ControllerPerPlayer
|
|
from configgen.settings.keyValueSettings import keyValueSettings
|
|
from configgen.generators.libretro.libretroChallengeLock import ChallengeLock
|
|
from configgen.utils.architecture import Architecture
|
|
|
|
|
|
class LibretroRetroarch:''',
|
|
251: ''' # Fill retroarch configuration
|
|
def fillRetroarchConfiguration(self):
|
|
settings: keyValueSettings = self.settings''',
|
|
421: '''
|
|
# Core assets path for dolphin only
|
|
if self.system.Core == "dolphin":
|
|
settings.setString("core_assets_directory", "/recalbox/share/bios/dolphin-emu/")
|
|
else:
|
|
settings.removeOption("core_assets_directory")
|
|
|
|
# Audio buffer (naomi2 anti-crackle, HDMI + CRT)
|
|
self.manage_audio_buffer(settings)
|
|
|
|
# Use bios in directories
|
|
# Most of the case are by core name
|
|
match self.system.Core:
|
|
case 'a5200':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/atari5200")
|
|
case 'bsnes':
|
|
if self.system.Name == 'gb' or self.system.Name == 'gbc':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/sgb")
|
|
elif self.system.Name == 'satellaview':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/satellaview")
|
|
else:
|
|
settings.setString("system_directory", recalboxFiles.BIOS)
|
|
case 'bsneshd':
|
|
if self.system.Name == 'gb' or self.system.Name == 'gbc':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/sgb")
|
|
elif self.system.Name == 'satellaview':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/satellaview")
|
|
else:
|
|
settings.setString("system_directory", recalboxFiles.BIOS)
|
|
case 'cdi2015':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/cdi")
|
|
case 'dolphin':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/gamecube")
|
|
case 'emuscv':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/scv")
|
|
case 'fmsx':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/msx")
|
|
case 'freechaf':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/channelf")
|
|
case 'freeintv':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/intellivision")
|
|
case 'gambatte':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/" + self.system.Name)
|
|
case 'gearsystem':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/" + self.system.Name)
|
|
case 'genesisplusgx':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/" + self.system.Name)
|
|
case 'genesisplusgxwide':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/" + self.system.Name)
|
|
case 'geolith':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/geolith")
|
|
case 'gpsp':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/gba")
|
|
case 'mednafen_pce_fast':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/" + self.system.Name)
|
|
case 'mednafen_pcfx':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/pcfx")
|
|
case 'mednafen_supergrafx':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/" + self.system.Name)
|
|
case 'geargrafx':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/pcenginecd")
|
|
case 'mesen_s':
|
|
if self.system.SuperGameBoy:
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/sgb")
|
|
else:
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/" + self.system.Name)
|
|
case 'minivmac':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/macintosh")
|
|
case 'mu':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/palm")
|
|
case 'o2em':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/o2em")
|
|
case 'opera':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/3do")
|
|
case 'picodrive':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/" + self.system.Name)
|
|
case 'pokemini':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/pokemini")
|
|
case 'prosystem':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/atari7800")
|
|
case 'sameboy':
|
|
if self.system.SuperGameBoy:
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/sgb")
|
|
else:
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/" + self.system.Name)
|
|
case 'snes9x':
|
|
if self.system.Name == 'satellaview':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/satellaview")
|
|
elif self.system.Name == 'sufami':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/sufami")
|
|
else:
|
|
settings.setString("system_directory", recalboxFiles.BIOS)
|
|
case _:
|
|
settings.setString("system_directory", recalboxFiles.BIOS)
|
|
|
|
# We have a few to use by system name - when there are multiple cores with same bios
|
|
amigaSystems = ["amiga600", "amiga1200", "amigacdtv", "amigacd32"]
|
|
if self.system.Name in amigaSystems:
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/amiga/bios")
|
|
elif self.system.Name == 'fds':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/fds")
|
|
elif self.system.Name == 'lynx':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/lynx")
|
|
elif self.system.Name == 'nds':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/nds")
|
|
elif self.system.Name == 'saturn':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/saturn")
|
|
elif self.system.Name == 'stv':
|
|
settings.setString("system_directory", recalboxFiles.BIOS + "/stv")''',
|
|
}
|
|
|
|
# libretroCores.py:1-17, 165-298, 348-355, 492-504, 633-705
|
|
CORES_BLOCKS = {
|
|
1: '''from typing import Dict
|
|
from configgen.Emulator import Emulator
|
|
from configgen.crt.CRTTypes import CRTAdapter
|
|
from configgen.settings.keyValueSettings import keyValueSettings
|
|
from configgen.controllers.controller import ControllerPerPlayer
|
|
from configgen.utils.architecture import Architecture
|
|
|
|
|
|
def _jvsCabFamily() -> str:
|
|
try:
|
|
with open("/sys/devices/platform/recalboxjvs/cab_family") as f:
|
|
return f.read().strip()
|
|
except OSError:
|
|
return ""
|
|
|
|
|
|
class LibretroCores:''',
|
|
165: r''' def configureAtariSTNew(self, coreSettings: keyValueSettings):
|
|
# Machine list as defined in HatariB core
|
|
MACHINE_UNKNOWN = -1
|
|
MACHINE_ST = 0
|
|
MACHINE_MEGA_ST = 1
|
|
MACHINE_STE = 2
|
|
MACHINE_MEGA_STE = 3
|
|
MACHINE_TT = 4
|
|
MACHINE_FALCON = 5
|
|
|
|
# Try identify machine folder
|
|
machines: dict[str, int] = \
|
|
{
|
|
"atari-st": MACHINE_ST,
|
|
"/st/": MACHINE_ST,
|
|
"atarimegast": MACHINE_MEGA_ST,
|
|
"atari-megast": MACHINE_MEGA_ST,
|
|
"atari-mega-st": MACHINE_MEGA_ST,
|
|
"megast": MACHINE_MEGA_ST,
|
|
"mega-st": MACHINE_MEGA_ST,
|
|
"atariste": MACHINE_STE,
|
|
"atari-ste": MACHINE_STE,
|
|
"/ste/": MACHINE_STE,
|
|
"atarimegaste": MACHINE_MEGA_STE,
|
|
"atari-megaste": MACHINE_MEGA_STE,
|
|
"atari-mega-ste": MACHINE_MEGA_STE,
|
|
"megaste": MACHINE_MEGA_STE,
|
|
"mega-ste": MACHINE_MEGA_STE,
|
|
"ataritt": MACHINE_TT,
|
|
"atari-tt": MACHINE_TT,
|
|
"/tt/": MACHINE_TT,
|
|
"falcon": MACHINE_FALCON,
|
|
"atari-falcon": MACHINE_FALCON,
|
|
}
|
|
defaultMachine: int = MACHINE_UNKNOWN
|
|
romLower: str = self.rom.lower()
|
|
for key in machines:
|
|
if key in romLower:
|
|
defaultMachine: int = machines[key]
|
|
break
|
|
if not defaultMachine == MACHINE_UNKNOWN:
|
|
coreSettings.setInt("hatarib_machine", defaultMachine)
|
|
print("MACHINE: {}".format(defaultMachine))
|
|
else:
|
|
defaultMachine = coreSettings.getInt("hatarib_machine", MACHINE_ST)
|
|
|
|
# Select bios
|
|
subdir: str = "atarist"
|
|
import os
|
|
biosFile = os.path.join(subdir, "st.img")
|
|
# ST/MEGAST use both st.img
|
|
if defaultMachine == MACHINE_FALCON : biosFile = os.path.join(subdir, "falcon.img")
|
|
elif defaultMachine == MACHINE_MEGA_STE: biosFile = os.path.join(subdir, "megaste.img")
|
|
elif defaultMachine == MACHINE_TT : biosFile = os.path.join(subdir, "tt.img")
|
|
elif defaultMachine == MACHINE_STE : biosFile = os.path.join(subdir, "ste.img")
|
|
import configgen.recalboxFiles as recalboxFiles
|
|
if not os.path.exists(os.path.join(recalboxFiles.BIOS, biosFile)):
|
|
biosFile = os.path.join(subdir, "tos.img")
|
|
if not os.path.exists(os.path.join(recalboxFiles.BIOS, biosFile)):
|
|
biosFile = "tos.img"
|
|
if not os.path.exists(os.path.join(recalboxFiles.BIOS, biosFile)):
|
|
biosFile = "<etos1024k>"
|
|
if "emutos" in self.rom:
|
|
biosFile = os.path.join(recalboxFiles.BIOS, subdir, "<etos1024k>")
|
|
coreSettings.setString("hatarib_tos", str(biosFile))
|
|
print("BIOS : {}".format(biosFile))
|
|
|
|
def configureAtariST(self, _):
|
|
# Machines as defined in old Hatari core
|
|
MACHINE_UNKNOWN = -1
|
|
MACHINE_ST = 0
|
|
MACHINE_STE = 1
|
|
MACHINE_TT = 2
|
|
MACHINE_FALCON = 3,
|
|
MACHINE_MEGA_STE = 4
|
|
|
|
# Load config
|
|
from configgen.settings.iniSettings import IniSettings
|
|
import configgen.recalboxFiles as recalboxFiles
|
|
atariStSettings = IniSettings(recalboxFiles.hatariCustomConfig)
|
|
atariStSettings.loadFile(True)
|
|
|
|
# Auto set machine
|
|
from typing import Dict
|
|
# Try identify machine folder
|
|
machines: Dict[str, int] = \
|
|
{
|
|
"atari-st": MACHINE_ST,
|
|
"/st/": MACHINE_ST,
|
|
"atariste": MACHINE_STE,
|
|
"atari-ste": MACHINE_STE,
|
|
"/ste/": MACHINE_STE,
|
|
"ataritt": MACHINE_TT,
|
|
"atari-tt": MACHINE_TT,
|
|
"/tt/": MACHINE_TT,
|
|
"atarimegaste": MACHINE_MEGA_STE,
|
|
"atari-megaste": MACHINE_MEGA_STE,
|
|
"atari-mega-ste": MACHINE_MEGA_STE,
|
|
"megaste": MACHINE_MEGA_STE,
|
|
"mega-ste": MACHINE_MEGA_STE,
|
|
"falcon": MACHINE_FALCON,
|
|
"atari-falcon": MACHINE_FALCON,
|
|
}
|
|
defaultMachine: int = MACHINE_UNKNOWN
|
|
romLower: str = self.rom.lower()
|
|
for key in machines:
|
|
if key in romLower:
|
|
defaultMachine: int = machines[key]
|
|
break
|
|
if not defaultMachine == MACHINE_UNKNOWN:
|
|
print("MACHINE: {}".format(defaultMachine))
|
|
atariStSettings.setString("System", "nMachineType", str(defaultMachine))
|
|
|
|
# Select bios
|
|
import os
|
|
subdir: str = "atarist"
|
|
biosPath = os.path.join(recalboxFiles.BIOS, subdir, "st.img")
|
|
if defaultMachine == MACHINE_STE : biosPath = os.path.join(recalboxFiles.BIOS, subdir, "ste.img")
|
|
elif defaultMachine == MACHINE_TT : biosPath = os.path.join(recalboxFiles.BIOS, subdir, "tt.img")
|
|
elif defaultMachine == MACHINE_MEGA_STE: biosPath = os.path.join(recalboxFiles.BIOS, subdir, "megaste.img")
|
|
elif defaultMachine == MACHINE_FALCON : biosPath = os.path.join(recalboxFiles.BIOS, subdir, "falcon.img")
|
|
if not os.path.exists(biosPath):
|
|
biosPath = os.path.join(recalboxFiles.BIOS, subdir, "tos.img")
|
|
if not os.path.exists(biosPath):
|
|
biosPath = os.path.join(recalboxFiles.BIOS, "tos.img")
|
|
if not os.path.exists(biosPath):
|
|
biosPath = os.path.join(recalboxFiles.BIOS, subdir, "emutos.img")
|
|
if "emutos" in self.rom:
|
|
biosPath = os.path.join(recalboxFiles.BIOS, subdir, "emutos.img")
|
|
atariStSettings.setString("ROM", "szTosImageFileName", str(biosPath))
|
|
print("BIOS : {}".format(biosPath))
|
|
|
|
# Save config
|
|
atariStSettings.saveFile()''',
|
|
348: '''
|
|
@staticmethod
|
|
def configureOdyssey2(coreSettings: keyValueSettings):
|
|
coreSettings.setString("o2em_bios", '"o2rom.bin"')
|
|
|
|
@staticmethod
|
|
def configureVideoPacPlus(coreSettings: keyValueSettings):
|
|
coreSettings.setString("o2em_bios", '"g7400.bin"')''',
|
|
492: ''' def configureDolphin(self, coreSettings: keyValueSettings) -> None:
|
|
coreSettings.setString("dolphin_emulator_speed", '"1.0"')
|
|
coreSettings.setString("dolphin_cpu_clock_rate", '"1.0"')
|
|
|
|
ipl0 = "/recalbox/share/bios/gamecube/EUR/IPL.bin"
|
|
ipl1 = "/recalbox/share/bios/gamecube/JP/IPL.bin"
|
|
ipl2 = "/recalbox/share/bios/gamecube/USA/IPL.bin"
|
|
|
|
import os.path
|
|
if os.path.exists(ipl0) or os.path.exists(ipl1) or os.path.exists(ipl2):
|
|
coreSettings.setString("dolphin_skip_gc_bios", '"disabled"')
|
|
else:
|
|
coreSettings.setString("dolphin_skip_gc_bios", '"enabled"')''',
|
|
633: r''' # Fill cores configuration
|
|
def fillCoresConfiguration(self):
|
|
settings = self.settings
|
|
|
|
# Specific configuration handlers per core
|
|
specificCoreHandlers =\
|
|
{
|
|
"bluemsx" : LibretroCores.configureBlueMSX,
|
|
"dolphin": self.configureDolphin,
|
|
"mame2003_plus": LibretroCores.configureMAME2003plus,
|
|
"mame": self.configureMAME,
|
|
"mame0258": self.configureMAME,
|
|
"mame0278": self.configureMAME,
|
|
"mupen64plus_nx" : LibretroCores.configureMUPEN64,
|
|
"opera": LibretroCores.configureOpera,
|
|
"parallel_n64": LibretroCores.configurePARALLELN64,
|
|
"np2kai" : LibretroCores.configureNPKAI,
|
|
"picodrive" : self.configurePicodrive,
|
|
"swanstation" : self.configureSwanstation,
|
|
"dosbox_pure" : LibretroCores.configureDosBoxPure,
|
|
"flycast": self.configureFlycast,
|
|
"flycast-next": self.configureFlycast,
|
|
"fbneo": self.configureFBNeo,
|
|
"scummvm": self.configureScummvm,
|
|
"hatari": self.configureAtariST,
|
|
"hatarib": self.configureAtariSTNew,
|
|
"melonds": LibretroCores.configureMelonDS,
|
|
"desmume": LibretroCores.configureDesmume,
|
|
"ppsspp": self.configurePPSSPP,
|
|
"pcsx_rearmed": self.configurePCSXRearmed,
|
|
"mednafen_psx_hw": self.configureBeetlePSX,
|
|
"mednafen_psx": self.configureBeetlePSX,
|
|
"gambatte": self.configureGambatte,
|
|
"mednafen_stv" : self.configureSTV,
|
|
"eightyone": self.configureEightyOne,
|
|
"tgbdual": self.configureTGBDual,
|
|
}
|
|
|
|
# Get handler and execute
|
|
core = self.system.Core
|
|
if core in specificCoreHandlers:
|
|
function = specificCoreHandlers.get(core)
|
|
function(settings)
|
|
|
|
# Specific configuration handlers per system
|
|
specificSystemHandlers =\
|
|
{
|
|
"amiga600" : LibretroCores.configureAmiga600,
|
|
"amiga1200" : LibretroCores.configureAmiga1200,
|
|
"amigacdtv" : LibretroCores.configureAmigaCDTV,
|
|
"amigacd32" : LibretroCores.configureAmigaCD32,
|
|
"atari5200" : LibretroCores.configureAtari5200,
|
|
"atari800" : LibretroCores.configureAtari800,
|
|
"amstradcpc" : LibretroCores.configureAmstradCPC,
|
|
"gx4000" : LibretroCores.configureAmstradGX4000,
|
|
"nds" : self.configureNDS,
|
|
"spectravideo" : LibretroCores.configureSpectravideo,
|
|
"64dd" : LibretroCores.configure64DD,
|
|
"msx1" : LibretroCores.configureMsx1,
|
|
"msx2" : LibretroCores.configureMsx2,
|
|
"msxturbor" : LibretroCores.configureMsxTurboR,
|
|
"pc98" : LibretroCores.configureNPKAI,
|
|
"odyssey2" : LibretroCores.configureOdyssey2,
|
|
"videopacplus" : LibretroCores.configureVideoPacPlus,
|
|
"sg1000" : LibretroCores.configureSg1000,
|
|
"naomi2" : self.configureNaomi2,
|
|
"saturn" : self.configureSaturn,
|
|
}
|
|
|
|
# Get handler and execute
|
|
if self.system.Name in specificSystemHandlers:
|
|
function = specificSystemHandlers.get(self.system.Name)
|
|
function(settings)''',
|
|
}
|
|
|
|
# libretroGenerator.py:7, 22, 814-815, 876-883
|
|
LIBRETRO_GENERATOR_BLOCKS = {
|
|
7: '''import configgen.recalboxFiles as recalboxFiles''',
|
|
22: '''class LibretroGenerator(Generator):''',
|
|
814: ''' @staticmethod
|
|
def buildRomArguments(rom: str, core: str, verbose: bool, demo: bool) -> List[str]:''',
|
|
876: ''' # macintosh
|
|
if core == "minivmac":
|
|
with open(recalboxFiles.minivmacRomFile, 'w') as f:
|
|
f.write('"Mini vMac" "' + recalboxFiles.minivmacOsFile + '" "' + rom + '"')
|
|
|
|
rom = [recalboxFiles.minivmacRomFile]
|
|
return rom
|
|
''',
|
|
}
|
|
|
|
# hatariGenerator.py:1-14, 103-167
|
|
HATARI_BLOCKS = {
|
|
1: '''import os
|
|
from typing import List, Dict
|
|
|
|
from configgen.Command import Command
|
|
from configgen.Emulator import Emulator
|
|
from configgen.controllers.controller import ControllerPerPlayer
|
|
from configgen.generators.Generator import Generator
|
|
import configgen.recalboxFiles as recalboxFiles
|
|
from configgen.settings.iniSettings import IniSettings
|
|
import configgen.settings.configOverriding as configOverriding
|
|
from configgen.settings.keyValueSettings import keyValueSettings
|
|
|
|
|
|
class HatariGenerator(Generator):''',
|
|
103: r'''
|
|
@staticmethod
|
|
def SetMachineAndBios(config: IniSettings, rom: str):
|
|
# Machine list
|
|
MACHINE_UNKNOWN = -1
|
|
MACHINE_ST = 0
|
|
MACHINE_MEGA_ST = 1
|
|
MACHINE_STE = 2
|
|
MACHINE_MEGA_STE = 3
|
|
MACHINE_TT = 4
|
|
MACHINE_FALCON = 5
|
|
|
|
# Try identify machine folder
|
|
machines: Dict[str, int] = \
|
|
{
|
|
"atari-st": MACHINE_ST,
|
|
"/st/": MACHINE_ST,
|
|
"atarimegast": MACHINE_MEGA_ST,
|
|
"atari-megast": MACHINE_MEGA_ST,
|
|
"atari-mega-st": MACHINE_MEGA_ST,
|
|
"megast": MACHINE_MEGA_ST,
|
|
"mega-st": MACHINE_MEGA_ST,
|
|
"atariste": MACHINE_STE,
|
|
"atari-ste": MACHINE_STE,
|
|
"/ste/": MACHINE_STE,
|
|
"atarimegaste": MACHINE_MEGA_STE,
|
|
"atari-megaste": MACHINE_MEGA_STE,
|
|
"atari-mega-ste": MACHINE_MEGA_STE,
|
|
"megaste": MACHINE_MEGA_STE,
|
|
"mega-ste": MACHINE_MEGA_STE,
|
|
"ataritt": MACHINE_TT,
|
|
"atari-tt": MACHINE_TT,
|
|
"/tt/": MACHINE_TT,
|
|
"falcon": MACHINE_FALCON,
|
|
"atari-falcon": MACHINE_FALCON,
|
|
}
|
|
defaultMachine: int = MACHINE_UNKNOWN
|
|
romLower: str = rom.lower()
|
|
for key in machines:
|
|
if key in romLower:
|
|
defaultMachine: int = machines[key]
|
|
break
|
|
if not defaultMachine == MACHINE_UNKNOWN:
|
|
config.setInt("System", "nMachineType", defaultMachine)
|
|
print("MACHINE: {}".format(defaultMachine))
|
|
else:
|
|
defaultMachine = coreSettings.getInt("hatarib_machine", MACHINE_ST)
|
|
|
|
# Select bios
|
|
subdir: str = "atarist"
|
|
biosFile = os.path.join(recalboxFiles.BIOS, subdir, "st.img")
|
|
# ST/MEGAST use both st.img
|
|
if defaultMachine == MACHINE_FALCON : biosFile = os.path.join(recalboxFiles.BIOS, subdir, "falcon.img")
|
|
elif defaultMachine == MACHINE_MEGA_STE: biosFile = os.path.join(recalboxFiles.BIOS, subdir, "megaste.img")
|
|
elif defaultMachine == MACHINE_TT : biosFile = os.path.join(recalboxFiles.BIOS, subdir, "tt.img")
|
|
elif defaultMachine == MACHINE_STE : biosFile = os.path.join(recalboxFiles.BIOS, subdir, "ste.img")
|
|
if not os.path.exists(biosFile):
|
|
biosFile = os.path.join(recalboxFiles.BIOS, subdir, "tos.img")
|
|
if not os.path.exists(biosFile):
|
|
biosFile = os.path.join(recalboxFiles.BIOS, "tos.img")
|
|
if not os.path.exists(biosFile):
|
|
biosFile = os.path.join(recalboxFiles.BIOS, subdir, "emutos.img")
|
|
if "emutos" in rom:
|
|
biosFile = os.path.join(recalboxFiles.BIOS, subdir, "emutos.img")
|
|
config.setString("ROM", "szTosImageFileName", str(biosFile))''',
|
|
}
|
|
|
|
# gsplusGenerator.py:1-11, 93-148
|
|
GSPLUS_BLOCKS = {
|
|
1: '''from typing import List
|
|
|
|
from configgen.Command import Command
|
|
import configgen.recalboxFiles as recalboxFiles
|
|
from configgen.Emulator import Emulator
|
|
from configgen.generators.Generator import Generator
|
|
from configgen.settings.keyValueSettings import keyValueSettings
|
|
from configgen.controllers.controller import Controller, InputItem, ControllerPerPlayer
|
|
|
|
|
|
class GSplusGenerator(Generator):''',
|
|
93: ''' def generate(self, system: Emulator, playersControllers: ControllerPerPlayer, recalboxOptions: keyValueSettings, args):
|
|
"""
|
|
Load, override keys and save back emulator's configuration file
|
|
This way, any modification is kept accross emulator launches
|
|
"""
|
|
|
|
slot: int = -1
|
|
if ".gsplus" in args.rom:
|
|
settings = keyValueSettings(args.rom, True)
|
|
settings.loadFile(True)
|
|
settings.changeSettingsFile(recalboxFiles.gsplusConfig)
|
|
|
|
# Adjust BIOS path
|
|
import os
|
|
if settings.getString("g_cfg_rom_path", "") == "%1":
|
|
settings.setString("g_cfg_rom_path", os.path.join(recalboxFiles.BIOS, "apple2gs1.rom"))
|
|
elif settings.getString("g_cfg_rom_path", "") == "%3":
|
|
settings.setString("g_cfg_rom_path", os.path.join(recalboxFiles.BIOS, "apple2gs3.rom"))
|
|
|
|
# Adjust rom path
|
|
folder = os.path.dirname(args.rom)
|
|
for i in range(1, 33):
|
|
if i < 3:
|
|
if settings.hasOption("s5d{}".format(i)) and settings.getString("s5d{}".format(i), "") != "":
|
|
settings.setString("s5d{}".format(i), os.path.join(folder, settings.getString("s5d{}".format(i), "")))
|
|
if settings.hasOption("s6d{}".format(i)) and settings.getString("s6d{}".format(i), "") != "":
|
|
settings.setString("s6d{}".format(i), os.path.join(folder, settings.getString("s6d{}".format(i), "")))
|
|
if settings.hasOption("s7d{}".format(i)) and settings.getString("s7d{}".format(i), "") != "":
|
|
settings.setString("s7d{}".format(i), os.path.join(folder, settings.getString("s7d{}".format(i), "")))
|
|
|
|
else:
|
|
# Load config file
|
|
settings = keyValueSettings(recalboxFiles.gsplusConfig, True)
|
|
settings.loadFile(True)
|
|
|
|
# Reset drive slots
|
|
for i in range(1, 33):
|
|
if i < 3:
|
|
if settings.hasOption("s5d{}".format(i)):
|
|
settings.setString("s5d{}".format(i), "")
|
|
if settings.hasOption("s6d{}".format(i)):
|
|
settings.setString("s6d{}".format(i), "")
|
|
if settings.hasOption("s7d{}".format(i)):
|
|
settings.setString("s7d{}".format(i), "")
|
|
|
|
# Reset bios
|
|
import os
|
|
bios = os.path.join(recalboxFiles.BIOS, "apple2gs1.rom")
|
|
if os.path.exists(bios):
|
|
settings.setString("g_cfg_rom_path", bios)
|
|
else:
|
|
bios = os.path.join(recalboxFiles.BIOS, "apple2gs3.rom")
|
|
if os.path.exists(bios):
|
|
settings.setString("g_cfg_rom_path", bios)
|
|
else:
|
|
settings.removeOption("g_cfg_rom_path")''',
|
|
}
|
|
|
|
# amiberryKickstarts.py:1-151
|
|
AMIBERRY_KICKSTARTS_BLOCKS = {
|
|
1: r'''#!/usr/bin/env python
|
|
import os
|
|
|
|
import configgen.recalboxFiles as recalboxFiles
|
|
from configgen.generators.amiberry.amiberryRomType import RomType
|
|
from configgen.generators.amiberry.amiberrySubSystems import SubSystems
|
|
|
|
# Kickstarts live in the amiga subfolder (see es_bios.xml and amiberry rom_path)
|
|
def AmigaBios(name: str) -> str:
|
|
return os.path.join(recalboxFiles.BIOS, "amiga", "bios", name)
|
|
|
|
|
|
class KickstartManager:
|
|
|
|
BIOS_LIST = \
|
|
{
|
|
# Kickstart v1.2 rev 33.180 (1986)(Commodore)(A500-A1000-A2000).rom
|
|
"kick33180.A500": (256, "KS ROM v1.2 (A500,A1000,A2000) rev 33.180 (256k) [315093-01]"),
|
|
# Kickstart v1.3 rev 34.5 (1987)(Commodore)(A500-A1000-A2000-CDTV).rom
|
|
"kick34005.A500": (256, "KS ROM v1.3 (A500,A1000,A2000) rev 34.5 (256k) [315093-02]"),
|
|
# Kickstart v2.04 rev 37.175 (1991)(Commodore)(A500+).rom
|
|
"kick37175.A500": (256, "KS ROM v2.04 (A500+) rev 37.175 (512k) [390979-01]"),
|
|
# Kickstart v3.0 rev 39.106 (1992)(Commodore)(A1200)[!].rom
|
|
"kick39106.A1200": (256, "KS ROM v3.0 (A1200) rev 39.106 (512k) [391523-01/391524-01]"),
|
|
# Kickstart v3.1 rev 40.63 (1993)(Commodore)(A500-A600-A2000)[!].rom
|
|
"kick40063.A600": (256, "KS ROM v3.1 (A600) rev 40.63 (512k)"),
|
|
# Kickstart v3.1 rev 40.68 (1993)(Commodore)(A1200).rom
|
|
"kick40068.A1200": (256, "KS ROM v3.1 (A1200) rev 40.68 (512k) [391773-01/391774-01]"),
|
|
# Kickstart v3.1 rev 40.68 (1993)(Commodore)(A4000).rom
|
|
"kick40068.A4000": (256, "KS ROM v3.1 (A4000) rev 40.68 (512k)"),
|
|
|
|
# Kickstart v1.3 rev 34.5 (1987)(Commodore)(A500-A2000-CDTV)[!].rom
|
|
"kick34005.CDTV": (256, "CDTV Extended ROM rev 34.5 (256k)"),
|
|
|
|
# Kickstart v3.1 rev 40.60 (1993)(Commodore)(CD32).rom
|
|
"kick40060.CD32": (512, "CD32 KS ROM v3.1 rev 40.60 (512k)"),
|
|
# CD32 Extended-ROM rev 40.60 (1993)(Commodore)(CD32).rom 87746be2
|
|
"kick40060.CD32.ext": (1024, "CD32 extended ROM rev 40.60 (512k)"),
|
|
|
|
# AROS Kickstart 2019
|
|
"kick02019.AROS": (256, "KS AROS 2019 Part#1 (512k)"),
|
|
# AROS Extended Kickstart 2019
|
|
"kick02019.AROS.ext": (256, "KS AROS 2019 Part#2 (512k)"),
|
|
}
|
|
|
|
BIOS_PER_SYSTEM = \
|
|
{
|
|
SubSystems.A600:
|
|
[
|
|
( # Use the highest kickstart with advanced hardware
|
|
[RomType.WHDL, RomType.HDF, RomType.PACKAGE, RomType.CDROM, RomType.HDDFS],
|
|
["kick37175.A500", "kick34005.A500", "kick33180.A500"]
|
|
),
|
|
( # Use kickstarts 1.3/1.2 to maximize compatibility when using disks
|
|
[RomType.DISK],
|
|
["kick34005.A500", "kick33180.A500"]
|
|
),
|
|
],
|
|
SubSystems.A1200:
|
|
[
|
|
( # Use kickstart 3.1 first, then 3.0. Fallback on A4000 kickstart
|
|
[RomType.WHDL, RomType.HDF, RomType.PACKAGE, RomType.CDROM, RomType.HDDFS, RomType.DISK],
|
|
["kick40068.A1200", "kick39106.A1200", "kick40068.A4000"]
|
|
),
|
|
],
|
|
SubSystems.CD32:
|
|
[
|
|
( # No choice for the CD32
|
|
[RomType.CDROM],
|
|
["kick40060.CD32|kick40060.CD32.ext"]
|
|
),
|
|
],
|
|
SubSystems.CDTV:
|
|
[
|
|
( # No choice for the CD32
|
|
[RomType.CDROM],
|
|
["kick34005.A500|kick34005.CDTV"]
|
|
),
|
|
],
|
|
}
|
|
|
|
def __init__(self):
|
|
self.rom: str = ""
|
|
self.ext: str = ""
|
|
|
|
@staticmethod
|
|
def GenerateWHDSymLinks(destinationfolder: str):
|
|
for bios in KickstartManager.BIOS_LIST:
|
|
biosPath = AmigaBios(bios)
|
|
biosLink = os.path.join(destinationfolder, bios)
|
|
if not os.path.exists(biosLink) and os.path.exists(biosPath):
|
|
os.symlink(biosPath, biosLink)
|
|
|
|
def GetKickstartsFor(self, subsystem: SubSystems, romtype: RomType):
|
|
# Select best available kickstart
|
|
biosFile = None
|
|
extFile = None
|
|
needExt = False
|
|
tuppleArray = KickstartManager.BIOS_PER_SYSTEM[subsystem]
|
|
for tupple in tuppleArray:
|
|
romTypes, biosList = tupple
|
|
if romtype in romTypes:
|
|
for biosCouple in biosList:
|
|
bios = biosCouple
|
|
ext = None
|
|
if '|' in biosCouple:
|
|
bios, ext = biosCouple.split('|', 2)
|
|
needExt = ext is not None
|
|
biosPath = AmigaBios(bios)
|
|
if os.path.exists(biosPath):
|
|
biosFile = biosPath
|
|
if needExt:
|
|
extPath = AmigaBios(ext)
|
|
if os.path.exists(extPath):
|
|
extFile = extPath
|
|
if biosFile is not None:
|
|
break
|
|
|
|
# Fallbacks
|
|
if subsystem in SubSystems.COMPUTERS:
|
|
# Fallback to AROS 2019?
|
|
if biosFile is None:
|
|
arosBios = AmigaBios("kick02019.AROS")
|
|
arosExt = AmigaBios("kick02019.AROS.ext")
|
|
if os.path.exists(arosBios) and os.path.exists(arosExt):
|
|
biosFile = arosBios
|
|
extFile = arosExt
|
|
|
|
# Fallback to internal Aros bios
|
|
if biosFile is None:
|
|
self.rom = ":AROS"
|
|
return
|
|
|
|
# Last check
|
|
if biosFile is None:
|
|
raise Exception("No BIOS found")
|
|
# If an extened bios is required, it's not worth going further w/o it
|
|
if extFile is None and needExt:
|
|
raise Exception("Extended BIOS not found")
|
|
|
|
self.rom = biosFile if biosFile is not None else ""
|
|
self.ext = extFile if extFile is not None else ""
|
|
|
|
@property
|
|
def GetBIOS(self) -> str: return self.rom
|
|
|
|
@property
|
|
def NeedExtendedBIOS(self) -> bool: return len(self.ext) != 0
|
|
|
|
@property
|
|
def GetExtendedBIOS(self) -> str: return self.ext''',
|
|
}
|
|
|
|
# amiberrySubSystems.py:1-13
|
|
AMIBERRY_SUBSYSTEMS_BLOCKS = {
|
|
1: '''#!/usr/bin/env python
|
|
|
|
|
|
class SubSystems:
|
|
UNKNOWN = "unknown"
|
|
A600 = "amiga600"
|
|
A1200 = "amiga1200"
|
|
CDTV = "amigacdtv"
|
|
CD32 = "amigacd32"
|
|
|
|
COMPUTERS = ( A600, A1200 )
|
|
CONSOLES = ( CDTV, CD32 )
|
|
HAVEMOUSE = ( A600, A1200, CDTV, CD32 )''',
|
|
}
|
|
|
|
# amiberryConfig.py:1-9, 337-345
|
|
AMIBERRY_CONFIG_BLOCKS = {
|
|
1: '''#!/usr/bin/env python
|
|
from typing import List
|
|
|
|
from configgen.generators.amiberry.amiberrySubSystems import SubSystems
|
|
from configgen.controllers.controller import ControllerPerPlayer
|
|
from configgen.generators.amiberry.amiberryRomType import RomType
|
|
|
|
|
|
class ConfigGenerator:''',
|
|
337: ''' def SetKickstarts(self, subsystem: SubSystems, romtype: RomType):
|
|
from configgen.generators.amiberry.amiberryKickstarts import KickstartManager
|
|
manager = KickstartManager()
|
|
manager.GetKickstartsFor(subsystem, romtype)
|
|
kickstart = manager.GetBIOS
|
|
self.settings.setString("kickstart_rom_file", kickstart)
|
|
if manager.NeedExtendedBIOS:
|
|
extended = manager.GetExtendedBIOS
|
|
self.settings.setString("kickstart_ext_rom_file", extended)''',
|
|
}
|
|
|
|
# pcsx2Generator.py:1-166
|
|
PCSX2_BLOCKS = {
|
|
1: r'''#!/usr/bin/env python
|
|
from typing import Dict
|
|
|
|
from configgen.Command import Command
|
|
from configgen.Emulator import Emulator
|
|
from configgen.controllers.controller import ControllerPerPlayer
|
|
from configgen.emulatorlauncher import recalboxFiles
|
|
from configgen.generators.Generator import Generator
|
|
from configgen.settings.iniSettings import IniSettings
|
|
from configgen.settings.keyValueSettings import keyValueSettings
|
|
|
|
class Pcsx2Generator(Generator):
|
|
|
|
pcsx2RootFolder = recalboxFiles.HOME + '/.config/PCSX2'
|
|
pcsx2ConfigFileReg = pcsx2RootFolder + '/PCSX2-reg.ini'
|
|
pcsx2ConfigFileGSdx = pcsx2RootFolder + '/GSdx.ini'
|
|
pcsx2ConfigFilePAD = pcsx2RootFolder + '/PAD.ini'
|
|
pcsx2ConfigFilePCSX2_ui = pcsx2RootFolder + '/PCSX2_ui.ini'
|
|
pcsx2ConfigFilePCSX2_vm = pcsx2RootFolder + '/PCSX2_vm.ini'
|
|
pcsx2ConfigFileSPU2 = pcsx2RootFolder + '/SPU2.ini'
|
|
|
|
SECTION_DEFAULT = "default"
|
|
# Core
|
|
SECTION_VM_EMUCORE_GS = "EmuCore/GS"
|
|
SECTION_VM_EMUCORE_SPEEDHACKS = "EmuCore/Speedhacks"
|
|
# UI
|
|
SECTION_UI_NONE = ""
|
|
SECTION_UI_GSWINDOWS = "GSWindow"
|
|
SECTION_UI_POPUPDISABLERS = "PopupDisablers"
|
|
SECTION_UI_FILENAMES = "Filenames"
|
|
# Sound (SPU2.ini)
|
|
SECTION_SPU2_MIXING = "MIXING"
|
|
SECTION_SPU2_OUTPUT = "OUTPUT"
|
|
SECTION_SPU2_SDL = "SDL"
|
|
|
|
# Bios names
|
|
BIOS_USA = "ps2-0230a-20080220.bin"
|
|
BIOS_EU = "ps2-0230e-20080220.bin"
|
|
BIOS_JP = "ps2-0230j-20080220.bin"
|
|
BIOS_HK = "ps2-0230h-20080220.bin"
|
|
|
|
@staticmethod
|
|
def __ConfigurePad(playersControllers: ControllerPerPlayer, system: Emulator):
|
|
with open(Pcsx2Generator.pcsx2ConfigFilePAD, "w") as f:
|
|
if system.Rumble:
|
|
f.write("options = 1\n")
|
|
f.write("mouse_sensitivity = 100\n")
|
|
f.write("ff_intensity = 32767\n")
|
|
|
|
if 1 in playersControllers: f.write("uid[0] = {}\n".format(playersControllers[1].SdlIndex))
|
|
if 2 in playersControllers: f.write("uid[1] = {}\n".format(playersControllers[2].SdlIndex))
|
|
if 1 in playersControllers: f.write("SDL2 = {}\n".format(playersControllers[1].generateSDLGameDBLine(False)))
|
|
if 2 in playersControllers: f.write("SDL2 = {}\n".format(playersControllers[2].generateSDLGameDBLine(False)))
|
|
|
|
@staticmethod
|
|
def __CheckPs2Bios(rom: str) -> str:
|
|
import os.path
|
|
|
|
def Exists(romPath: str) -> bool:
|
|
return os.path.exists(os.path.join(recalboxFiles.BIOS, "ps2", romPath))
|
|
|
|
# Try to get Redump region
|
|
lrom = rom.lower()
|
|
# EU region
|
|
if "europe" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "france" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "ireland" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "germany" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "belgium" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "italy" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "greece" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "croatia" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "denmark" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "spain" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "portugal" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "poland" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "finland" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "russia" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "austria" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "netherlands" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "switzerland" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "norway" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "sweden" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "scandinavia" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "australia" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "india" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "south africa" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
# USA region
|
|
elif "canada" in lrom and Exists(Pcsx2Generator.BIOS_USA): return Pcsx2Generator.BIOS_USA
|
|
elif "brazil" in lrom and Exists(Pcsx2Generator.BIOS_USA): return Pcsx2Generator.BIOS_USA
|
|
elif "latin america" in lrom and Exists(Pcsx2Generator.BIOS_USA): return Pcsx2Generator.BIOS_USA
|
|
# JP region
|
|
elif "japan" in lrom and Exists(Pcsx2Generator.BIOS_JP): return Pcsx2Generator.BIOS_JP
|
|
elif "korea" in lrom and Exists(Pcsx2Generator.BIOS_JP): return Pcsx2Generator.BIOS_JP
|
|
elif "asia" in lrom and Exists(Pcsx2Generator.BIOS_JP): return Pcsx2Generator.BIOS_JP
|
|
# CH
|
|
elif "china" in lrom and Exists(Pcsx2Generator.BIOS_JP): return Pcsx2Generator.BIOS_HK
|
|
elif "taiwan" in lrom and Exists(Pcsx2Generator.BIOS_JP): return Pcsx2Generator.BIOS_HK
|
|
# Shortest name at the end
|
|
elif "uk" in lrom and Exists(Pcsx2Generator.BIOS_EU): return Pcsx2Generator.BIOS_EU
|
|
elif "usa" in lrom and Exists(Pcsx2Generator.BIOS_USA): return Pcsx2Generator.BIOS_USA
|
|
|
|
# Check if PS2 bios bin in folders
|
|
for bios in (Pcsx2Generator.BIOS_USA, Pcsx2Generator.BIOS_EU, Pcsx2Generator.BIOS_JP, Pcsx2Generator.BIOS_HK):
|
|
if Exists(bios):
|
|
return bios
|
|
return ""
|
|
|
|
@staticmethod
|
|
def __ConfigureReg():
|
|
reg = keyValueSettings(Pcsx2Generator.pcsx2ConfigFileReg)
|
|
reg.loadFile(True)
|
|
|
|
# Override Reg Configuration
|
|
reg.setString("DocumentsFolderMode", "User")
|
|
reg.setString("CustomDocumentsFolder", "/usr/bin/PCSX2")
|
|
reg.setString("UseDefaultSettingsFolder", "enabled")
|
|
reg.setString("SettingsFolder", "/recalbox/share/system/.config/PCSX2")
|
|
reg.setString("Install_Dir", "/usr/bin/PCSX2")
|
|
reg.setString("RunWizard", "0")
|
|
|
|
# Save configuration
|
|
reg.saveFile()
|
|
|
|
@staticmethod
|
|
def __ConfigureCore():
|
|
import multiprocessing
|
|
NbCpu = multiprocessing.cpu_count()
|
|
|
|
# Override VM Configuration
|
|
vm = IniSettings(Pcsx2Generator.pcsx2ConfigFilePCSX2_vm, True)
|
|
vm.loadFile(True)
|
|
|
|
vm.setString(Pcsx2Generator.SECTION_VM_EMUCORE_GS, "FrameLimitEnable", "enabled")
|
|
vm.setString(Pcsx2Generator.SECTION_VM_EMUCORE_GS, "FramerateNTSC", "59.94")
|
|
vm.setInt(Pcsx2Generator.SECTION_VM_EMUCORE_GS, "FrameratePAL", 50)
|
|
vm.setString(Pcsx2Generator.SECTION_VM_EMUCORE_GS, "FrameSkipEnable", "disabled")
|
|
vm.setInt(Pcsx2Generator.SECTION_VM_EMUCORE_GS, "FramesToDraw", 2)
|
|
vm.setInt(Pcsx2Generator.SECTION_VM_EMUCORE_GS, "FramesToSkip", 2)
|
|
vm.setString(Pcsx2Generator.SECTION_VM_EMUCORE_GS, "LimitScalar", "1.00")
|
|
vm.setString(Pcsx2Generator.SECTION_VM_EMUCORE_GS, "SynchronousMTGS", "disabled")
|
|
vm.setInt(Pcsx2Generator.SECTION_VM_EMUCORE_GS, "VsyncQueueSize", 2)
|
|
vm.setString(Pcsx2Generator.SECTION_VM_EMUCORE_SPEEDHACKS, "vuThread", "enabled" if NbCpu > 3 else "disabled")
|
|
|
|
# Save configuration
|
|
vm.saveFile()
|
|
|
|
@staticmethod
|
|
def __ConfigureUI(system: Emulator, rom: str):
|
|
# Game Ratio
|
|
GAME_RATIO: Dict[str, str] = \
|
|
{
|
|
"16/9": "16:9",
|
|
"4/3": "4:3",
|
|
}
|
|
|
|
gameRatio = GAME_RATIO[system.Ratio] if system.Ratio in GAME_RATIO else "4:3"
|
|
|
|
# override UI Configuration
|
|
ui = IniSettings(Pcsx2Generator.pcsx2ConfigFilePCSX2_ui, True)
|
|
ui.defineBool("1", "0").loadFile(True)
|
|
|
|
ui.setString(Pcsx2Generator.SECTION_UI_NONE, "AskOnBoot", "disabled")
|
|
ui.setString(Pcsx2Generator.SECTION_UI_GSWINDOWS, "AspectRatio", gameRatio)
|
|
ui.setString(Pcsx2Generator.SECTION_UI_POPUPDISABLERS, "Dialog.ComponentsConfig.Overrides", "disabled,ok")
|
|
ui.setString(Pcsx2Generator.SECTION_UI_FILENAMES, "BIOS", Pcsx2Generator.__CheckPs2Bios(rom))''',
|
|
}
|
|
|
|
# dolphinGenerator.py:1-13, 113, 152-163, 228-273
|
|
DOLPHIN_BLOCKS = {
|
|
1: '''#!/usr/bin/env python3
|
|
from configgen.Command import Command
|
|
import configgen.recalboxFiles as recalboxFiles
|
|
from configgen.Emulator import Emulator
|
|
from configgen.controllers.controller import ControllerPerPlayer
|
|
from configgen.generators.Generator import Generator
|
|
from configgen.settings.keyValueSettings import keyValueSettings
|
|
from configgen.utils.Vulkan import Vulkan
|
|
from configgen.utils.architecture import Architecture
|
|
from configgen.utils.videoMode import createCrtDesktopRes, getCrtResolution, deleteCrtDesktopRes
|
|
|
|
|
|
class DolphinGenerator(Generator):''',
|
|
113: ''' SECTION_CORE = "Core"''',
|
|
152: ''' # Check if Gamecube bios IPL.bin in folders
|
|
@staticmethod
|
|
def CheckGamecubeIpl() -> str:
|
|
ipl0 = "/recalbox/share/bios/gamecube/EUR/IPL.bin"
|
|
ipl1 = "/recalbox/share/bios/gamecube/JP/IPL.bin"
|
|
ipl2 = "/recalbox/share/bios/gamecube/USA/IPL.bin"
|
|
# if os.path? return "True" set "False" for disable "SkipIpl=Video boot intro"
|
|
import os.path
|
|
if os.path.exists(ipl0) or os.path.exists(ipl1) or os.path.exists(ipl2):
|
|
return "False"
|
|
else:
|
|
return "True"''',
|
|
228: r''' def mainConfiguration(self, system: Emulator, args):
|
|
# Get Languages
|
|
language = self.GetLanguage()
|
|
systemLanguage = self.GetFrontendLanguage()
|
|
gamecubeLanguage = self.GAMECUBE_LANGUAGES[language] if language in self.GAMECUBE_LANGUAGES else 0
|
|
# Get Netplay configs
|
|
nickname = self.GetNetplay()
|
|
lobbyServer = self.NETPLAY_SERVERS[systemLanguage] if systemLanguage in self.NETPLAY_SERVERS else None
|
|
## Get if we have all IPL.bin in folders Gc bios
|
|
biosGamecube = self.CheckGamecubeIpl()
|
|
## set Gamecube Wii U adapter
|
|
padsGamecube = self.SetGamecubeWiiUAdapter()
|
|
# Get current rom path
|
|
import os
|
|
romName = os.path.basename(args.rom)
|
|
homeDir = args.rom.replace("/" + romName, "")
|
|
|
|
# Load Configuration
|
|
from configgen.settings.iniSettings import IniSettings
|
|
dolphinSettings = IniSettings(DolphinGenerator.iniFile, True)
|
|
dolphinSettings.loadFile(True) \
|
|
.defineBool('True', 'False')
|
|
|
|
# Resolution
|
|
from configgen.utils.resolutions import ResolutionParser
|
|
resolution = ResolutionParser(system.VideoMode)
|
|
if resolution.isSet and resolution.selfProcess:
|
|
dolphinSettings.setString(self.SECTION_DISPLAY, "FullscreenResolution", resolution.string)
|
|
dolphinSettings.setBool(self.SECTION_DISPLAY, "Fullscreen", True)
|
|
if Architecture().isX64:
|
|
dolphinSettings.setBool(self.SECTION_DISPLAY, "RenderToMain", True)
|
|
dolphinSettings.setBool(self.SECTION_DISPLAY, "KeepWindowOnTop", True)
|
|
# Logging
|
|
dolphinSettings.setBool(self.SECTION_DSP, "CaptureLog", args.verbose)
|
|
# Force audio backend on Pulse Audio
|
|
dolphinSettings.setString(self.SECTION_DSP, "Backend", "Pulse")
|
|
# Analytics
|
|
dolphinSettings.setBool(self.SECTION_ANALYTICS, "Enabled", True)
|
|
dolphinSettings.setBool(self.SECTION_ANALYTICS, "PermissionAsked", True)
|
|
# Bluetooth Pass-trough
|
|
dolphinSettings.setBool(self.SECTION_BTPASSTHROUGH, "Enabled", False)
|
|
# Core
|
|
dolphinSettings.setInt(self.SECTION_CORE, "SelectedLanguage", gamecubeLanguage)
|
|
dolphinSettings.setBool(self.SECTION_CORE, "WiimoteContinuousScanning", True)
|
|
dolphinSettings.setBool(self.SECTION_CORE, "WiiKeyboard", False)
|
|
dolphinSettings.setString(self.SECTION_CORE, "SkipIpl", biosGamecube)''',
|
|
}
|
|
|
|
# xemuGenerator.py:1-27, 30-60
|
|
XEMU_BLOCKS = {
|
|
1: """#!/usr/bin/env python3
|
|
import shutil
|
|
import os.path
|
|
from configgen.Command import Command
|
|
from configgen.Emulator import Emulator
|
|
from configgen.generators.Generator import Generator, ControllerPerPlayer
|
|
import configgen.recalboxFiles as recalboxFiles
|
|
from configgen.settings.keyValueSettings import keyValueSettings
|
|
from configgen.utils.Vulkan import Vulkan
|
|
from configgen.generators.xemu.xemuControllers import XemuControllers
|
|
|
|
|
|
class XemuGenerator(Generator):
|
|
SECTION_GENERAL = "general"
|
|
SECTION_SYS = "sys"
|
|
SECTION_SYS_FILES = "sys.files"
|
|
SECTION_AUDIO = "audio"
|
|
SECTION_DISPLAY = "display"
|
|
SECTION_DISPLAY_QUALITY = "display.quality"
|
|
SECTION_DISPLAY_UI = "display.ui"
|
|
SECTION_DISPLAY_WINDOW = "display.window"
|
|
SECTION_INPUT = "input"
|
|
SECTION_NET = "net"
|
|
|
|
savePath = recalboxFiles.SAVES + "/xbox"
|
|
biosPath = recalboxFiles.BIOS + "/xbox"
|
|
optionsConfigFile = recalboxFiles.CONF + '/xemu/xemu_config.ini'""",
|
|
30: ''' def mainConfiguration(self, system: Emulator, args):
|
|
# Load xemu config file defined in frontend options and get values
|
|
from configgen.settings.iniSettings import IniSettings
|
|
configSettings = IniSettings(self.optionsConfigFile, True)
|
|
configSettings.loadFile(True).defineBool('true', 'false')
|
|
|
|
skip_boot_anim = configSettings.getBool(self.SECTION_GENERAL, "skip_boot_anim", False)
|
|
show_menubar = configSettings.getBool(self.SECTION_DISPLAY_UI, "show_menubar", False)
|
|
show_notifications = configSettings.getBool(self.SECTION_DISPLAY_UI, "show_notifications", False)
|
|
fit = configSettings.getString(self.SECTION_DISPLAY_UI, "fit", "'scale'")
|
|
aspect_ratio = configSettings.getString(self.SECTION_DISPLAY_UI, "aspect_ratio", "'native'")
|
|
surface_scale = configSettings.getInt(self.SECTION_DISPLAY_QUALITY, "surface_scale", 1)
|
|
|
|
print(f"Xemu configuration: skip_boot_anim:{skip_boot_anim}")
|
|
|
|
# Load configuration
|
|
xemuSettings = IniSettings(self.xemuConfigFile, True)
|
|
xemuSettings.loadFile(True).defineBool('true', 'false')
|
|
|
|
# General
|
|
xemuSettings.setBool(self.SECTION_GENERAL, "skip_boot_anim", skip_boot_anim)
|
|
xemuSettings.setBool(self.SECTION_GENERAL, "show_welcome", False)
|
|
xemuSettings.setString(self.SECTION_GENERAL, "screenshot_dir", f"'{recalboxFiles.SCREENSHOTS}'")
|
|
|
|
# Sys
|
|
xemuSettings.setString(self.SECTION_SYS, "mem_limit", "'128'")
|
|
|
|
# Sys.files
|
|
xemuSettings.setString(self.SECTION_SYS_FILES, "flashrom_path", f"'{self.biosPath}/Complex_4627.bin'")
|
|
xemuSettings.setString(self.SECTION_SYS_FILES, "bootrom_path", f"'{self.biosPath}/mcpx_1.0.bin'")
|
|
xemuSettings.setString(self.SECTION_SYS_FILES, "hdd_path", f"'{self.savePath}/xbox_hdd.qcow2'")''',
|
|
}
|
|
|
|
# mupenConfig.py:1-5, 117-123
|
|
MUPEN_CONFIG_BLOCKS = {
|
|
1: '''#!/usr/bin/env python
|
|
import configgen.recalboxFiles as recalboxFiles
|
|
from configgen.Emulator import Emulator
|
|
from configgen.settings.iniSettings import IniSettings
|
|
from configgen.controllers.controller import InputItem, ControllerPerPlayer, Controller''',
|
|
117: '''def __setIpl64DD(system: Emulator, mupenSettings: IniSettings):
|
|
# Set ipl only for n64
|
|
section = '64DD'
|
|
if system.Name == "64dd":
|
|
mupenSettings.setString(section, "IPL-ROM", "/recalbox/share/bios/64DD_IPL.bin")
|
|
elif system.Name == "n64":
|
|
mupenSettings.setString(section, "IPL-ROM", "")''',
|
|
}
|
|
|
|
BLOCKS = {
|
|
FILES_MODULE: FILES_MODULE_BLOCKS,
|
|
LAUNCHER: LAUNCHER_BLOCKS,
|
|
RETROARCH: RETROARCH_BLOCKS,
|
|
CORES: CORES_BLOCKS,
|
|
LIBRETRO_GENERATOR: LIBRETRO_GENERATOR_BLOCKS,
|
|
HATARI: HATARI_BLOCKS,
|
|
GSPLUS: GSPLUS_BLOCKS,
|
|
AMIBERRY_KICKSTARTS: AMIBERRY_KICKSTARTS_BLOCKS,
|
|
AMIBERRY_SUBSYSTEMS: AMIBERRY_SUBSYSTEMS_BLOCKS,
|
|
AMIBERRY_CONFIG: AMIBERRY_CONFIG_BLOCKS,
|
|
PCSX2: PCSX2_BLOCKS,
|
|
DOLPHIN: DOLPHIN_BLOCKS,
|
|
XEMU: XEMU_BLOCKS,
|
|
MUPEN_CONFIG: MUPEN_CONFIG_BLOCKS,
|
|
}
|
|
|
|
|
|
def files(path: str | None = None, old: str = "", new: str = "") -> dict[str, str]:
|
|
"""The configgen tree, with `old` replaced once by `new` in `path`."""
|
|
tree = {name: placed(blocks) for name, blocks in BLOCKS.items()}
|
|
if path is not None:
|
|
if old not in tree[path]:
|
|
raise AssertionError(f"{old!r} is not in {path}")
|
|
tree[path] = tree[path].replace(old, new, 1)
|
|
return tree
|
|
|
|
|
|
def configgen(path: str | None = None, old: str = "", new: str = "") -> Configgen:
|
|
return Configgen(files(path, old, new).__getitem__)
|
|
|
|
|
|
def ref(path: str, lines: str) -> str:
|
|
return f"{path}:{lines}"
|
|
|
|
|
|
class SystemDirectoryPerCore(unittest.TestCase):
|
|
"""libretroRetroarch.py:431-529, the directory RetroArch is given."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
cls.records = system_directories(configgen())
|
|
|
|
def by(self, emulator: str) -> list[Launcher]:
|
|
return [r for r in self.records if r.emulator == emulator]
|
|
|
|
def test_a_core_with_one_directory(self):
|
|
gpsp, = self.by("gpsp")
|
|
self.assertEqual(gpsp.kind, "passes_path")
|
|
self.assertEqual(gpsp.build, "libretro")
|
|
self.assertEqual(gpsp.names, [])
|
|
self.assertEqual(gpsp.search_dir, "/recalbox/share/bios/gba")
|
|
self.assertEqual(gpsp.passes_to, "retroarch system_directory")
|
|
self.assertIsNone(gpsp.depends_on_option)
|
|
self.assertEqual(gpsp.source_ref, ref(RETROARCH, "472-473"))
|
|
geargrafx, = self.by("geargrafx")
|
|
self.assertEqual(geargrafx.search_dir, "/recalbox/share/bios/pcenginecd")
|
|
|
|
def test_a_core_whose_directory_depends_on_the_system(self):
|
|
self.assertEqual(
|
|
[(r.search_dir, r.depends_on_option, r.source_ref) for r in self.by("bsnes")],
|
|
[
|
|
("/recalbox/share/bios/sgb", "system gb, gbc", ref(RETROARCH, "436-438")),
|
|
("/recalbox/share/bios/satellaview", "system satellaview",
|
|
ref(RETROARCH, "436, 439-440")),
|
|
("/recalbox/share/bios", "system other than gb, gbc, satellaview",
|
|
ref(RETROARCH, "436, 441-442")),
|
|
],
|
|
)
|
|
self.assertEqual(
|
|
[(r.search_dir, r.depends_on_option) for r in self.by("mesen_s")],
|
|
[("/recalbox/share/bios/sgb", "system flag SuperGameBoy"),
|
|
("/recalbox/share/bios/<system>", "without system flag SuperGameBoy")],
|
|
)
|
|
|
|
def test_a_directory_named_after_the_system(self):
|
|
picodrive, = self.by("picodrive")
|
|
self.assertEqual(picodrive.search_dir, "/recalbox/share/bios/<system>")
|
|
|
|
def test_every_other_core_gets_the_bios_root(self):
|
|
default = [r for r in self.by(ALL_CORES)
|
|
if r.depends_on_option == "core with no case of its own"]
|
|
self.assertEqual([(r.search_dir, r.source_ref) for r in default],
|
|
[("/recalbox/share/bios", ref(RETROARCH, "513-514"))])
|
|
|
|
def test_the_system_overrides_come_after_the_core(self):
|
|
overrides = [r for r in self.by(ALL_CORES) if r.depends_on_option.startswith("system")]
|
|
self.assertEqual(
|
|
[(r.search_dir, r.depends_on_option.removesuffix(
|
|
", replacing the directory chosen by core")) for r in overrides],
|
|
[
|
|
("/recalbox/share/bios/amiga/bios",
|
|
"system amiga600, amiga1200, amigacdtv, amigacd32"),
|
|
("/recalbox/share/bios/fds", "system fds"),
|
|
("/recalbox/share/bios/lynx", "system lynx"),
|
|
("/recalbox/share/bios/nds", "system nds"),
|
|
("/recalbox/share/bios/saturn", "system saturn"),
|
|
("/recalbox/share/bios/stv", "system stv"),
|
|
],
|
|
)
|
|
self.assertEqual(overrides[0].source_ref, ref(RETROARCH, "516-519"))
|
|
|
|
def test_the_cases_cover_the_match(self):
|
|
cores = {name for r in self.records for name in r.emulator.split(", ")}
|
|
self.assertEqual(len(cores - {ALL_CORES}), 29)
|
|
|
|
def test_dolphin_assets_directory(self):
|
|
assets, = core_assets(configgen())
|
|
self.assertEqual(assets.emulator, "dolphin")
|
|
self.assertEqual(assets.search_dir, "/recalbox/share/bios/dolphin-emu")
|
|
self.assertEqual(assets.passes_to, "retroarch core_assets_directory")
|
|
self.assertEqual(assets.source_ref, ref(RETROARCH, "423-424"))
|
|
|
|
def test_a_guarded_case_raises(self):
|
|
with self.assertRaises(LauncherShapeError):
|
|
system_directories(configgen(RETROARCH, "case 'gpsp':", "case 'gpsp' if True:"))
|
|
|
|
def test_a_branch_test_it_cannot_read_raises(self):
|
|
tree = configgen(RETROARCH, "elif self.system.Name == 'sufami':",
|
|
"elif self.system.Name.startswith('sufami'):")
|
|
with self.assertRaises(LauncherShapeError):
|
|
system_directories(tree)
|
|
|
|
def test_a_directory_it_cannot_spell_raises(self):
|
|
tree = configgen(RETROARCH, 'recalboxFiles.BIOS + "/gba"', "gbaDirectory()")
|
|
with self.assertRaises(LauncherShapeError):
|
|
system_directories(tree)
|
|
|
|
|
|
class HatariTosCascade(unittest.TestCase):
|
|
"""hatariGenerator.py:103-167 and libretroCores.py:165-298."""
|
|
|
|
def test_the_standalone_tries_the_machine_then_three_fallbacks(self):
|
|
records = hatari(configgen())
|
|
self.assertEqual(
|
|
[(r.slot, r.names[0]) for r in records[:-1]],
|
|
[("st", "atarist/st.img"), ("megast", "atarist/st.img"),
|
|
("ste", "atarist/ste.img"), ("megaste", "atarist/megaste.img"),
|
|
("tt", "atarist/tt.img"), ("falcon", "atarist/falcon.img")],
|
|
)
|
|
for record in records[:-1]:
|
|
self.assertEqual(record.kind, "names_searched")
|
|
self.assertEqual(record.names[1:], ["atarist/tos.img", "tos.img", "atarist/emutos.img"])
|
|
self.assertEqual(record.search_dir, "/recalbox/share/bios")
|
|
self.assertEqual(record.passes_to, "[ROM] szTosImageFileName")
|
|
self.assertIs(record.fails_when_missing, False)
|
|
self.assertEqual(record.source_ref, ref(HATARI, "108-138, 151-167"))
|
|
# coreSettings is never bound in SetMachineAndBios: no option fallback.
|
|
self.assertEqual(record.depends_on_option,
|
|
"machine read from the rom path through machines")
|
|
|
|
def test_emutos_in_the_rom_path_forces_one_name(self):
|
|
emutos = hatari(configgen())[-1]
|
|
self.assertEqual((emutos.kind, emutos.slot, emutos.names),
|
|
("fixed_name", "emutos", ["atarist/emutos.img"]))
|
|
self.assertEqual(emutos.depends_on_option, '"emutos" in the rom path')
|
|
|
|
def test_the_two_libretro_cores(self):
|
|
records = hatari_libretro(configgen())
|
|
old = [r for r in records if r.emulator == "hatari"]
|
|
new = [r for r in records if r.emulator == "hatarib"]
|
|
self.assertEqual([r.slot for r in old],
|
|
["st", "ste", "tt", "falcon", "megaste", "emutos"])
|
|
self.assertEqual(old[0].passes_to, "[ROM] szTosImageFileName")
|
|
self.assertEqual(old[0].source_ref, ref(CORES, "235-239, 249-267, 280-294"))
|
|
self.assertEqual([r.slot for r in new],
|
|
["st", "megast", "ste", "megaste", "tt", "falcon"])
|
|
self.assertEqual(new[0].names, ["atarist/st.img", "atarist/tos.img", "tos.img"])
|
|
self.assertEqual(new[0].passes_to,
|
|
"core option hatarib_tos (<etos1024k> when none is found)")
|
|
self.assertEqual(new[0].depends_on_option,
|
|
"machine read from the rom path through machines, "
|
|
"else option hatarib_machine")
|
|
self.assertEqual(new[0].source_ref, ref(CORES, "168-198, 211-212, 214-229"))
|
|
|
|
def test_another_existence_test_raises(self):
|
|
tree = configgen(HATARI, "if not os.path.exists(biosFile):",
|
|
"if not os.path.isfile(biosFile):")
|
|
with self.assertRaises(LauncherShapeError):
|
|
hatari(tree)
|
|
|
|
|
|
class GsplusRom(unittest.TestCase):
|
|
"""gsplusGenerator.py:93-148."""
|
|
|
|
def test_the_search_and_the_game_file_placeholders(self):
|
|
searched, rom1, rom3 = gsplus(configgen())
|
|
self.assertEqual(searched.names, ["apple2gs1.rom", "apple2gs3.rom"])
|
|
self.assertEqual(searched.kind, "names_searched")
|
|
self.assertEqual(searched.passes_to, "g_cfg_rom_path")
|
|
self.assertEqual(searched.depends_on_option, 'rom path without ".gsplus"')
|
|
self.assertEqual(searched.source_ref, ref(GSPLUS, "140-148"))
|
|
self.assertEqual([(r.slot, r.names) for r in (rom1, rom3)],
|
|
[("%1", ["apple2gs1.rom"]), ("%3", ["apple2gs3.rom"])])
|
|
self.assertEqual(rom1.source_ref, ref(GSPLUS, "107-108"))
|
|
|
|
def test_a_last_miss_that_keeps_the_key_raises(self):
|
|
tree = configgen(GSPLUS, 'settings.removeOption("g_cfg_rom_path")', "pass")
|
|
with self.assertRaises(LauncherShapeError):
|
|
gsplus(tree)
|
|
|
|
|
|
class AmiberryKickstarts(unittest.TestCase):
|
|
"""amiberryKickstarts.py:47-143 and amiberryConfig.py:337-345."""
|
|
|
|
def test_each_system_and_rom_type(self):
|
|
records = amiberry(configgen())
|
|
self.assertEqual(
|
|
[(r.slot, r.kind, r.names, r.fails_when_missing) for r in records],
|
|
[
|
|
("amiga600", "names_searched",
|
|
["kick37175.A500", "kick34005.A500", "kick33180.A500"], False),
|
|
("amiga600", "names_searched", ["kick34005.A500", "kick33180.A500"], False),
|
|
("amiga1200", "names_searched",
|
|
["kick40068.A1200", "kick39106.A1200", "kick40068.A4000"], False),
|
|
("amigacd32", "fixed_name", ["kick40060.CD32", "kick40060.CD32.ext"],
|
|
"No BIOS found, Extended BIOS not found"),
|
|
("amigacdtv", "fixed_name", ["kick34005.A500", "kick34005.CDTV"],
|
|
"No BIOS found, Extended BIOS not found"),
|
|
("amiga600, amiga1200", "fixed_name", ["kick02019.AROS", "kick02019.AROS.ext"],
|
|
False),
|
|
],
|
|
)
|
|
self.assertEqual(records[1].depends_on_option, "rom type DISK")
|
|
self.assertEqual(records[0].search_dir, "/recalbox/share/bios/amiga/bios")
|
|
self.assertEqual(records[0].passes_to, "kickstart_rom_file")
|
|
self.assertEqual(records[-1].passes_to,
|
|
"kickstart_rom_file, kickstart_ext_rom_file "
|
|
"(:AROS built in when either is missing)")
|
|
self.assertEqual(records[0].source_ref,
|
|
f"{ref(AMIBERRY_KICKSTARTS, '9-10, 50-53, 94-142')}, "
|
|
f"{ref(AMIBERRY_CONFIG, '337-345')}")
|
|
|
|
def test_several_pairs_for_one_rom_type_raise(self):
|
|
tree = configgen(AMIBERRY_KICKSTARTS, '["kick40060.CD32|kick40060.CD32.ext"]',
|
|
'["kick40060.CD32|kick40060.CD32.ext", "a|b"]')
|
|
with self.assertRaises(LauncherShapeError):
|
|
amiberry(tree)
|
|
|
|
|
|
class Pcsx2Region(unittest.TestCase):
|
|
"""pcsx2Generator.py:36-40, 55-107, 166."""
|
|
|
|
def test_the_region_token_comes_first_then_a_fixed_order(self):
|
|
records = pcsx2(configgen())
|
|
eu = records[0]
|
|
self.assertEqual(eu.slot, "EU")
|
|
self.assertEqual(eu.names, ["ps2-0230e-20080220.bin", "ps2-0230a-20080220.bin",
|
|
"ps2-0230j-20080220.bin", "ps2-0230h-20080220.bin"])
|
|
self.assertTrue(eu.depends_on_option.startswith("rom name contains europe, france"))
|
|
self.assertTrue(eu.depends_on_option.endswith(", uk"))
|
|
self.assertEqual(eu.search_dir, "/recalbox/share/bios/ps2")
|
|
self.assertEqual(eu.passes_to, "[Filenames] BIOS")
|
|
self.assertEqual(eu.source_ref, ref(PCSX2, "37-40, 56-107, 166"))
|
|
self.assertEqual([r.slot for r in records], ["EU", "USA", "JP", "HK", "default"])
|
|
|
|
def test_the_chinese_name_is_written_when_the_japanese_file_exists(self):
|
|
hk = pcsx2(configgen())[3]
|
|
self.assertEqual((hk.kind, hk.names), ("fixed_name", ["ps2-0230h-20080220.bin"]))
|
|
self.assertEqual(hk.depends_on_option,
|
|
"rom name contains china, taiwan and ps2-0230j-20080220.bin is present")
|
|
|
|
def test_a_region_branch_of_another_shape_raises(self):
|
|
tree = configgen(PCSX2, 'elif "uk" in lrom and Exists', 'elif "uk" in lrom or Exists')
|
|
with self.assertRaises(LauncherShapeError):
|
|
pcsx2(tree)
|
|
|
|
|
|
class FixedPaths(unittest.TestCase):
|
|
def test_xemu_writes_two_paths(self):
|
|
record, = xemu(configgen())
|
|
self.assertEqual(record.names, ["xbox/Complex_4627.bin", "xbox/mcpx_1.0.bin"])
|
|
self.assertEqual(record.passes_to, "[sys.files] flashrom_path, [sys.files] bootrom_path")
|
|
self.assertEqual(record.source_ref, ref(XEMU, "16, 26, 58-59"))
|
|
|
|
def test_mupen64plus_writes_the_64dd_ipl_for_one_system(self):
|
|
record, = mupen64plus(configgen())
|
|
self.assertEqual(record.names, ["64DD_IPL.bin"])
|
|
self.assertEqual(record.passes_to, "[64DD] IPL-ROM")
|
|
self.assertEqual(record.depends_on_option, "system 64dd")
|
|
self.assertEqual(record.source_ref, ref(MUPEN_CONFIG, "118-121"))
|
|
|
|
def test_dolphin_only_tests_the_ipl_for_presence(self):
|
|
standalone, libretro = dolphin(configgen())
|
|
names = ["gamecube/EUR/IPL.bin", "gamecube/JP/IPL.bin", "gamecube/USA/IPL.bin"]
|
|
self.assertEqual((standalone.emulator, standalone.names), ("dolphin, dolphin-gui", names))
|
|
self.assertEqual(standalone.passes_to, "[Core] SkipIpl (False when one is present, else True)")
|
|
self.assertEqual(standalone.source_ref, ref(DOLPHIN, "154-163, 236-237, 273"))
|
|
self.assertEqual((libretro.emulator, libretro.names), ("dolphin", names))
|
|
self.assertEqual(libretro.passes_to,
|
|
"core option dolphin_skip_gc_bios (disabled when one is present, "
|
|
"else enabled)")
|
|
self.assertEqual(libretro.source_ref, ref(CORES, "496-504"))
|
|
|
|
def test_minivmac_boot_disk_goes_through_a_command_file(self):
|
|
record, = content_files(configgen())
|
|
self.assertEqual((record.emulator, record.names),
|
|
("minivmac", ["macintosh/MinivMacBootv2.dsk"]))
|
|
self.assertEqual(record.passes_to,
|
|
"written into /tmp/minivmac.cmd, which is loaded as the content")
|
|
self.assertEqual(record.source_ref,
|
|
f"{ref(LIBRETRO_GENERATOR, '877-882')}, {ref(FILES_MODULE, '174')}")
|
|
|
|
def test_o2em_bios_option_is_read_from_the_core_directory(self):
|
|
cg = configgen()
|
|
records = core_file_options(cg, system_directories(cg))
|
|
self.assertEqual(
|
|
[(r.names, r.search_dir, r.depends_on_option) for r in records],
|
|
[(["o2rom.bin"], "/recalbox/share/bios/o2em", "system odyssey2"),
|
|
(["g7400.bin"], "/recalbox/share/bios/o2em", "system videopacplus")],
|
|
)
|
|
self.assertEqual(records[0].passes_to, "core option o2em_bios")
|
|
self.assertEqual(records[0].source_ref,
|
|
f"{ref(CORES, '350-351, 695')}, {ref(RETROARCH, '491-492')}")
|
|
|
|
|
|
class ExtractionNeverEmpty(unittest.TestCase):
|
|
def test_every_file_read_is_one_the_scraper_fetches(self):
|
|
tree = files()
|
|
read: list[str] = []
|
|
|
|
def reader(path: str) -> str:
|
|
read.append(path)
|
|
return tree[path]
|
|
|
|
records = extract_launchers(reader)
|
|
self.assertEqual(sorted(set(read)), sorted(SOURCE_FILES))
|
|
self.assertEqual(len(records), 85)
|
|
|
|
def test_records_fit_the_schema(self):
|
|
schema = json.loads((REPO_ROOT / "schemas" / "platform.schema.json").read_text())
|
|
launcher = schema["$defs"]["launcher"]
|
|
kinds = set(launcher["properties"]["kind"]["enum"])
|
|
for record in extract_launchers(files().__getitem__):
|
|
self.assertEqual(sorted(set(record) - set(launcher["properties"])), [])
|
|
self.assertEqual(sorted(set(launcher["required"]) - set(record)), [])
|
|
self.assertIn(record["kind"], kinds)
|
|
|
|
def test_a_files_module_without_bios_raises(self):
|
|
with self.assertRaises(LauncherShapeError):
|
|
configgen(FILES_MODULE, "BIOS = '/recalbox/share/bios'", "BIOS_DIR = ''")
|
|
|
|
def test_the_scraper_refuses_a_file_it_did_not_fetch(self):
|
|
tree = files()
|
|
scraper = recalbox_scraper.Scraper.__new__(recalbox_scraper.Scraper)
|
|
scraper.tag = "10.1.1"
|
|
fetched = {path: tree[path] for path in SOURCE_FILES if path != PCSX2}
|
|
with mock.patch.object(scraper, "_blobs", return_value=fetched), \
|
|
self.assertRaises(LauncherShapeError):
|
|
scraper._fetch_launchers()
|
|
|
|
def test_the_scraper_hands_over_what_it_fetched(self):
|
|
tree = files()
|
|
scraper = recalbox_scraper.Scraper.__new__(recalbox_scraper.Scraper)
|
|
scraper.tag = "10.1.1"
|
|
reader: Callable[[str], str] = tree.__getitem__
|
|
with mock.patch.object(scraper, "_blobs", return_value=tree):
|
|
self.assertEqual(scraper._fetch_launchers(), extract_launchers(reader))
|
|
|
|
|
|
class PlatformRecords(unittest.TestCase):
|
|
"""The launchers platforms/recalbox.yml carries, as the scraper wrote them."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
cls.config = yaml.safe_load((REPO_ROOT / "platforms" / "recalbox.yml").read_text())
|
|
|
|
def test_every_emulator_is_one_recalbox_names(self):
|
|
cores = set(self.config["cores"]) | {ALL_CORES}
|
|
named = {
|
|
name for record in self.config["launchers"]
|
|
for name in record["emulator"].split(", ")
|
|
}
|
|
self.assertEqual(sorted(named - cores), [])
|
|
|
|
def test_every_reference_is_in_configgen(self):
|
|
for record in self.config["launchers"]:
|
|
for part in record["source_ref"].split(", "):
|
|
if not part[0].isdigit():
|
|
self.assertTrue(part.startswith(f"{CONFIGGEN}/"), record)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|