#!/usr/bin/env python3 """Compare 40250 phase switch values with the classic session gates. The Win32 source has three kinds of cases that must not be confused: phase payload handlers, transport/control packets consumed by ClassicStream, and observer-only cases that are not in the main packet registry. Compare numeric wire values so enum aliases cannot hide a missing branch. """ from __future__ import annotations import argparse import json import re from pathlib import Path import refroot # script directory is on sys.path when run directly CONTROL_VALUES = {44, 152, 153, 250, 251, 252, 253, 254, 255} OBSERVER_VALUES = {96, 97, 98} def active_lines(text: str, *, gaiden: bool = False) -> list[str]: out: list[str] = [] stack: list[bool] = [] enabled = True for line in text.splitlines(): stripped = line.strip() if stripped in {"#if defined(GAIDEN)", "#ifdef GAIDEN"}: stack.append(enabled) enabled = enabled and gaiden continue if stripped == "#ifndef GAIDEN": stack.append(enabled) enabled = enabled and not gaiden continue if stripped == "#else" and stack: parent = stack[-1] enabled = parent and not enabled continue if stripped.startswith("#endif") and stack: enabled = stack.pop() continue if enabled and not stripped.startswith("//"): out.append(line) return out def values(path: Path, prefix: str) -> dict[str, int]: result: dict[str, int] = {} pattern = re.compile(rf"\b({re.escape(prefix)}[A-Z0-9_]+)\s*=\s*(0x[0-9A-Fa-f]+|\d+)") for line in active_lines(path.read_text(errors="replace")): match = pattern.search(line) if match: result.setdefault(match.group(1), int(match.group(2), 0)) return result def function_body(text: str, name: str) -> str: match = re.search(rf"\bbool\s+{re.escape(name)}\s*\([^)]*\)\s*\{{", text) if not match: raise ValueError(f"cannot find {name}") start = match.end() depth = 1 index = start while index < len(text) and depth: if text[index] == "{": depth += 1 elif text[index] == "}": depth -= 1 index += 1 if depth: raise ValueError(f"unterminated {name}") return text[start:index - 1] def case_values(body: str, prefix: str, table: dict[str, int]) -> set[int]: result: set[int] = set() for name in re.findall(rf"\b({re.escape(prefix)}[A-Z0-9_]+)\s*:", body): if name in table: result.add(table[name]) return result def reference_phase_values(root: Path, filename: str) -> set[int]: text = (root / "UserInterface" / filename).read_text(errors="replace") enum_values = values(root / "UserInterface" / "Packet.h", "HEADER_GC_") return case_values(text, "HEADER_GC_", enum_values) def audit(repo_root: Path) -> dict: reference_root = refroot.reference_root(repo_root) wire = repo_root / "extension/src/net/classic/wire_classic.h" session_path = repo_root / "extension/src/net/classic/classic_session.cpp" reference_phases = { "login": reference_phase_values(reference_root, "PythonNetworkStreamPhaseLogin.cpp"), "select": reference_phase_values(reference_root, "PythonNetworkStreamPhaseSelect.cpp"), "loading": reference_phase_values(reference_root, "PythonNetworkStreamPhaseLoading.cpp"), "game": reference_phase_values(reference_root, "PythonNetworkStreamPhaseGame.cpp"), } current_values = values(wire, "HDR_GC_") session_text = session_path.read_text(errors="replace") current_phases = { "login": case_values(function_body(session_text, "login_phase_packet"), "HDR_GC_", current_values), "select": case_values(function_body(session_text, "select_phase_packet"), "HDR_GC_", current_values), "loading": case_values(function_body(session_text, "loading_phase_packet"), "HDR_GC_", current_values), "game": case_values(function_body(session_text, "game_phase_packet"), "HDR_GC_", current_values), } differences: dict[str, dict[str, list[int]]] = {} for phase, reference in reference_phases.items(): ignored = CONTROL_VALUES | (OBSERVER_VALUES if phase == "game" else set()) expected = reference - ignored actual = current_phases[phase] - ignored differences[phase] = { "reference_payload_not_current": sorted(expected - actual), "current_not_reference_payload": sorted(actual - expected), } status = "PASS" if all(not values for row in differences.values() for values in row.values()) else "PARTIAL" return { "status": status, "ignored_transport_control_values": sorted(CONTROL_VALUES), "ignored_observer_values": sorted(OBSERVER_VALUES), "reference_case_counts": {phase: len(items) for phase, items in reference_phases.items()}, "current_case_counts": {phase: len(items) for phase, items in current_phases.items()}, "differences": differences, } def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[4]) parser.add_argument("--strict", action="store_true") args = parser.parse_args() try: result = audit(args.repo_root.resolve()) except (OSError, ValueError) as exc: print(json.dumps({"status": "ERROR", "error": str(exc)}, indent=2, sort_keys=True)) return 2 print(json.dumps(result, indent=2, sort_keys=True)) return 1 if args.strict and result["status"] != "PASS" else 0 if __name__ == "__main__": raise SystemExit(main())