mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 04:39:06 -05:00
chore: merge audit fixes (retrobat)
This commit is contained in:
commit
7967e7a9fd
4 files changed
+634
-7
No files matched your search
@@ -37,13 +37,6 @@ notes: |
|
||||
file names the binary fixes. Each file is opened with fopen "rb" and read
|
||||
with a fixed count, no length, checksum or header is compared.
|
||||
|
||||
RetroBat's generator writes SCDEURBIOS, SCDUSABIOS and SCDJAPBIOS as
|
||||
bios_CD_E.bin, bios_CD_U.bin and bios_CD_J.bin, and the three 32X keys as
|
||||
32X_G_BIOS.BIN, 32X_M_BIOS.BIN and 32X_S_BIOS.BIN in its bios folder, each
|
||||
only when the file exists. GenesisBIOS receives the bios folder itself, so
|
||||
the fopen fails and no TMSS ROM is loaded. The 8-bit keys are not written.
|
||||
ref: emulatorlauncher Generators/KegaFusion.Generator.cs:99-128
|
||||
|
||||
CDTray.raw, Cursor_8.raw, STATIC_8.RAW and gg_nor.all are looked up by name
|
||||
in an archive embedded in the executable (0x47a530), not on disk. Plugins
|
||||
(*.rpi) and the KGV1 video codec ship with the program. Fusion.ini, battery
|
||||
|
||||
@@ -3238,6 +3238,261 @@ launchers:
|
||||
fails_when_missing: not checked by the launcher
|
||||
depends_on_option: duck_bios
|
||||
source_ref: "emulatorLauncher/Generators/Duckstation.Generator.cs:204-209, .emulationstation/es_features.cfg:11638-11646"
|
||||
- emulator: jgenesis
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- gba_bios.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "jgenesis-config.toml [game_boy_advance] bios_path, only if the file\
|
||||
\ exists"
|
||||
fails_when_missing: bios_path not written
|
||||
depends_on_option: system == "game_boy_advance"
|
||||
source_ref: "emulatorLauncher/Generators/Jgenesis.Generator.cs:257-263"
|
||||
- emulator: jgenesis
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- bios_CD_U.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "jgenesis-config.toml [sega_cd] bios_path, only if the file exists"
|
||||
fails_when_missing: bios_path not written
|
||||
depends_on_option: system == "genesis" || system == "sega_cd" || system == "sega_32x",
|
||||
system == "sega_cd"
|
||||
source_ref: "emulatorLauncher/Generators/Jgenesis.Generator.cs:282-283, 317, 323-324"
|
||||
- emulator: jgenesis
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- bios_CD_J.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "jgenesis-config.toml [sega_cd] jp_bios_path, only if the file exists"
|
||||
fails_when_missing: jp_bios_path not written
|
||||
depends_on_option: system == "genesis" || system == "sega_cd" || system == "sega_32x",
|
||||
system == "sega_cd"
|
||||
source_ref: "emulatorLauncher/Generators/Jgenesis.Generator.cs:282-283, 318, 325-326"
|
||||
- emulator: jgenesis
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- bios_CD_E.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "jgenesis-config.toml [sega_cd] eu_bios_path, only if the file exists"
|
||||
fails_when_missing: eu_bios_path not written
|
||||
depends_on_option: system == "genesis" || system == "sega_cd" || system == "sega_32x",
|
||||
system == "sega_cd"
|
||||
source_ref: "emulatorLauncher/Generators/Jgenesis.Generator.cs:282-283, 319, 327-328"
|
||||
- emulator: kega-fusion
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- bios_CD_E.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: Fusion.ini SCDEURBIOS, only if the file exists
|
||||
fails_when_missing: SCDEURBIOS not written
|
||||
source_ref: "emulatorLauncher/Generators/KegaFusion.Generator.cs:106-108"
|
||||
- emulator: kega-fusion
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- bios_CD_U.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: Fusion.ini SCDUSABIOS, only if the file exists
|
||||
fails_when_missing: SCDUSABIOS not written
|
||||
source_ref: "emulatorLauncher/Generators/KegaFusion.Generator.cs:110-112"
|
||||
- emulator: kega-fusion
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- bios_CD_J.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: Fusion.ini SCDJAPBIOS, only if the file exists
|
||||
fails_when_missing: SCDJAPBIOS not written
|
||||
source_ref: "emulatorLauncher/Generators/KegaFusion.Generator.cs:114-116"
|
||||
- emulator: kega-fusion
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- 32X_G_BIOS.BIN
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: Fusion.ini 32XM68KBIOS, only if the file exists
|
||||
fails_when_missing: 32XM68KBIOS not written
|
||||
source_ref: "emulatorLauncher/Generators/KegaFusion.Generator.cs:118-120"
|
||||
- emulator: kega-fusion
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- 32X_M_BIOS.BIN
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: Fusion.ini 32XMSH2BIOS, only if the file exists
|
||||
fails_when_missing: 32XMSH2BIOS not written
|
||||
source_ref: "emulatorLauncher/Generators/KegaFusion.Generator.cs:122-124"
|
||||
- emulator: kega-fusion
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- 32X_S_BIOS.BIN
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: Fusion.ini 32XSSH2BIOS, only if the file exists
|
||||
fails_when_missing: 32XSSH2BIOS not written
|
||||
source_ref: "emulatorLauncher/Generators/KegaFusion.Generator.cs:126-128"
|
||||
- emulator: kega-fusion
|
||||
build: standalone
|
||||
kind: passes_path
|
||||
names: []
|
||||
search_dir: bios
|
||||
passes_to: Fusion.ini GenesisBIOS = bios
|
||||
fails_when_missing: false
|
||||
source_ref: "emulatorLauncher/Generators/KegaFusion.Generator.cs:99, 103"
|
||||
- emulator: melonds
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- firmware.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "melonDS.toml [DS] FirmwarePath, only if the file exists"
|
||||
fails_when_missing: FirmwarePath not written
|
||||
depends_on_option: melonds_externalBIOS on
|
||||
source_ref: "emulatorLauncher/Generators/MelonDS.Generator.cs:167, 174-175"
|
||||
- emulator: melonds
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- bios7.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "melonDS.toml [DS] BIOS7Path, only if the file exists"
|
||||
fails_when_missing: BIOS7Path not written
|
||||
depends_on_option: melonds_externalBIOS on
|
||||
source_ref: "emulatorLauncher/Generators/MelonDS.Generator.cs:168, 177-178"
|
||||
- emulator: melonds
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- bios9.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "melonDS.toml [DS] BIOS9Path, only if the file exists"
|
||||
fails_when_missing: BIOS9Path not written
|
||||
depends_on_option: melonds_externalBIOS on
|
||||
source_ref: "emulatorLauncher/Generators/MelonDS.Generator.cs:169, 180-181"
|
||||
- emulator: mupen64
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- IPL_JAP.n64
|
||||
case_insensitive: true
|
||||
search_dir: bios/Mupen64plus
|
||||
passes_to: "Config/mupen64plus.cfg [Rosalie's Mupen GUI Core 64DD] 64DD_JapaneseIPL,\
|
||||
\ only if the file exists"
|
||||
fails_when_missing: 64DD_JapaneseIPL not written
|
||||
source_ref: "emulatorLauncher/Generators/Mupen64.Generator.cs:228-230"
|
||||
- emulator: mupen64
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- IPL_USA.n64
|
||||
case_insensitive: true
|
||||
search_dir: bios/Mupen64plus
|
||||
passes_to: "Config/mupen64plus.cfg [Rosalie's Mupen GUI Core 64DD] 64DD_AmericanIPL,\
|
||||
\ only if the file exists"
|
||||
fails_when_missing: 64DD_AmericanIPL not written
|
||||
source_ref: "emulatorLauncher/Generators/Mupen64.Generator.cs:232-234"
|
||||
- emulator: mupen64
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- IPL_DEV.n64
|
||||
case_insensitive: true
|
||||
search_dir: bios/Mupen64plus
|
||||
passes_to: "Config/mupen64plus.cfg [Rosalie's Mupen GUI Core 64DD] 64DD_DevelopmentIPL,\
|
||||
\ only if the file exists"
|
||||
fails_when_missing: 64DD_DevelopmentIPL not written
|
||||
source_ref: "emulatorLauncher/Generators/Mupen64.Generator.cs:236-238"
|
||||
- emulator: project64
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- IPL_JAP.n64
|
||||
case_insensitive: true
|
||||
search_dir: bios/Mupen64plus
|
||||
passes_to: "Config/Project64.cfg [Settings] Disk IPL ROM Path, only if the file\
|
||||
\ exists"
|
||||
fails_when_missing: Disk IPL ROM Path not written
|
||||
source_ref: "emulatorLauncher/Generators/Project64.Generator.cs:91-93"
|
||||
- emulator: project64
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- IPL_USA.n64
|
||||
case_insensitive: true
|
||||
search_dir: bios/Mupen64plus
|
||||
passes_to: "Config/Project64.cfg [Settings] Disk IPL USA ROM Path, only if the file\
|
||||
\ exists"
|
||||
fails_when_missing: Disk IPL USA ROM Path not written
|
||||
source_ref: "emulatorLauncher/Generators/Project64.Generator.cs:95-97"
|
||||
- emulator: project64
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- IPL_DEV.n64
|
||||
case_insensitive: true
|
||||
search_dir: bios/Mupen64plus
|
||||
passes_to: "Config/Project64.cfg [Settings] Disk IPL TOOL ROM Path, only if the\
|
||||
\ file exists"
|
||||
fails_when_missing: Disk IPL TOOL ROM Path not written
|
||||
source_ref: "emulatorLauncher/Generators/Project64.Generator.cs:99-101"
|
||||
- emulator: mgba
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- gb_bios.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "config.ini [ports.qt] gb.bios, only if the file exists"
|
||||
fails_when_missing: gb.bios not written
|
||||
source_ref: "emulatorLauncher/Generators/mGBA.Generator.cs:112-114"
|
||||
- emulator: mgba
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- sgb2_boot.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "config.ini [ports.qt] sgb.bios, only if the file exists"
|
||||
fails_when_missing: sgb.bios not written
|
||||
source_ref: "emulatorLauncher/Generators/mGBA.Generator.cs:116-118"
|
||||
- emulator: mgba
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- gbc_bios.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "config.ini [ports.qt] gbc.bios, only if the file exists"
|
||||
fails_when_missing: gbc.bios not written
|
||||
source_ref: "emulatorLauncher/Generators/mGBA.Generator.cs:120-122"
|
||||
- emulator: mgba
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
names:
|
||||
- gba_bios.bin
|
||||
case_insensitive: true
|
||||
search_dir: bios
|
||||
passes_to: "config.ini [ports.qt] gba.bios, only if the file exists"
|
||||
fails_when_missing: gba.bios not written
|
||||
source_ref: "emulatorLauncher/Generators/mGBA.Generator.cs:124-126"
|
||||
- emulator: hatari
|
||||
build: standalone
|
||||
kind: fixed_name
|
||||
|
||||
@@ -23,13 +23,19 @@ SOURCE_FILES = (
|
||||
f"{GENERATORS}/Bizhawk.Generator.cs",
|
||||
f"{GENERATORS}/Duckstation.Generator.cs",
|
||||
f"{GENERATORS}/Hatari.Generator.cs",
|
||||
f"{GENERATORS}/Jgenesis.Generator.cs",
|
||||
f"{GENERATORS}/KegaFusion.Generator.cs",
|
||||
f"{GENERATORS}/LibRetro.CoreOptions.cs",
|
||||
f"{GENERATORS}/Mednafen.Generator.cs",
|
||||
f"{GENERATORS}/MelonDS.Generator.cs",
|
||||
f"{GENERATORS}/Mesen.Generator.cs",
|
||||
f"{GENERATORS}/Mupen64.Generator.cs",
|
||||
f"{GENERATORS}/OpenMSX.Generator.cs",
|
||||
f"{GENERATORS}/Pcsx2.Generator.cs",
|
||||
f"{GENERATORS}/Project64.Generator.cs",
|
||||
f"{GENERATORS}/Uae.Generator.cs",
|
||||
f"{GENERATORS}/Xemu.Generator.cs",
|
||||
f"{GENERATORS}/mGBA.Generator.cs",
|
||||
FEATURES,
|
||||
)
|
||||
|
||||
@@ -54,6 +60,7 @@ class Source:
|
||||
self.text = text
|
||||
self._starts = [0] + [m.end() for m in re.finditer(r"\n", text)]
|
||||
self._blocks: list[tuple[int, int]] | None = None
|
||||
self._ends: list[int] | None = None
|
||||
|
||||
def line(self, offset: int) -> int:
|
||||
return bisect.bisect_right(self._starts, offset)
|
||||
@@ -107,6 +114,15 @@ class Source:
|
||||
raise LauncherPatternMissing(self.path, f"block around line {self.line(offset)}")
|
||||
return max(holding)
|
||||
|
||||
def header(self, block: tuple[int, int]) -> str:
|
||||
"""The statement that owns a block's opening brace, comments removed."""
|
||||
if self._ends is None:
|
||||
self._ends = [o for o, c in _code_chars(self.text) if c in ";{}"]
|
||||
previous = bisect.bisect_left(self._ends, block[0]) - 1
|
||||
start = self._ends[previous] + 1 if previous >= 0 else 0
|
||||
text = re.sub(r"//[^\n]*|/\*.*?\*/", "", self.text[start : block[0]], flags=re.S)
|
||||
return " ".join(text.split())
|
||||
|
||||
def statement_span(self, block: tuple[int, int]) -> tuple[int, int]:
|
||||
"""Lines of a block, from the statement that owns its opening brace."""
|
||||
first, last = self.lines(*block)
|
||||
@@ -1169,10 +1185,223 @@ def duckstation(sources: dict[str, Source]) -> list[Record]:
|
||||
]
|
||||
|
||||
|
||||
# Generators that write each BIOS path into the emulator's configuration
|
||||
# only when the file exists: string x = Path.Combine(bios, ..., "name");
|
||||
# if (File.Exists(x)) ini.WriteValue("section", "key", x).
|
||||
GUARDED_WRITES = {
|
||||
f"{GENERATORS}/Jgenesis.Generator.cs": "jgenesis",
|
||||
f"{GENERATORS}/KegaFusion.Generator.cs": "kega-fusion",
|
||||
f"{GENERATORS}/MelonDS.Generator.cs": "melonds",
|
||||
f"{GENERATORS}/Mupen64.Generator.cs": "mupen64",
|
||||
f"{GENERATORS}/Project64.Generator.cs": "project64",
|
||||
f"{GENERATORS}/mGBA.Generator.cs": "mgba",
|
||||
}
|
||||
|
||||
_METHOD = re.compile(
|
||||
r"(?:(?:public|private|protected|internal|static|override|virtual)\s+)+[\w<>\[\],. ]+\s\w+\s*\("
|
||||
)
|
||||
_TRANSPARENT = re.compile(r"(?:using\s*\(.*\)|try)")
|
||||
_IF = re.compile(r"(?:else\s+)?if\s*\((.*)\)")
|
||||
_GUARD = re.compile(
|
||||
r'if \(File\.Exists\((\w+)\)\)\s*(\w+)\.WriteValue\("([^"]*)", "([^"]+)", ([^;\n]+)\);'
|
||||
)
|
||||
_BIOS_ROOT = r'(?:Program\.)?AppConfig\.GetFullPath\("bios"\)'
|
||||
|
||||
|
||||
def _negate(condition: str) -> str:
|
||||
"""The condition under which an early return is not taken.
|
||||
|
||||
A conjunction of inequalities becomes the disjunction of equalities,
|
||||
anything else is wrapped in not (...).
|
||||
"""
|
||||
tests = condition.split(" && ")
|
||||
compared = [re.fullmatch(rf"(\w+) != ({STRING})", t) for t in tests]
|
||||
if all(compared):
|
||||
return " || ".join(f"{m.group(1)} == {m.group(2)}" for m in compared)
|
||||
return f"not ({condition})"
|
||||
|
||||
|
||||
Reach = tuple[tuple[int, int], list[str], list[tuple[int, int]]]
|
||||
|
||||
|
||||
def _conditions(src: Source, offset: int) -> Reach | None:
|
||||
"""The method holding offset, the conditions under which it is reached,
|
||||
and the lines of the early returns among them.
|
||||
|
||||
Each enclosing if adds its condition, and an early return at the top of
|
||||
the method adds the condition that does not return. None when a block in
|
||||
between is not an if, a using or a try: a loop or an else would need its
|
||||
own reading.
|
||||
"""
|
||||
enclosing: list[str] = []
|
||||
for block in sorted((b for b in src.blocks() if b[0] < offset < b[1]), reverse=True):
|
||||
header = src.header(block)
|
||||
if _METHOD.match(header):
|
||||
returns, spans = [], []
|
||||
for found in re.finditer(r"\bif \(", src.text[block[0] : offset]):
|
||||
paren = block[0] + found.end() - 1
|
||||
if src.enclosing(paren) != block:
|
||||
continue
|
||||
cond = _call_args(src, paren)
|
||||
tail = re.compile(r"\s*return\b[^;]*;").match(src.text, paren + len(cond) + 2)
|
||||
if tail:
|
||||
returns.append(_negate(" ".join(cond.split())))
|
||||
spans.append(src.lines(block[0] + found.start(), tail.end()))
|
||||
return block, returns + enclosing[::-1], spans
|
||||
condition = _IF.fullmatch(header)
|
||||
if condition:
|
||||
text = " ".join(condition.group(1).split())
|
||||
switch = re.fullmatch(
|
||||
r'SystemConfig\.isOptSet\("(\w+)"\) && SystemConfig\.getOptBoolean\("\1"\)', text
|
||||
)
|
||||
enclosing.append(f"{switch.group(1)} on" if switch else text)
|
||||
elif not _TRANSPARENT.fullmatch(header):
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _config_name(src: Source, method: tuple[int, int], writer: str) -> str:
|
||||
"""The file the writer variable opens, read in its method or else in the file."""
|
||||
opener = r"(\w+)\s*=\s*(?:\w+\.FromFile|new \w+)\((\w+)"
|
||||
declared = {
|
||||
m.group(1): "/".join(_strings(m.group(2)))
|
||||
for m in re.finditer(
|
||||
r'string (\w+) = Path\.Combine\(path, ((?:"[^"]+"(?:, )?)+)\);', src.text
|
||||
)
|
||||
}
|
||||
local = [
|
||||
m.group(2)
|
||||
for m in re.finditer(opener, src.text[method[0] : method[1]])
|
||||
if m.group(1) == writer and m.group(2) in declared
|
||||
]
|
||||
if local:
|
||||
return declared[local[0]]
|
||||
files = {declared[m.group(2)] for m in re.finditer(opener, src.text) if m.group(2) in declared}
|
||||
if len(files) != 1:
|
||||
raise LauncherPatternMissing(src.path, f"configuration file opened as {writer}")
|
||||
return files.pop()
|
||||
|
||||
|
||||
def _root_writes(src: Source, emulator: str) -> list[Record]:
|
||||
"""Configuration keys that receive the bios folder itself."""
|
||||
records = []
|
||||
for root in re.finditer(rf"string (\w+) = {_BIOS_ROOT};", src.text):
|
||||
root_reach = _conditions(src, root.start())
|
||||
if root_reach is None:
|
||||
continue
|
||||
method = root_reach[0]
|
||||
for write in re.compile(
|
||||
rf'(\w+)\.WriteValue\("([^"]*)", "([^"]+)", {root.group(1)}\);'
|
||||
).finditer(src.text, root.end(), method[1]):
|
||||
reached = _conditions(src, write.start())
|
||||
if reached is None:
|
||||
continue
|
||||
writer, section, key = write.groups()
|
||||
target = f"[{section}] {key}" if section else key
|
||||
records.append(
|
||||
_record(
|
||||
emulator,
|
||||
"standalone",
|
||||
"passes_path",
|
||||
[],
|
||||
source_ref(
|
||||
(
|
||||
src.path,
|
||||
[
|
||||
*reached[2],
|
||||
src.lines(root.start(), root.end()),
|
||||
src.lines(write.start(), write.end()),
|
||||
],
|
||||
)
|
||||
),
|
||||
search_dir="bios",
|
||||
passes_to=f"{_config_name(src, method, writer)} {target} = bios",
|
||||
fails_when_missing=False,
|
||||
depends_on_option=", ".join(reached[1]) or None,
|
||||
)
|
||||
)
|
||||
return records
|
||||
|
||||
|
||||
def _guarded_write(src: Source, emulator: str, guard: re.Match[str]) -> Record | None:
|
||||
"""The record of one guarded write, None when its shape is another one.
|
||||
|
||||
The written value must be the checked path itself (quoted or with its
|
||||
separators replaced), the path a literal under the bios folder that
|
||||
nothing reassigns before the check, and every block around the write an
|
||||
if, a using or a try.
|
||||
"""
|
||||
var, writer, section, key, value = guard.groups()
|
||||
if not re.fullmatch(
|
||||
rf"(?:{STRING} \+ )?{var}(?:\.Replace\({STRING}, {STRING}\))?(?: \+ {STRING})?",
|
||||
value.strip(),
|
||||
):
|
||||
return None
|
||||
reached = _conditions(src, guard.start())
|
||||
if reached is None:
|
||||
return None
|
||||
method, conditions, returns = reached
|
||||
decls = list(
|
||||
re.compile(
|
||||
rf"string {var} = Path\.Combine\((\w+|{_BIOS_ROOT}), ((?:{STRING}, )*{STRING})\);"
|
||||
).finditer(src.text, method[0], guard.start())
|
||||
)
|
||||
if not decls or re.search(rf"\b{var}\s*=[^=]", src.text[decls[-1].end() : guard.start()]):
|
||||
return None
|
||||
decl = decls[-1]
|
||||
root = decl.group(1)
|
||||
if not re.fullmatch(_BIOS_ROOT, root) and not re.search(
|
||||
rf"string {root} = {_BIOS_ROOT};", src.text[method[0] : decl.start()]
|
||||
):
|
||||
return None
|
||||
*folders, name = _strings(decl.group(2))
|
||||
target = f"[{section}] {key}" if section else key
|
||||
return _record(
|
||||
emulator,
|
||||
"standalone",
|
||||
"fixed_name",
|
||||
[name],
|
||||
source_ref(
|
||||
(
|
||||
src.path,
|
||||
[
|
||||
*returns,
|
||||
src.lines(decl.start(), decl.end()),
|
||||
src.lines(guard.start(), guard.end()),
|
||||
],
|
||||
)
|
||||
),
|
||||
case_insensitive=True,
|
||||
search_dir="/".join(["bios", *folders]),
|
||||
passes_to=f"{_config_name(src, method, writer)} {target}, only if the file exists",
|
||||
fails_when_missing=f"{key} not written",
|
||||
depends_on_option=", ".join(conditions) or None,
|
||||
)
|
||||
|
||||
|
||||
def guarded_writes_in(src: Source, emulator: str) -> list[Record]:
|
||||
"""Every guarded write of one generator, then the keys given the bios folder."""
|
||||
found = [_guarded_write(src, emulator, guard) for guard in _GUARD.finditer(src.text)]
|
||||
records = [record for record in found if record is not None]
|
||||
if not records:
|
||||
raise LauncherPatternMissing(src.path, "File.Exists guarded WriteValue")
|
||||
return records + _root_writes(src, emulator)
|
||||
|
||||
|
||||
def guarded_writes(sources: dict[str, Source]) -> list[Record]:
|
||||
"""One fixed name per configuration key, written only when the file exists."""
|
||||
return [
|
||||
record
|
||||
for path, emulator in GUARDED_WRITES.items()
|
||||
for record in guarded_writes_in(sources[path], emulator)
|
||||
]
|
||||
|
||||
|
||||
EXTRACTORS: tuple[Callable[[dict[str, Source]], list[Record]], ...] = (
|
||||
amiberry,
|
||||
ares,
|
||||
duckstation,
|
||||
guarded_writes,
|
||||
hatari,
|
||||
hatari_libretro,
|
||||
mesen,
|
||||
|
||||
@@ -201,6 +201,94 @@ HATARI_FEATURES = """
|
||||
"""
|
||||
|
||||
|
||||
KEGA = """
|
||||
private void SetupConfiguration(string path, string system)
|
||||
{
|
||||
string iniFile = Path.Combine(path, "Fusion.ini");
|
||||
|
||||
using (var ini = IniFile.FromFile(iniFile))
|
||||
{
|
||||
string biosPath = AppConfig.GetFullPath("bios");
|
||||
if (!Directory.Exists(biosPath)) try { Directory.CreateDirectory(biosPath); }
|
||||
catch { }
|
||||
|
||||
ini.WriteValue("", "GenesisBIOS", biosPath);
|
||||
|
||||
// BIOS
|
||||
string euBios = Path.Combine(biosPath, "bios_CD_E.bin");
|
||||
if (File.Exists(euBios))
|
||||
ini.WriteValue("", "SCDEURBIOS", euBios);
|
||||
|
||||
string bios32Xg = Path.Combine(biosPath, "32X_G_BIOS.BIN");
|
||||
if (File.Exists(bios32Xg))
|
||||
ini.WriteValue("", "32XM68KBIOS", bios32Xg);
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
JGENESIS = """
|
||||
private void SetupTomlConfiguration(string path)
|
||||
{
|
||||
string settingsFile = Path.Combine(path, "jgenesis-config.toml");
|
||||
using (IniFileJGenesis ini = new IniFileJGenesis(settingsFile, IniOptionsJGenesis.UseSpaces))
|
||||
{
|
||||
ConfigureGenesis(ini, system);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureGenesis(IniFileJGenesis ini, string system)
|
||||
{
|
||||
if (system != "genesis" && system != "sega_cd")
|
||||
return;
|
||||
|
||||
if (system == "sega_cd")
|
||||
{
|
||||
string segaCdBios = Path.Combine(AppConfig.GetFullPath("bios"), "bios_CD_U.bin");
|
||||
|
||||
ini.WriteValue("sega_cd", "per_region_bios", "true");
|
||||
|
||||
if (File.Exists(segaCdBios))
|
||||
ini.WriteValue("sega_cd", "bios_path", "'" + segaCdBios + "'");
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
MELONDS = """
|
||||
private void SetupConfiguration(string path, string rom)
|
||||
{
|
||||
string settingsFile = Path.Combine(path, "melonDS.toml");
|
||||
|
||||
using (IniTomlFile ini = IniTomlFile.FromFile(settingsFile, IniTomlOptions.UseSpaces))
|
||||
{
|
||||
if (SystemConfig.isOptSet("melonds_externalBIOS") && SystemConfig.getOptBoolean("melonds_externalBIOS"))
|
||||
{
|
||||
string bios7 = Path.Combine(AppConfig.GetFullPath("bios"), "bios7.bin");
|
||||
|
||||
if (File.Exists(bios7))
|
||||
ini.WriteValue("DS", "BIOS7Path", "\\"" + bios7.Replace("\\\\", "/") + "\\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# Kronos: the path is replaced by an option before the check, so the name
|
||||
# checked is not the literal.
|
||||
REASSIGNED = """
|
||||
private void SetupConfiguration(string path)
|
||||
{
|
||||
string conf = Path.Combine(path, "kronos.ini");
|
||||
using (var ini = IniFile.FromFile(conf))
|
||||
{
|
||||
string bios = Path.Combine(AppConfig.GetFullPath("bios"), "saturn_bios.bin");
|
||||
if (SystemConfig.isOptSet("kronos_force_bios"))
|
||||
bios = Path.Combine(AppConfig.GetFullPath("bios"), SystemConfig["kronos_force_bios"]);
|
||||
if (File.Exists(bios))
|
||||
ini.WriteValue("1.0", "General\\\\Bios", bios.Replace("\\\\", "/"));
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def _psx_sources() -> dict[str, rl.Source]:
|
||||
return _sources(
|
||||
**{
|
||||
@@ -314,6 +402,68 @@ class NamesInLauncherOrder(unittest.TestCase):
|
||||
self.assertEqual(option["search_dir"], "bios/hatari/tos")
|
||||
|
||||
|
||||
def _one(text: str, emulator: str, path: str = f"{G}/X.Generator.cs") -> list[dict]:
|
||||
return rl.guarded_writes_in(_sources(**{path: text})[path], emulator)
|
||||
|
||||
|
||||
class GuardedConfigWrites(unittest.TestCase):
|
||||
def test_kega_fusion_one_record_per_key(self):
|
||||
records = _one(KEGA, "kega-fusion")
|
||||
fixed = [r for r in records if r["kind"] == "fixed_name"]
|
||||
self.assertEqual(
|
||||
[(r["names"], r["passes_to"], r["fails_when_missing"]) for r in fixed],
|
||||
[
|
||||
(["bios_CD_E.bin"], "Fusion.ini SCDEURBIOS, only if the file exists",
|
||||
"SCDEURBIOS not written"),
|
||||
(["32X_G_BIOS.BIN"], "Fusion.ini 32XM68KBIOS, only if the file exists",
|
||||
"32XM68KBIOS not written"),
|
||||
],
|
||||
)
|
||||
self.assertEqual(fixed[0]["source_ref"], f"{G}/X.Generator.cs:14-16")
|
||||
self.assertTrue(all(r["search_dir"] == "bios" and r["case_insensitive"] for r in fixed))
|
||||
self.assertNotIn("depends_on_option", fixed[0])
|
||||
|
||||
def test_kega_fusion_genesis_key_receives_the_folder(self):
|
||||
(folder,) = [r for r in _one(KEGA, "kega-fusion") if r["kind"] == "passes_path"]
|
||||
self.assertEqual(folder["names"], [])
|
||||
self.assertEqual(folder["passes_to"], "Fusion.ini GenesisBIOS = bios")
|
||||
self.assertEqual(folder["source_ref"], f"{G}/X.Generator.cs:7, 11")
|
||||
|
||||
def test_early_return_and_enclosing_if_are_conditions(self):
|
||||
(record,) = _one(JGENESIS, "jgenesis")
|
||||
self.assertEqual(
|
||||
record["depends_on_option"],
|
||||
'system == "genesis" || system == "sega_cd", system == "sega_cd"',
|
||||
)
|
||||
self.assertEqual(
|
||||
record["passes_to"], "jgenesis-config.toml [sega_cd] bios_path, only if the file exists"
|
||||
)
|
||||
self.assertEqual(record["source_ref"], f"{G}/X.Generator.cs:12-13, 17, 21-22")
|
||||
|
||||
def test_option_switch_is_named(self):
|
||||
(record,) = _one(MELONDS, "melonds")
|
||||
self.assertEqual(record["names"], ["bios7.bin"])
|
||||
self.assertEqual(record["depends_on_option"], "melonds_externalBIOS on")
|
||||
self.assertEqual(
|
||||
record["passes_to"], "melonDS.toml [DS] BIOS7Path, only if the file exists"
|
||||
)
|
||||
|
||||
def test_reassigned_path_is_not_a_fixed_name(self):
|
||||
with self.assertRaises(rl.LauncherPatternMissing):
|
||||
_one(REASSIGNED, "kronos")
|
||||
|
||||
def test_write_inside_a_loop_is_not_read(self):
|
||||
looped = KEGA.replace("// BIOS", "foreach (var x in xs)\n {").replace(
|
||||
'"32XM68KBIOS", bios32Xg);', '"32XM68KBIOS", bios32Xg);\n }'
|
||||
)
|
||||
with self.assertRaises(rl.LauncherPatternMissing):
|
||||
_one(looped, "kega-fusion")
|
||||
|
||||
def test_pinned_kega_fusion_generator_is_read(self):
|
||||
self.assertIn(f"{G}/KegaFusion.Generator.cs", rl.SOURCE_FILES)
|
||||
self.assertEqual(rl.GUARDED_WRITES[f"{G}/KegaFusion.Generator.cs"], "kega-fusion")
|
||||
|
||||
|
||||
class AbsentConstructStopsTheScrape(unittest.TestCase):
|
||||
def test_each_extractor_raises_on_an_empty_source(self):
|
||||
empty = {path: rl.Source(path, "") for path in rl.SOURCE_FILES}
|
||||
|
||||
Reference in new issue
Block a user