"""Decisive test of the 0x284 fix: with TWO pilots (so the designation sites are reachable), press the target-designation keys and confirm the write lands on objectiveMech while deathPending stays 0. Before the fix, `*(int*)(pilot+0x284) = target` stored a Mech* into deathPending and silently disabled the pilot's respawn for the rest of the process. Kills only the PIDs it spawns. """ import ctypes import os import re import subprocess import time KEYUP = 0x0002 user32 = ctypes.windll.user32 REPO = r"C:\git\bt411" CONTENT = os.path.join(REPO, "content") EXE = os.path.join(REPO, "build", "Release", "btl4.exe") PORT = 2900 OUT = os.path.join(REPO, "scratchpad", "des") os.makedirs(OUT, exist_ok=True) for f in os.listdir(OUT): try: os.remove(os.path.join(OUT, f)) except Exception: pass procs = [] base = dict(os.environ) base.pop("BT_NO_PILOTLIST", None) p = subprocess.Popen(["python", os.path.join(REPO, "tools", "btconsole.py"), "--relay", str(PORT), "MP_BHMC.EGG", "--bind", "127.0.0.1"], cwd=CONTENT, env=base, stdout=open(os.path.join(OUT, "relay.log"), "w"), stderr=subprocess.STDOUT) procs.append(p); print("relay pid", p.pid) time.sleep(3) for i, tag in ((2, "1602"), (1, "1502")): e = dict(base) e.update({ "BT_RELAY": "127.0.0.1:%d" % PORT, "BT_SELF": "127.0.0.1:%s" % tag, "BT_START_INSIDE": "1", "BT_SCORE_LOG": "1", "BT_LOG": os.path.join(OUT, "pod%d.log" % i), }) if i == 1: e["BT_KEY_NOFOCUS"] = "1" port = 1501 if i == 1 else 1601 q = subprocess.Popen([EXE, "-egg", "MP_BHMC.EGG", "-net", str(port)], cwd=CONTENT, env=e, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) procs.append(q); print("pod%d pid %d" % (i, q.pid)) time.sleep(1.5) A = os.path.join(OUT, "pod1.log") try: # wait until pod1's roster has both pilots (designation needs pilotCount > 1) deadline = time.time() + 240 while time.time() < deadline: if os.path.exists(A): t = open(A, errors="replace").read() if re.search(r"roster built: [2-9]", t): break time.sleep(3) print("roster has 2+ pilots; sending designations") time.sleep(4) for rnd in range(4): for ch in "tyuio": vk = ord(ch.upper()) user32.keybd_event(vk, 0, 0, 0); time.sleep(0.09) user32.keybd_event(vk, 0, KEYUP, 0); time.sleep(0.12) time.sleep(3) print(" round %d sent" % (rnd + 1)) finally: for q in procs: try: q.terminate() except Exception: pass time.sleep(2) t = open(A, errors="replace").read() if os.path.exists(A) else "" des = re.findall(r"^\[score\] designate:.*$", t, re.M) print("\n=========== RESULT ===========") print("roster lines :", re.findall(r"roster built: \d+", t)[-1:] or "none") print("designate lines :", len(des)) for l in des[:8]: print(" ", l) bad = [l for l in des if "LATCH SET" in l] print("\nlatch-corrupting designations:", len(bad)) print("VERDICT:", "PASS -- designation no longer touches deathPending" if des and not bad else ("FAIL -- latch still being set" if bad else "INCONCLUSIVE -- designation never ran"))