cdp_precise_launch.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. """Precise launch: find the ready_start button's REAL clickable sub-element
  2. (the '启动' text node inside it), verify game context, click, and watch the
  3. Electron ipc 'openApp' via CDP console + process spawn.
  4. """
  5. import asyncio
  6. import base64
  7. import json
  8. import subprocess
  9. import time
  10. import httpx
  11. import websockets
  12. async def main():
  13. async with httpx.AsyncClient(timeout=5) as c:
  14. r = await c.get("http://127.0.0.1:9222/json/list")
  15. det = next((t for t in r.json() if "gameDetail" in t.get("url", "")), None)
  16. if not det:
  17. print("no gameDetail target")
  18. return 1
  19. async with websockets.connect(det["webSocketDebuggerUrl"], max_size=10**7) as ws:
  20. mid = 0
  21. async def ev(expr, wait=True):
  22. nonlocal mid
  23. mid += 1
  24. await ws.send(json.dumps({"id": mid, "method": "Runtime.evaluate",
  25. "params": {"expression": expr, "returnByValue": True}}))
  26. if not wait:
  27. return None
  28. while True:
  29. m = json.loads(await ws.recv())
  30. if m.get("id") == mid:
  31. return m.get("result", {}).get("result", {}).get("value")
  32. async def click(x, y):
  33. nonlocal mid
  34. mid += 1
  35. await ws.send(json.dumps({"id": mid, "method": "Input.dispatchMouseEvent",
  36. "params": {"type": "mousePressed", "x": x, "y": y, "button": "left", "clickCount": 1, "buttons": 1}}))
  37. await asyncio.sleep(0.07)
  38. mid += 1
  39. await ws.send(json.dumps({"id": mid, "method": "Input.dispatchMouseEvent",
  40. "params": {"type": "mouseReleased", "x": x, "y": y, "button": "left", "clickCount": 1, "buttons": 0}}))
  41. # drain
  42. while True:
  43. m = json.loads(await ws.recv())
  44. if m.get("id") == mid:
  45. return
  46. # 1. Full anatomy of ready_start and its children
  47. anatomy = await ev(r"""
  48. (function() {
  49. const btn = document.querySelector('.ready_start');
  50. if (!btn) return JSON.stringify({found: false});
  51. const r = btn.getBoundingClientRect();
  52. const kids = [];
  53. btn.querySelectorAll('*').forEach(k => {
  54. const kr = k.getBoundingClientRect();
  55. if (kr.width > 20) {
  56. kids.push({
  57. tag: k.tagName,
  58. cls: (k.className || '').toString().slice(0, 35),
  59. text: (k.innerText || '').trim().slice(0, 20),
  60. x: Math.round(kr.x + kr.width/2), y: Math.round(kr.y + kr.height/2),
  61. w: Math.round(kr.width), h: Math.round(kr.height)
  62. });
  63. }
  64. });
  65. // also check Vue handlers
  66. const vueKeys = Object.keys(btn).filter(k => k.startsWith('__vue') || k.startsWith('onclick') || k.includes('click'));
  67. return JSON.stringify({
  68. found: true,
  69. rect: {x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height)},
  70. cx: Math.round(r.x + r.width/2), cy: Math.round(r.y + r.height/2),
  71. fullText: (btn.innerText || '').replace(/\n/g, ' | ').slice(0, 80),
  72. kids: kids.slice(0, 12),
  73. handlers: vueKeys.slice(0, 5)
  74. }, null, 1);
  75. })()
  76. """)
  77. print("=== ready_start anatomy ===")
  78. print(anatomy)
  79. # 2. Hook the window.raw IPC bridge to observe openApp calls
  80. await ev(r"""
  81. (function() {
  82. if (window.__hookInstalled) return 'already';
  83. window.__hookInstalled = true;
  84. window.__ipcCalls = [];
  85. const raw = window.raw;
  86. if (raw && raw.openApp) {
  87. const orig = raw.openApp.bind(raw);
  88. raw.openApp = function(...args) {
  89. window.__ipcCalls.push({fn: 'openApp', args: JSON.stringify(args).slice(0, 200), t: Date.now()});
  90. return orig(...args);
  91. };
  92. return 'hooked window.raw.openApp';
  93. }
  94. return 'window.raw not found — hooks may differ';
  95. })()
  96. """)
  97. hook = await ev("window.__hookInstalled ? (window.__ipcCalls ? 'hook ready' : '?') : 'no'")
  98. print(f"\nIPC hook: {hook}")
  99. # 3. Click the CENTER of the button (or its main text child)
  100. a = json.loads(anatomy or "{}")
  101. if not a.get("found"):
  102. print("button not found!")
  103. return 1
  104. # prefer a child whose text contains 启动 or 上一次 — else button center
  105. target = None
  106. for k in a.get("kids", []):
  107. if "启动" in k["text"] or "上一次" in k["text"]:
  108. target = k
  109. break
  110. if not target:
  111. target = {"x": a["cx"], "y": a["cy"], "text": "(center)"}
  112. print(f"\nclicking child: '{target['text']}' @ ({target['x']},{target['y']})")
  113. await click(target["x"], target["y"])
  114. await asyncio.sleep(3)
  115. # 4. Did the IPC fire? Did anything change?
  116. calls = await ev("JSON.stringify(window.__ipcCalls || [])")
  117. print(f"\nopenApp IPC calls after click: {calls}")
  118. # 5. Fallback: call openApp DIRECTLY via the bridge (exactly what 启动 does)
  119. direct = await ev(r"""
  120. (function() {
  121. const raw = window.raw;
  122. if (!raw) return 'no raw bridge';
  123. // the game page knows the game id; find it in state
  124. const gid = 206923;
  125. const url = 'https://h5qfdt.996box.com/';
  126. if (raw.openApp) {
  127. const p = raw.openApp('cq', url);
  128. return 'direct openApp fired: cq + ' + url;
  129. }
  130. return 'raw.openApp missing';
  131. })()
  132. """)
  133. print(f"direct openApp: {direct}")
  134. # 6. monitor processes + traffic
  135. print("\n=== monitoring 180s ===")
  136. t0 = time.time()
  137. while time.time() - t0 < 180:
  138. await asyncio.sleep(5)
  139. el = int(time.time() - t0)
  140. try:
  141. out = subprocess.run(
  142. ["powershell", "-NoProfile", "-Command",
  143. "$p=(Get-Process | Where-Object {$_.Name -match '^cq'} | ForEach-Object {$_.Name+':'+$_.Id}) -join ',';"
  144. "$g=(netstat -ano | Select-String ':12000' | Measure-Object).Count;"
  145. "$c=(Get-Content 'D:\\传奇\\cq2_sdk\\real_login.jsonl' -ErrorAction SilentlyContinue | Measure-Object).Count;"
  146. "$p+'|'+$g+'|'+$c"],
  147. capture_output=True, text=True, timeout=8)
  148. parts = out.stdout.strip().split("|")
  149. procs, gw, cap = parts[0], parts[1] if len(parts) > 1 else "0", parts[2] if len(parts) > 2 else "0"
  150. except Exception:
  151. procs, gw, cap = "", "0", "0"
  152. flags = []
  153. if procs:
  154. flags.append(f"procs={procs}")
  155. if gw != "0":
  156. flags.append(f"gw={gw}")
  157. if cap != "0":
  158. flags.append(f"cap={cap}")
  159. print(f" t+{el:>3}s {' '.join(flags) or '...'}")
  160. if cap != "0" and int(cap) >= 2:
  161. print("\n*** TRAFFIC CAPTURED ***")
  162. await asyncio.sleep(20)
  163. return 0
  164. if procs and gw != "0" and el > 50:
  165. await asyncio.sleep(30)
  166. return 0
  167. return 1
  168. import sys
  169. sys.exit(asyncio.run(main()))