diff --git a/docs/project-reorganization.md b/docs/project-reorganization.md index 2ec9623..aad6f5d 100644 --- a/docs/project-reorganization.md +++ b/docs/project-reorganization.md @@ -146,6 +146,10 @@ host-зависимости и проверять их перед упаковк разные локальные диски. Не требовать закрывать все чужие процессы MAME по общему шаблону: предотвращать конфликт только за конкретный изменяемый носитель/IPC-сеанс. Закрытие MAME не должно терять лог и снимки теста. +Обычный `run_sprinter_mame.py` перед стартом копирует `cfg` и `nvram` из +`MAME_HOME` в каталог сессии, затем включает обе клавиатуры в копии +`sprinter.cfg`, сохраняя остальные настройки. Исходная установка не меняется; +отладчик DAP по-прежнему начинает с чистого изолированного состояния. ## Граница MAME и отладки diff --git a/tests/sdbg/test_launcher.py b/tests/sdbg/test_launcher.py index 88ce4ca..03b70b2 100644 --- a/tests/sdbg/test_launcher.py +++ b/tests/sdbg/test_launcher.py @@ -60,5 +60,25 @@ class LauncherTests(unittest.TestCase): self.assertEqual(keyboards, {':': '1', ':kbd:ms_naturl': '1'}) + def test_existing_config_is_merged_not_replaced(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory)/'sprinter.cfg' + path.write_text('\ufeff\n' + '' + '' + '' + '' + '', encoding='utf-8') + write_keyboard_config(Path(directory)) + root = ET.parse(path).getroot() + system = root.find('system') + self.assertEqual([item.tag for item in system.find('input')], + ['keyboard', 'keyboard', 'port']) + keyboards = {item.attrib['tag']: item.attrib['enabled'] + for item in system.findall('input/keyboard')} + self.assertEqual(keyboards, {':': '1', ':kbd:ms_naturl': '1'}) + self.assertEqual(system.find('input/port').attrib['value'], '1') + self.assertIsNotNone(system.find('video/target')) + if __name__ == '__main__': unittest.main() diff --git a/toolchain/mame_profile.py b/toolchain/mame_profile.py index c07a915..2b4ebb2 100644 --- a/toolchain/mame_profile.py +++ b/toolchain/mame_profile.py @@ -5,6 +5,8 @@ from __future__ import annotations from dataclasses import dataclass import os from pathlib import Path +import sys +import xml.etree.ElementTree as ET @dataclass(frozen=True) @@ -77,16 +79,45 @@ def from_arguments(args) -> MameProfile: for name in names}) +KEYBOARD_TAGS = (":", ":kbd:ms_naturl") + + def write_keyboard_config(cfg_directory: Path) -> None: - """Включить обе клавиатуры Sprinter, включая прямой ввод в DSS.""" + """Включить обе клавиатуры Sprinter, включая прямой ввод в DSS. + + Если в каталоге уже лежит sprinter.cfg (например, скопированный из + MAME_HOME/cfg), остальные его настройки сохраняются, а в копии + включаются только нужные клавиатуры. + """ cfg_directory.mkdir(parents=True, exist_ok=True) - (cfg_directory / "sprinter.cfg").write_text( - '\n' - '\n' - ' \n' - ' \n' - ' \n' - ' \n' - ' \n' - ' \n' - '\n', encoding="utf-8") + path = cfg_directory / "sprinter.cfg" + root = None + if path.is_file(): + try: + root = ET.parse(path).getroot() + except ET.ParseError as error: + print(f"mame_profile: {path} не разобран ({error}), создаётся заново", + file=sys.stderr) + if root is None or root.tag != "mameconfig": + root = ET.Element("mameconfig", version="10") + system = next((item for item in root.findall("system") + if item.get("name") == "sprinter"), None) + if system is None: + system = ET.SubElement(root, "system", name="sprinter") + input_node = system.find("input") + if input_node is None: + input_node = ET.SubElement(system, "input") + keyboards = {item.get("tag"): item for item in input_node.findall("keyboard")} + for tag in KEYBOARD_TAGS: + keyboard = keyboards.get(tag) + if keyboard is None: + keyboard = ET.Element("keyboard", tag=tag) + # MAME ожидает keyboard перед port внутри input. + position = sum(1 for item in input_node if item.tag == "keyboard") + input_node.insert(position, keyboard) + keyboard.set("enabled", "1") + tree = ET.ElementTree(root) + ET.indent(tree, space=" ") + tree.write(path, encoding="utf-8", xml_declaration=True) + with path.open("a", encoding="utf-8") as stream: + stream.write("\n") diff --git a/toolchain/run_sprinter_mame.py b/toolchain/run_sprinter_mame.py index ff0a49f..624dd88 100644 --- a/toolchain/run_sprinter_mame.py +++ b/toolchain/run_sprinter_mame.py @@ -4,6 +4,7 @@ from __future__ import annotations import argparse from pathlib import Path +import shutil import subprocess import sys import tempfile @@ -43,13 +44,21 @@ def main() -> int: state = Path(directory) for name in ("cfg", "nvram", "diff", "snapshot"): (state / name).mkdir() + # Настройки и NVRAM установленной среды берутся копией: MAME + # перезаписывает их при выходе, а MAME_HOME должен остаться нетронутым. + for name in ("cfg", "nvram"): + source = profile.home / name if profile.home else None + if source and source.is_dir(): + shutil.copytree(source, state / name, dirs_exist_ok=True) snapshots = Path(args.snapshot_dir).resolve() if args.snapshot_dir else state / "snapshot" snapshots.mkdir(parents=True, exist_ok=True) write_keyboard_config(state / "cfg") command = [str(profile.binary), "sprinter", "-noreadconfig", "-rompath", str(profile.rompath), "-bios", profile.bios, "-kbd", "ms_naturl,bios=sp2k", "-video", args.video, - "-window", "-skip_gameinfo", "-beta:wd179x:0", "35hd", + "-window", "-skip_gameinfo", + "-beta:wd179x:0", "35hd", + "-beta:wd179x:1", "35hd", "-flop2", str(profile.dss_image), "-hard1", str(profile.system_hdd_image)] if "floppy" in media: