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: