cdp_launch_game.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. """Post-login: find 玖扬复古176 on the 996box main page and click 启动.
  2. Assumes:
  3. - 996box logged in (window = main page)
  4. - capture running in background
  5. - CDP on 9222
  6. Drives the REAL launch: game page -> 启动 button -> cq.exe -> cq2.exe -> TCP.
  7. """
  8. import asyncio
  9. import base64
  10. import json
  11. import subprocess
  12. import sys
  13. import time
  14. import httpx
  15. import websockets
  16. CDP = "http://127.0.0.1:9222"
  17. async def get_targets():
  18. async with httpx.AsyncClient(timeout=5) as c:
  19. r = await c.get(f"{CDP}/json/list")
  20. return r.json()
  21. class Page:
  22. def __init__(self, ws):
  23. self.ws = ws
  24. self.mid = 0
  25. async def call(self, method, params=None):
  26. self.mid += 1
  27. await self.ws.send(json.dumps({"id": self.mid, "method": method, "params": params or {}}))
  28. while True:
  29. msg = json.loads(await self.ws.recv())
  30. if msg.get("id") == self.mid:
  31. return msg.get("result", {})
  32. async def eval(self, expr):
  33. r = await self.call("Runtime.evaluate", {"expression": expr, "returnByValue": True})
  34. return r.get("result", {}).get("value")
  35. async def click(self, x, y):
  36. await self.call("Input.dispatchMouseEvent", {"type": "mousePressed", "x": x, "y": y, "button": "left", "clickCount": 1, "buttons": 1})
  37. await asyncio.sleep(0.05)
  38. await self.call("Input.dispatchMouseEvent", {"type": "mouseReleased", "x": x, "y": y, "button": "left", "clickCount": 1, "buttons": 0})
  39. def cq_pids():
  40. try:
  41. out = subprocess.run(
  42. ["powershell", "-NoProfile", "-Command",
  43. "Get-Process | Where-Object { $_.Name -match '^cq' } | ForEach-Object { \"$($_.Name):$($_.Id):$($_.MainWindowTitle)\" }"],
  44. capture_output=True, text=True, timeout=5)
  45. return [l.strip() for l in out.stdout.splitlines() if l.strip()] or []
  46. except Exception:
  47. return []
  48. def gateway_conns():
  49. try:
  50. out = subprocess.run(
  51. ["powershell", "-NoProfile", "-Command",
  52. "netstat -ano | Select-String ':12000'"],
  53. capture_output=True, text=True, timeout=5)
  54. return [l.strip() for l in out.stdout.splitlines() if l.strip()]
  55. except Exception:
  56. return []
  57. def capture_lines():
  58. try:
  59. out = subprocess.run(
  60. ["powershell", "-NoProfile", "-Command",
  61. "(Get-Content 'D:\\传奇\\cq2_sdk\\real_login.jsonl' -ErrorAction SilentlyContinue | Measure-Object).Count"],
  62. capture_output=True, text=True, timeout=5)
  63. return int(out.stdout.strip() or 0)
  64. except Exception:
  65. return 0
  66. async def main():
  67. targets = await get_targets()
  68. print("=== CDP targets ===")
  69. for t in targets:
  70. print(f" [{t['type']}] {t.get('title', '')[:35]:<35} {t['url'][:70]}")
  71. # find the main web page (not login, not file://)
  72. page_t = next(
  73. (t for t in targets if t["type"] == "page"
  74. and ("hzwlan" in t["url"] or "996box.com" in t["url"] or "h5qfdt" in t["url"])
  75. and "login" not in t["url"]),
  76. None,
  77. )
  78. if not page_t:
  79. print("no game page found (still on login?)")
  80. return 1
  81. print(f"\nusing: {page_t['url'][:70]}")
  82. async with websockets.connect(page_t["webSocketDebuggerUrl"], max_size=10**7) as ws:
  83. page = Page(ws)
  84. # Page state
  85. state = await page.eval("""
  86. JSON.stringify({
  87. url: location.href.slice(0,80),
  88. title: document.title,
  89. bodyHead: document.body ? document.body.innerText.slice(0,200) : ''
  90. })
  91. """)
  92. try:
  93. st = json.loads(state or "{}")
  94. print(f"url: {st.get('url')}")
  95. print(f"title: {st.get('title')}")
  96. print(f"body: {st.get('bodyHead','')[:150]}")
  97. except Exception:
  98. print(state[:200])
  99. # Search for the game (玖扬复古176 / gameId 206923)
  100. game = await page.eval("""
  101. (function() {
  102. const results = [];
  103. // search game cards / names
  104. const els = document.querySelectorAll('*');
  105. for (const el of els) {
  106. if (el.children.length <= 2) {
  107. const t = (el.innerText || '').trim();
  108. if (t && (t.includes('玖扬') || t.includes('复古176')) && t.length < 60 && !results.some(r => r.text === t)) {
  109. const r = el.getBoundingClientRect();
  110. if (r.width > 0 && r.height > 0) {
  111. results.push({text: t, x: r.x + r.width/2, y: r.y + r.height/2, w: r.width, h: r.height});
  112. }
  113. }
  114. }
  115. if (results.length >= 8) break;
  116. }
  117. return JSON.stringify(results);
  118. })()
  119. """)
  120. print(f"\n=== 玖扬/复古 matches ===")
  121. try:
  122. games = json.loads(game or "[]")
  123. if not games:
  124. print(" none found on this page — searching input?")
  125. # maybe need to search
  126. sr = await page.eval("""
  127. (function() {
  128. const inp = document.querySelector('input[type=text], input[placeholder*=搜索], input[placeholder*=游戏]');
  129. if (!inp) return 'no search input';
  130. return JSON.stringify({ph: inp.placeholder, x: inp.getBoundingClientRect().x, y: inp.getBoundingClientRect().y});
  131. })()
  132. """)
  133. print(f" search input: {sr}")
  134. for g in games:
  135. print(f" '{g['text'][:30]}' @ ({g['x']:.0f},{g['y']:.0f}) {g['w']:.0f}x{g['h']:.0f}")
  136. except Exception:
  137. print(game[:300])
  138. # Screenshot for the record
  139. shot = await page.call("Page.captureScreenshot", {"format": "png"})
  140. if shot.get("data"):
  141. with open("cdp_main_page.png", "wb") as f:
  142. f.write(base64.b64decode(shot["data"]))
  143. print("\nscreenshot: cdp_main_page.png")
  144. # If a game element was found, click it then find 启动
  145. try:
  146. games = json.loads(game or "[]")
  147. except Exception:
  148. games = []
  149. if games:
  150. g0 = games[0]
  151. print(f"\nclicking game card '{g0['text'][:20]}'...")
  152. await page.click(g0["x"], g0["y"])
  153. await asyncio.sleep(3)
  154. # look for 启动 button
  155. launch = await page.eval("""
  156. (function() {
  157. const btns = document.querySelectorAll('button, [class*=btn], [class*=start], [class*=launch]');
  158. for (const b of btns) {
  159. const t = (b.innerText || '').trim();
  160. if (/^(启动|开始游戏|进入游戏|继续游戏)$/.test(t) && b.offsetParent) {
  161. const r = b.getBoundingClientRect();
  162. return JSON.stringify({text: t, x: r.x + r.width/2, y: r.y + r.height/2});
  163. }
  164. }
  165. return null;
  166. })()
  167. """)
  168. if launch:
  169. l = json.loads(launch)
  170. print(f"LAUNCH button found: '{l['text']}' @ ({l['x']:.0f},{l['y']:.0f})")
  171. await page.click(l["x"], l["y"])
  172. print("*** 启动 CLICKED — cq.exe spawning ***")
  173. else:
  174. print("no 启动 button yet — page may need navigation")
  175. # Monitor game launch + traffic
  176. print("\n=== monitoring 180s ===")
  177. t0 = time.time()
  178. while time.time() - t0 < 180:
  179. await asyncio.sleep(5)
  180. el = int(time.time() - t0)
  181. procs = cq_pids()
  182. conns = gateway_conns()
  183. lines = capture_lines()
  184. flags = []
  185. if procs:
  186. flags.append("procs=" + ",".join(p.split(":")[0] for p in procs))
  187. if conns:
  188. flags.append(f"gw_conns={len(conns)}")
  189. if lines:
  190. flags.append(f"cap={lines}")
  191. print(f" t+{el:>3}s {' '.join(flags) or '...'}")
  192. if lines >= 2:
  193. print("\n*** TRAFFIC CAPTURED ***")
  194. return 0
  195. if procs and conns and el > 40:
  196. print("\n*** game connected; letting capture accumulate 30s ***")
  197. await asyncio.sleep(30)
  198. return 0
  199. return 1
  200. sys.exit(asyncio.run(main()))