wait_wander6.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. # -*- coding: utf-8 -*-
  2. """
  3. wait_wander6.py — 等待图走动·终版(2026-09-23 抓包定案)。
  4. 真实客户端抓包(/tmp/cap_wait.jsonl, 996盒子在等待图滞留带):
  5. 走一步 = CS3011 + 50ms后 CS1, 每550ms重复:
  6. CS3011: recog=0, p1=dir(0-7), p2=递增计数(等待图段 1638421≈25<<16 起,
  7. 每步 +65535 附近抖动), p3=95(固定), body="等待地图"(UTF-8 12B)
  8. CS1: recog=X, p1=Y, p2=dir, p3=32368232(固定), tail=递增,
  9. body=encode6("+GOOD/<大毫秒>") — 明文即 +GOOD/98996961125 量级,
  10. 步进 ~750-1000ms; 坐标(X,Y)即步后位置(每步减/加1)
  11. → 角色确实在滞留带移动(轨迹 (20,26)→(19,27)→(18,26)...)!
  12. → 同一 3011 在比奇布局不同: p1=dir, p2=pack_point(起点) — quest_bot3 已实证。
  13. 之前全拒的根因: 布局错(发成比奇布局 recog=pack_point) + 缺紧跟的 CS1。
  14. 用法:
  15. .venv/bin/python wait_wander6.py --zone 14504562 --name 萌新8195 \
  16. -u longwu988 -p Longwu1988 --minutes 2
  17. """
  18. import argparse
  19. import json
  20. import random
  21. import time
  22. import requests as rq
  23. import urllib3
  24. urllib3.disable_warnings()
  25. import login_996pc as L
  26. import quest_bot3 as Q
  27. from combat_bot import log, DIRS, lo16, hi16
  28. from encode6bitbuf_final import encode6
  29. class TrueWalkBot(Q.PushBot):
  30. """抓包同构: 3011(等待图布局)+50ms CS1, 550ms/步。"""
  31. def __init__(self, *a, **kw):
  32. super().__init__(*a, **kw)
  33. self.good_ms = 98996961125 + random.randint(0, 5000) # 抓包同量级
  34. self.wait_seq = random.randint(20, 30) << 16 # p2 计数初值
  35. self.tail_seq = 90 # CS1 tail
  36. self.move_confirmed = 0
  37. self.move_rejected = 0
  38. def _send_good(self, x, y, d):
  39. """CS1: recog=X p1=Y p2=dir p3=32368232 body=encode6('+GOOD/ms')。
  40. tail 走主链(抓包实证 CS1 tail 恒 817 是引擎会话参数; 主链唯一递增即可)。"""
  41. self.good_ms += random.randint(750, 1000)
  42. body = encode6(f"+GOOD/{self.good_ms}".encode())
  43. self.send(1, x & 0xFFFF, y & 0xFFFF, d, 32368232, body)
  44. def step_true(self, d):
  45. """走一步 = 三帧组, 严格链式(黄金抓包 cap_gold 会话4 定案):
  46. ①CS3011: recog=0, p1=dir, p2=pack_point(起点), p3=sid, body=图名
  47. 起点 = 上一组 CS1 的落点(链式咬合, 断链→SC28拉回锚点)
  48. ②CS1(+70ms): recog=落点X, p1=落点Y, p2=dir, p3=32368232, body=+GOOD/ms
  49. ③CS28(+20ms): recog=锚点X, p1=锚点Y, p2=0, p3=32368232, body=空
  50. 锚点=服务器权威传送落点(每步恒定重复申报)"""
  51. from combat_bot import pack_point
  52. if getattr(self, "chain_pos", None) is None:
  53. self.chain_pos = (self.me["x"], self.me["y"])
  54. if getattr(self, "anchor_pos", None) is None:
  55. self.anchor_pos = (self.me["x"], self.me["y"])
  56. dx, dy = DIRS[d]
  57. x, y = self.chain_pos
  58. nx, ny = x + dx, y + dy
  59. self.send(3011, 0, d, pack_point(x, y), self.sid, Q.WAIT_MAP.encode())
  60. time.sleep(0.07)
  61. self._send_good(nx, ny, d)
  62. time.sleep(0.02)
  63. ax, ay = self.anchor_pos
  64. self.send(28, ax & 0xFFFF, ay & 0xFFFF, 0, 32368232, b"")
  65. self.chain_pos = (nx, ny)
  66. self.me["x"], self.me["y"], self.me["dir"] = nx, ny, d
  67. def _dispatch(self, mid, recog, p1, p2, p3, body):
  68. ev = super()._dispatch(mid, recog, p1, p2, p3, body)
  69. if mid in (11, 13):
  70. j = self._jbody(body) or {}
  71. uid = str(j.get("UserID", ""))
  72. if uid in ((self.me.get("uid") or ""),
  73. (self.me.get("sid_short") or ""),
  74. str(self.me.get("id") or "")):
  75. self.move_confirmed += 1
  76. y2 = p2 & 0x7FF if (p2 & 0x800) else p2
  77. self.me["x"], self.me["y"] = p1, y2
  78. self.me["last_x"], self.me["last_y"] = p1, y2
  79. log(f" << SC{mid} ★★★服务器确认移动 → ({p1},{y2})")
  80. elif mid == 28:
  81. y2 = p2 & 0x7FF if (p2 & 0x800) else p2
  82. self.move_rejected += 1
  83. if self.move_rejected <= 10:
  84. log(f" << SC28 拒绝 → ({p1},{y2}) recog={recog}")
  85. return ev
  86. def enter(args):
  87. j = None
  88. for a in range(5):
  89. try:
  90. s = rq.Session()
  91. j, _ = L.do_login(s, args.user, args.password)
  92. if j.get("code") == 200:
  93. break
  94. except Exception as e:
  95. log(f"登录重试{a + 1}: {str(e)[:60]}")
  96. time.sleep(12)
  97. if not j or j.get("code") != 200:
  98. raise SystemExit(f"[!] 登录失败: {(j or {}).get('msg', '网络异常')}")
  99. jwt = j["data"]["token"]
  100. uid = str(j["data"]["userInfo"]["id"])
  101. import cq2_pure_enter as P
  102. from combat_bot import send_init_frames
  103. for a in range(4):
  104. try:
  105. P.enter_game(s, jwt, args.zone)
  106. break
  107. except Exception:
  108. if a == 3:
  109. raise
  110. time.sleep(12)
  111. got = None
  112. for pt in ((args.port,) if args.port else (8021, 8061, 8001, 8041)):
  113. try:
  114. r = P.login_gate(args.zone, jwt, args.name, pt, uid)
  115. if r[0]:
  116. got = r
  117. break
  118. except SystemExit as e:
  119. log(f" 端口{pt}: {e}")
  120. except Exception as e:
  121. log(f" 端口{pt}: {str(e)[:60]}")
  122. if not got:
  123. raise SystemExit("[!] 登录服失败")
  124. key32, sid, rname, tkey, gp = got
  125. g = P.enter_game_gate(args.zone, rname, tkey, sid, gp, uid)
  126. bot = TrueWalkBot(g, sid, rname)
  127. # 心跳经 bot.send: tail 与主链共用递增(抓包实证全序连续)
  128. P.heartbeat_loop(g, sid, sender=bot.send)
  129. t0 = time.time()
  130. while time.time() - t0 < 10:
  131. bot.pump(0.3)
  132. if 658 in bot.frame_log:
  133. break
  134. if 658 not in bot.frame_log:
  135. g.close()
  136. raise SystemExit("[!] SC658 超时")
  137. send_init_frames(bot, uid)
  138. for _ in range(24):
  139. bot.pump(0.5)
  140. if bot.me["id"] and bot.me.get("mapname"):
  141. break
  142. log(f"进世界: map={bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']}")
  143. return g, bot
  144. def npc_talk_to_biqi(bot):
  145. """滞留带 → 点NPC → 传比奇(quest_bot3 实证链)。"""
  146. t0 = time.time()
  147. while time.time() - t0 < 8 and not bot.npcs:
  148. bot.pump(0.4)
  149. npc = bot.find_guide_npc()
  150. if not npc:
  151. return bot.me.get("mapname") == "比奇大城"
  152. aid = npc["aid"]
  153. for idx in [npc["index"], 35, 34]:
  154. if idx == npc["index"] and idx == 35:
  155. pass
  156. bot.pending_map_ack = "比奇大城"
  157. bot.send(1010, 0, 0, 0, bot.sid, json.dumps(
  158. {"UserID": aid, "index": idx}, separators=(",", ":")).encode())
  159. bot.send(5012, 0, 0, 0, bot.sid, b"0")
  160. bot.send(5027, 0, 0, 0, bot.sid, b"0")
  161. t1 = time.time()
  162. while time.time() - t1 < 8 and bot.alive:
  163. evs = bot.pump(0.3)
  164. if bot.me.get("mapname") == "比奇大城":
  165. bot.pending_map_ack = None
  166. return True
  167. talk = next((e[1] for e in evs if e[0] == "npc_talk"), None)
  168. if talk:
  169. acts = bot.parse_sui_acts(str(talk.get("sMsg", "")))
  170. act = next((a for a in acts if a not in ("exit", "close")), None)
  171. if act:
  172. bot.npc_click_act(talk, act)
  173. bot.pending_map_ack = None
  174. if bot.me.get("mapname") == "比奇大城":
  175. return True
  176. return bot.me.get("mapname") == "比奇大城"
  177. def renwu_back_to_wait(bot):
  178. """比奇 → @renwu_mov → 等待图可走区(黄金抓包 cap_gold t=121-146 定案):
  179. 1011 → 服务器把权威坐标传到 (24,27)(可走区) → 客户端 100ms 后
  180. 5012/5027+3013(起点=服务器新位置) → CS1 申报 → 连续走。
  181. bot 要点: 等 SC634/SC51 拿权威落点后再起步, 不从旧坐标走。"""
  182. import os
  183. from combat_bot import pack_point
  184. if 1 in bot.mission_bodies:
  185. body3031 = json.dumps(bot.mission_bodies[1], separators=(",", ":")).encode()
  186. else:
  187. body3031 = open(os.path.join(Q.HERE, "docs", "cs3031_template.txt"), "rb").read()
  188. bot.send(3031, 0, 0, 0, bot.sid, body3031)
  189. bot.pump(0.3)
  190. bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
  191. {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999},
  192. separators=(",", ":")).encode())
  193. # 黄金时序(cap_gold 会话4 t=750.87-751.2): 1011后 ~200ms:
  194. # 5012/5027+cTh ×2组 → 首步3013(p2=服务器权威落点!) → CS1(落点) → CS28(落点)
  195. # 等 SC634 权威落点(修正解析后: recog=X, p1=Y) — y>4 才算传送到可走区
  196. t0 = time.time()
  197. while time.time() - t0 < 6 and bot.alive:
  198. bot.pump(0.1)
  199. if bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] > 4:
  200. break
  201. x0, y0 = bot.me["x"], bot.me["y"]
  202. if y0 < 20:
  203. x0, y0 = 24, 27 # 兜底: 权威落点(黄金抓包)
  204. for _ in range(2):
  205. for mid in (5012, 5027):
  206. bot.send(mid, 0, 0, 0, bot.sid, Q.WAIT_MAP.encode())
  207. bot.send(5012, 0, 0, 0, 32611848, b"cTh")
  208. # 首步: 3013 起点必须=权威落点(黄金会话4: p2=(24,27))
  209. bot.send(3013, 0, 5, pack_point(x0, y0), bot.sid, Q.WAIT_MAP.encode())
  210. nx, ny = x0 - 2, y0 + 2 # 跑dir5两格
  211. bot._send_good(nx, ny, 5)
  212. time.sleep(0.02)
  213. bot.send(28, nx & 0xFFFF, ny & 0xFFFF, 0, 32368232, b"")
  214. bot.chain_pos = (nx, ny) # 链式起点
  215. bot.anchor_pos = (x0, y0) # CS28 锚点=权威落点
  216. bot.me["x"], bot.me["y"] = nx, ny
  217. bot.me["last_x"], bot.me["last_y"] = x0, y0
  218. time.sleep(0.5)
  219. bot.pump(0.5)
  220. n28 = bot.frame_log.get(28, 0)
  221. log(f"链式起步 权威=({x0},{y0}) 链位=({nx},{ny}) SC28={n28}")
  222. return True
  223. def main():
  224. ap = argparse.ArgumentParser()
  225. ap.add_argument("-u", "--user", default="longwu988")
  226. ap.add_argument("-p", "--password", default="Longwu1988")
  227. ap.add_argument("--zone", type=int, default=14504562)
  228. ap.add_argument("--name", default="萌新8195")
  229. ap.add_argument("--port", type=int, default=0)
  230. ap.add_argument("--minutes", type=float, default=2)
  231. args = ap.parse_args()
  232. args.minutes = min(args.minutes, 2)
  233. g, bot = enter(args)
  234. try:
  235. Q.P.enable_auto_heal(g, bot.sid, want_on=True)
  236. log("喝药保护: on")
  237. if bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] <= 4:
  238. log("[1] 滞留带 → NPC传比奇")
  239. if not npc_talk_to_biqi(bot):
  240. log("[!] 未传比奇, 中止")
  241. return
  242. log("[1b] 比奇下线(回归态落盘) → 15s后重连")
  243. g.close()
  244. time.sleep(15)
  245. g, bot = enter(args) # 回归角色应直接落可走区
  246. Q.P.enable_auto_heal(g, bot.sid, want_on=True)
  247. if bot.me.get("mapname") != Q.WAIT_MAP:
  248. log("[2] 比奇 → @renwu_mov 回等待图可走区")
  249. if not renwu_back_to_wait(bot):
  250. log("[!] 未回可走区, 中止")
  251. return
  252. log("=" * 60)
  253. log(f"[3] 可走区真实布局走动 @{bot.me['x']},{bot.me['y']}")
  254. log("=" * 60)
  255. t_end = time.time() + args.minutes * 60
  256. last_step = 0.0
  257. rep = time.time()
  258. heading = 5
  259. while time.time() < t_end and bot.alive:
  260. bot.pump(0.05)
  261. now = time.time()
  262. if now - last_step >= 0.55:
  263. # 换向策略: 确认多→保持, 拒绝多→随机换向
  264. if bot.move_rejected > bot.move_confirmed:
  265. heading = random.choice([h for h in range(8)
  266. if h != heading])
  267. bot.move_rejected = 0
  268. bot.move_confirmed = 0
  269. bot.step_true(heading)
  270. last_step = now
  271. if now - rep >= 15:
  272. rep = now
  273. log(f"[STAT] pos=({bot.me['x']},{bot.me['y']}) "
  274. f"确认={bot.move_confirmed} 拒绝={bot.move_rejected}")
  275. log("=" * 60)
  276. log(f"[结束] pos=({bot.me['x']},{bot.me['y']}) "
  277. f"确认={bot.move_confirmed} 拒绝={bot.move_rejected}")
  278. fl = sorted(bot.frame_log.items(), key=lambda kv: -kv[1])
  279. log(f"收帧Top10: {fl[:10]}")
  280. finally:
  281. g.close()
  282. if __name__ == "__main__":
  283. main()