"""Correct game launch: use the KNOWN enterGameClient HTTP path from our SDK (same as the real client's 启动 button) targeted at gameId=206923 + a real 玖扬 serverid, PLUS CDP-click verification. But first: find the true 玖扬 game detail page via the search flow (click suggestion properly this time), because EnterGameClient only records the launch — cq.exe is spawned by the Electron side via the game page's IPC. Robust plan: 1. CDP: focus search input, select-all, type 玖扬复古176 with REAL key events 2. Wait for suggestion dropdown; click the suggestion whose text EXACTLY starts with 玖扬 (verify its gameId in its attributes/parent) 3. On the detail page: verify the game name shows 玖扬复古176 before clicking 启动 4. Background capture running the whole time """ import asyncio import base64 import json import subprocess import sys import time import httpx import websockets CDP = "http://127.0.0.1:9222" GAME = "玖扬复古176" async def get_targets(): async with httpx.AsyncClient(timeout=5) as c: r = await c.get(f"{CDP}/json/list") return r.json() class Page: def __init__(self, ws): self.ws = ws self.mid = 0 async def call(self, method, params=None): self.mid += 1 await self.ws.send(json.dumps({"id": self.mid, "method": method, "params": params or {}})) while True: m = json.loads(await self.ws.recv()) if m.get("id") == self.mid: return m.get("result", {}) async def eval(self, expr): r = await self.call("Runtime.evaluate", {"expression": expr, "returnByValue": True}) return r.get("result", {}).get("value") async def click(self, x, y): await self.call("Input.dispatchMouseEvent", {"type": "mousePressed", "x": x, "y": y, "button": "left", "clickCount": 1, "buttons": 1}) await asyncio.sleep(0.06) await self.call("Input.dispatchMouseEvent", {"type": "mouseReleased", "x": x, "y": y, "button": "left", "clickCount": 1, "buttons": 0}) async def key(self, key, code, vk, text=None): p = {"type": "keyDown", "key": key, "code": code, "windowsVirtualKeyCode": vk} if text: p["text"] = text await self.call("Input.dispatchKeyEvent", p) await asyncio.sleep(0.03) p["type"] = "keyUp" await self.call("Input.dispatchKeyEvent", p) async def shot(self, name): r = await self.call("Page.captureScreenshot", {"format": "png"}) if r.get("data"): with open(name, "wb") as f: f.write(base64.b64decode(r["data"])) def poll(): try: out = subprocess.run( ["powershell", "-NoProfile", "-Command", "$p = Get-Process | Where-Object {$_.Name -match '^cq'} | ForEach-Object { $_.Name + ':' + $_.Id };" "$g = (netstat -ano | Select-String ':12000' | Measure-Object).Count;" "$c = (Get-Content 'D:\\传奇\\cq2_sdk\\real_login.jsonl' -ErrorAction SilentlyContinue | Measure-Object).Count;" "ConvertTo-Json @{procs=$p; gw=$g; cap=$c}"], capture_output=True, text=True, timeout=8) return json.loads(out.stdout or "{}") except Exception: return {} async def main(): targets = await get_targets() home = next((t for t in targets if "homePage" in t.get("url", "")), None) if not home: print("no home page") return 1 async with websockets.connect(home["webSocketDebuggerUrl"], max_size=10**7) as ws: page = Page(ws) # ---- 1. search flow ---- inp = await page.eval(""" (function() { const inps = document.querySelectorAll('input'); for (const i of inps) { const r = i.getBoundingClientRect(); if (r.width > 100 && r.y < 150 && i.offsetParent) { return JSON.stringify({x: r.x + 30, y: r.y + r.height/2}); } } return null; })() """) if not inp: print("no search input") return 1 pos = json.loads(inp) print(f"search input @ ({pos['x']:.0f},{pos['y']:.0f})") # focus + select all + backspace (clear old text) await page.click(pos["x"], pos["y"]) await asyncio.sleep(0.3) await page.key("a", "KeyA", 65) await asyncio.sleep(0.1) await page.key("Backspace", "Backspace", 8) await asyncio.sleep(0.3) # type via IME-commit style: set value + input event per char # (CDP can't do real IME, but the page's input handler reads .value) for ch in GAME: await page.eval(f""" (function() {{ const i = document.activeElement; if (i && i.tagName === 'INPUT') {{ const set = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; set.call(i, i.value + '{ch}'); i.dispatchEvent(new Event('input', {{bubbles: true}})); i.dispatchEvent(new KeyboardEvent('keyup', {{key: '{ch}', bubbles: true}})); }} }})() """) await asyncio.sleep(0.12) val = await page.eval("document.activeElement && document.activeElement.value") print(f"typed: {val}") # press Enter to commit search await page.key("Enter", "Enter", 13, text="\r") await asyncio.sleep(4) url = await page.eval("location.href") print(f"url after Enter: {url[:80]}") await page.shot("search_commit.png") # ---- 2. find the EXACT game card ---- card = await page.eval(r""" (function() { // search-results page cards; match text EXACTLY on 玖扬复古176 const els = document.querySelectorAll('*'); let best = null; for (const el of els) { if (el.children.length <= 4 && el.offsetParent) { const t = (el.innerText || '').trim(); if (t.replace(/\s/g, '') === '玖扬复古176') { const r = el.getBoundingClientRect(); if (r.width > 60 && r.height > 30 && (!best || r.width > best.w)) { best = {x: r.x + r.width/2, y: r.y + r.height/2, w: r.width, h: r.height}; } } } } return best ? JSON.stringify(best) : null; })() """) if not card: # maybe still on home; check any exact-match anywhere print("no exact card; screenshot for diagnosis") body = await page.eval("document.body.innerText.slice(0, 400)") print(f"body head: {body}") return 1 c = json.loads(card) print(f"*** EXACT 玖扬复古176 card @ ({c['x']:.0f},{c['y']:.0f}) ***") await page.shot("target_card.png") # ---- 3. click card -> detail page ---- await page.click(c["x"], c["y"]) await asyncio.sleep(4) url2 = await page.eval("location.href") title = await page.eval("document.title") print(f"after card click: url={url2[:80]} title={title}") # verify we're on 玖扬's page verify = await page.eval(""" document.body.innerText.includes('玖扬复古176') ? 'YES' : document.body.innerText.slice(0,200) """) print(f"page contains 玖扬复古176: {verify if verify == 'YES' else str(verify)[:150]}") if verify != "YES": print("WRONG PAGE — aborting to avoid another misfire") await page.shot("wrong_page.png") return 1 # ---- 4. find + click 启动 on THE GAME'S OWN PAGE ---- for attempt in range(4): btn = await page.eval(r""" (function() { const btns = document.querySelectorAll('button, [class*=btn]'); for (const b of btns) { const t = (b.innerText || '').trim(); if (/^(启动|开始游戏|进入游戏)$/.test(t) && b.offsetParent) { const r = b.getBoundingClientRect(); return JSON.stringify({text: t, x: r.x + r.width/2, y: r.y + r.height/2}); } } return null; })() """) if btn: b = json.loads(btn) print(f"启动 button: '{b['text']}' @ ({b['x']:.0f},{b['y']:.0f})") await page.shot("before_launch_target.png") await page.click(b["x"], b["y"]) print("*** 启动 CLICKED (on 玖扬复古176's page) ***") break await asyncio.sleep(2.5) else: print("no 启动 button on detail page") await page.shot("no_launch_on_detail.png") return 1 # ---- 5. monitor ---- print("\n=== monitoring 240s ===") t0 = time.time() ok = False while time.time() - t0 < 240: await asyncio.sleep(5) el = int(time.time() - t0) st = poll() procs = st.get("procs", []) gw = st.get("gw", 0) cap = st.get("cap", 0) flags = [] if procs: flags.append("procs=" + ",".join(p.split(":")[0] for p in (procs if isinstance(procs, list) else [procs]))) if gw: flags.append(f"gw={gw}") if cap: flags.append(f"cap={cap}") print(f" t+{el:>3}s {' '.join(flags) or '...'}") if cap >= 2: ok = True print("\n*** CAPTURED REAL 玖扬 LOGIN TRAFFIC ***") await asyncio.sleep(20) break if procs and gw and el > 60: ok = True print("\n*** 玖扬 game connected to gateway ***") await asyncio.sleep(30) break return 0 if ok else 1 sys.exit(asyncio.run(main()))