#!/usr/bin/env python3 from __future__ import annotations import argparse import json import os import subprocess import sys from pathlib import Path def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Temporarily switch selected legacy client packs to .m2p and run the Wine login smoke test." ) parser.add_argument( "--runtime-root", type=Path, required=True, help="Client runtime root containing pack/ and config/.", ) parser.add_argument( "--client-repo", type=Path, required=True, help="Path to m2dev-client-src checkout.", ) parser.add_argument( "--master-key", type=Path, required=True, help="Path to m2pack runtime master key file.", ) parser.add_argument( "--key-id", type=str, default="1", help="Runtime key_id to inject into the client.", ) parser.add_argument( "--timeout", type=int, default=20, help="Wine run timeout in seconds.", ) parser.add_argument( "--pack", dest="packs", action="append", required=True, help="Pack basename to test as .m2p. Repeat for multiple packs.", ) parser.add_argument( "--json", action="store_true", help="Emit machine-readable JSON result.", ) return parser.parse_args() def activate_pack(pack_dir: Path, pack_name: str, moved: list[tuple[Path, Path]]) -> None: offsingle = pack_dir / f"{pack_name}.m2p.offsingle" m2p = pack_dir / f"{pack_name}.m2p" legacy = pack_dir / f"{pack_name}.pck" legacy_backup = pack_dir / f"{pack_name}.pck.testbak" if offsingle.exists(): offsingle.rename(m2p) moved.append((m2p, offsingle)) if legacy.exists(): legacy.rename(legacy_backup) moved.append((legacy_backup, legacy)) def restore_moves(moved: list[tuple[Path, Path]]) -> None: for src, dst in reversed(moved): if src.exists(): src.rename(dst) def main() -> int: args = parse_args() runtime_root = args.runtime_root.resolve() client_repo = args.client_repo.resolve() pack_dir = runtime_root / "pack" run_script = client_repo / "scripts" / "run-wine-headless.sh" build_bin = client_repo / "build-mingw64-lld" / "bin" log_dir = build_bin / "log" if not pack_dir.is_dir(): raise SystemExit(f"runtime pack dir not found: {pack_dir}") if not run_script.is_file(): raise SystemExit(f"wine runner not found: {run_script}") if not (build_bin / "Metin2_RelWithDebInfo.exe").is_file(): raise SystemExit(f"client binary not found: {build_bin / 'Metin2_RelWithDebInfo.exe'}") moved: list[tuple[Path, Path]] = [] try: for pack_name in args.packs: activate_pack(pack_dir, pack_name, moved) log_dir.mkdir(parents=True, exist_ok=True) for file_path in log_dir.glob("*.txt"): file_path.unlink(missing_ok=True) for file_path in [build_bin / "syserr.txt", build_bin / "ErrorLog.txt"]: file_path.unlink(missing_ok=True) env = os.environ.copy() env["M2PACK_MASTER_KEY_HEX"] = args.master_key.read_text(encoding="utf-8").strip() env["M2PACK_KEY_ID"] = args.key_id env["M2_TIMEOUT"] = str(args.timeout) env.setdefault("WINEDEBUG", "-all") completed = subprocess.run( [str(run_script), str(build_bin)], cwd=client_repo, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) prototype_trace = (log_dir / "prototype_trace.txt").read_text(encoding="utf-8", errors="ignore") if (log_dir / "prototype_trace.txt").exists() else "" system_trace = (log_dir / "system_py_trace.txt").read_text(encoding="utf-8", errors="ignore") if (log_dir / "system_py_trace.txt").exists() else "" syserr = (build_bin / "syserr.txt").read_text(encoding="utf-8", errors="ignore") if (build_bin / "syserr.txt").exists() else "" result = { "ok": "SetLoginPhase ok" in prototype_trace, "returncode": completed.returncode, "packs": args.packs, "prototype_tail": prototype_trace.strip().splitlines()[-10:], "system_tail": system_trace.strip().splitlines()[-10:], "syserr_tail": syserr.strip().splitlines()[-10:], } if args.json: print(json.dumps(result, indent=2)) else: print(f"packs={','.join(args.packs)} ok={result['ok']} returncode={completed.returncode}") if result["prototype_tail"]: print("prototype tail:") for line in result["prototype_tail"]: print(f" {line}") return 0 if result["ok"] else 1 finally: restore_moves(moved) if __name__ == "__main__": sys.exit(main())