mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-11 05:53:23 -05:00
Loading the emulator profiles is the most expensive step of every command here, and all forty call sites used the pure-Python scanner while libyaml sat unused in the same wheel. One shared yaml_load picks the C loader when pyyaml ships it: the 375 profiles parse in 0.18s instead of 1.39s, and verify --platform retroarch drops from 2.17s to 0.73s. The loader class is the same restricted one safe_load uses. es_bios.xml was parsed straight from the network while install.py already refused a document declaring entities; both now share one guard. Scrapers reach it through a single path bootstrap in the package rather than two ad-hoc ones.
92 lines
2.5 KiB
Python
92 lines
2.5 KiB
Python
"""Parser for Logiqx XML DAT format.
|
|
|
|
Parses files like No-Intro daily packs and TOSEC releases which use:
|
|
<datafile>
|
|
<header><name>...</name><version>...</version></header>
|
|
<game name="[BIOS] Game Boy Advance (World)">
|
|
<description>...</description>
|
|
<rom name="..." size="16384" crc="..." md5="..." sha1="..."/>
|
|
</game>
|
|
</datafile>
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import xml.etree.ElementTree as ET
|
|
from dataclasses import dataclass, field
|
|
|
|
from common import parse_untrusted_xml
|
|
|
|
|
|
@dataclass
|
|
class LogiqxRom:
|
|
"""A ROM entry from a Logiqx DAT file."""
|
|
|
|
game: str
|
|
description: str
|
|
name: str
|
|
size: int
|
|
crc32: str
|
|
md5: str
|
|
sha1: str
|
|
|
|
|
|
@dataclass
|
|
class LogiqxDat:
|
|
"""A parsed Logiqx DAT file."""
|
|
|
|
name: str = ""
|
|
version: str = ""
|
|
roms: list[LogiqxRom] = field(default_factory=list)
|
|
|
|
|
|
def parse_logiqx(content: str | bytes) -> LogiqxDat:
|
|
"""Parse Logiqx XML DAT content.
|
|
|
|
Handles both <game> and <machine> entries, and games with
|
|
multiple <rom> children. Content declaring XML entities is
|
|
rejected: DAT files never define them, and expanding entities
|
|
from untrusted packs opens entity-expansion attacks.
|
|
"""
|
|
root = parse_untrusted_xml(content, "DAT files")
|
|
dat = LogiqxDat()
|
|
|
|
header = root.find("header")
|
|
if header is not None:
|
|
dat.name = header.findtext("name", "").strip()
|
|
dat.version = header.findtext("version", "").strip()
|
|
|
|
for game in list(root.iter("game")) + list(root.iter("machine")):
|
|
game_name = game.get("name", "")
|
|
description = game.findtext("description", "").strip()
|
|
for rom in game.iter("rom"):
|
|
name = rom.get("name", "")
|
|
if not name:
|
|
continue
|
|
try:
|
|
size = int(rom.get("size", "0"))
|
|
except ValueError:
|
|
size = 0
|
|
dat.roms.append(
|
|
LogiqxRom(
|
|
game=game_name,
|
|
description=description or game_name,
|
|
name=name,
|
|
size=size,
|
|
crc32=rom.get("crc", "").lower(),
|
|
md5=rom.get("md5", "").lower(),
|
|
sha1=rom.get("sha1", "").lower(),
|
|
)
|
|
)
|
|
|
|
return dat
|
|
|
|
|
|
def validate_logiqx_format(content: str | bytes) -> bool:
|
|
"""Validate that content parses as a Logiqx DAT with rom entries."""
|
|
try:
|
|
dat = parse_logiqx(content)
|
|
except ET.ParseError:
|
|
return False
|
|
return bool(dat.roms)
|