walk_final.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. # -*- coding: utf-8 -*-
  2. """
  3. walk_final.py — 等待图走动·终版: 全帧分类日志 + 0139 网格感知游走。
  4. 2026-09-23 定案的行走规则(黄金抓包 cap_gold + Lua 真源 + SPY 实捕三方一致):
  5. 【服务器下行帧 · 走路相关】(Lua 处理器已核对, 见 gameMapController/netActorController)
  6. SC11/12/13 移动广播: body.UserID 查表, 自己的 → SetMoveTo+SetConfirmed(true)
  7. = 移动成功确认(本服布局: recog=X, p1=Y, p2=dir — SPY 抓包定案)
  8. SC28 MOVE_FAIL 拉回: setPosition(接受格) + ACTION_IDLE_LOCK
  9. SC1 行为确认: SetConfirmed(true)
  10. SC634 跨图传送: recog=X, p1=Y(旧代码按 p1/p2 解析 — 已修), body=图名JSON
  11. SC51 场景创建: 出生坐标(本服 recog=X, p1=Y)
  12. SC20102 服务器寻路目标(T=1)
  13. 可忽略: SC5020(buff)/SC41/SC881(表现)/SC10/30(视野雷达, 杀怪才用)
  14. 【客户端上行 · 每步三帧组】(黄金抓包逐帧还原)
  15. CS3011: recog=0, p1=dir, p2=pack_point(起点), p3=sid, body=图名
  16. CS1 : recog=落点X, p1=落点Y, p2=dir, p3=32368232, body=encode6(+GOOD/ms)
  17. CS28 : recog=锚点X, p1=锚点Y, p2=0, p3=32368232, body=空(锚点=传送落点,恒定)
  18. 链式规则: 3011.p2 起点 = 上一步 CS1 落点; 断链或撞墙 → SC28 拉回
  19. 【地图规则】0139.map(等待图): y=0-2 滞留带(墙), y>=3 可走;
  20. 走进墙格必被 SC28 拒 — 游走必须查网格!
  21. 用法:
  22. .venv/bin/python walk_final.py --zone 14504562 --name 斗士9906 \
  23. -u z1098210167 -p yang535006 --minutes 2 [--verbose]
  24. """
  25. import argparse
  26. import json
  27. import os
  28. import random
  29. import time
  30. import requests as rq
  31. import urllib3
  32. urllib3.disable_warnings()
  33. import login_996pc as L
  34. import quest_bot3 as Q
  35. from combat_bot import log, pack_point, DIRS, proto_map_name, lo16, hi16
  36. from encode6bitbuf_final import encode6
  37. from mir_map import load as map_load
  38. # 走路相关下行帧分类(其余静默计数)
  39. WALK_FRAMES = {11, 12, 13, 28, 1, 634, 51, 801, 20102, 800}
  40. IGNORE_FRAMES = {60000, 5037, -1, 41, 5020, 10, 30, 35, 881, 50, 14, 32, 55}
  41. class FinalWalkBot(Q.PushBot):
  42. """全帧分类日志 + 三帧组链式走动 + 网格感知。"""
  43. def __init__(self, *a, **kw):
  44. super().__init__(*a, **kw)
  45. self.good_ms = 98996961125 + random.randint(0, 5000)
  46. self.chain_pos = None # 链式当前位置(=上步CS1落点)
  47. self.anchor_pos = None # CS28 锚点(传送落点)
  48. self.verbose = False
  49. self.stats = {"ok": 0, "rej": 0, "walk_frames": {}}
  50. # ---------- 上行三帧组 ----------
  51. # ---------- 上行: 只发 CS3011 ----------
  52. def send(self, mid, recog, p1, p2, p3, body=b"", tail=None):
  53. if self.verbose and mid in (3011, 3013, 5012, 5027, 1010, 1011, 3031):
  54. x, y = lo16(p2), (p2 >> 16) & 0xFFFF
  55. log(f" [TX→S] CS{mid} recog={recog} p1={p1} "
  56. f"p2={p2}({x},{y}) p3={p3} tail={tail if tail is not None else 'auto'}")
  57. return super().send(mid, recog, p1, p2, p3, body, tail)
  58. def step_true(self, d, m=None, run=False):
  59. """移动帧(cap_unlock 双组抓包定案):
  60. CS3011(走1格) / CS3013(跑2格), 布局相同:
  61. recog=0, p1=dir, p2=pack_point(起点), p3=sid, body=图名。
  62. ★ renwu_mov 传送后的等待图: 3011 被 M2 锁定(48帧全拒铁证),
  63. 3013 放行(真客户端 SC20102 后连跑3帧全确认) — 任务寻路强制跑步。
  64. 服务器回 SC1(+GOOD) 确认落点后才能走下一步。"""
  65. x, y = self.chain_pos
  66. dx, dy = DIRS[d]
  67. dist = 2 if run else 1
  68. nx, ny = x + dx * dist, y + dy * dist
  69. if m is not None and not m.walkable(nx, ny):
  70. return False
  71. mid = 3013 if run else 3011
  72. self.send(mid, 0, d, pack_point(x, y), self.sid,
  73. proto_map_name(self.me).encode())
  74. self._pending_step = (nx, ny) # 等 SC1 确认后才更新链位
  75. self.me["dir"] = d
  76. return True
  77. # ---------- 下行帧分类处理 ----------
  78. def _dispatch(self, mid, recog, p1, p2, p3, body):
  79. """接收=RX(服务器下行)。tail 判源: 下行帧 tail 通常=0/乱数;
  80. body 密文态: 明文JSON/encode6(e6)/zlib(-1批量) 自动剥层(_jbody)。"""
  81. ev = super()._dispatch(mid, recog, p1, p2, p3, body)
  82. self.stats["walk_frames"][mid] = self.stats["walk_frames"].get(mid, 0) + 1
  83. if not self.verbose:
  84. return ev
  85. y2 = p2 & 0x7FF if (p2 & 0x800) else p2
  86. if mid in (11, 12, 13):
  87. j = self._jbody(body) or {}
  88. uid = str(j.get("UserID", ""))
  89. mine = uid in ((self.me.get("uid") or ""),
  90. (self.me.get("sid_short") or ""),
  91. str(self.me.get("id") or ""))
  92. tag = "★自己" if mine else "他人"
  93. if mine:
  94. self.stats["ok"] += 1
  95. log(f" [S→RX] SC{mid} {tag}移动 UserID={uid[:12]} "
  96. f"@({recog},{p1}) dir={p2} [SetMoveTo+Confirmed]")
  97. elif mid == 28:
  98. if p3 == 32368232:
  99. # 我方 CS28 锚点帧的服务器侧回显(p3同魔数) — 非拒绝
  100. log(f" [S→RX] SC28 锚点回显({recog},{p1}) — 非拒绝")
  101. else:
  102. self.stats["rej"] += 1
  103. log(f" [S→RX] SC28 ✗真MOVE_FAIL 拉回({recog},{p1}) "
  104. f"dir={p3 & 0xFF} [IDLE_LOCK]")
  105. elif mid == 1:
  106. # SC1 = 服务器移动确认(下行): recog=落点X, p1=落点Y, p2=dir,
  107. # body=encode6(+GOOD/ms), tail=817 — 确认后才能发下一步
  108. pend = getattr(self, "_pending_step", None)
  109. if pend is not None:
  110. self.chain_pos = pend
  111. self.me["x"], self.me["y"] = pend
  112. self.me["last_x"], self.me["last_y"] = pend
  113. self._pending_step = None
  114. self.stats["ok"] += 1
  115. if self.verbose:
  116. log(f" [S→RX] SC1 ★移动确认({recog},{p1}) dir={p2} "
  117. f"[链位→{pend}]")
  118. else:
  119. log(f" [S→RX] SC1 确认({recog},{p1}) [无待确认步]")
  120. elif mid == 634:
  121. log(f" [S→RX] SC634 跨图传送→({recog},{p1}) "
  122. f"body={self._jbody(body) or body[:20]!r}")
  123. elif mid == 51:
  124. log(f" [S→RX] SC51 场景创建({recog},{p1})")
  125. elif mid in (800, 801):
  126. log(f" [S→RX] SC{mid} 传送({recog},{p1},{y2})")
  127. elif mid == 20102:
  128. j = self._jbody(body) or {}
  129. log(f" [S→RX] SC20102 服务器寻路目标 {j}")
  130. elif mid not in IGNORE_FRAMES:
  131. log(f" [S→RX] SC{mid} recog={recog} p=({p1},{y2},{p3}) "
  132. f"body={body[:24]!r} [未分类]")
  133. return ev
  134. def enter(args):
  135. j = None
  136. for a in range(5):
  137. try:
  138. s = rq.Session()
  139. j, _ = L.do_login(s, args.user, args.password)
  140. if j.get("code") == 200:
  141. break
  142. except Exception as e:
  143. log(f"登录重试{a + 1}: {str(e)[:60]}")
  144. time.sleep(12)
  145. if not j or j.get("code") != 200:
  146. raise SystemExit(f"[!] 登录失败: {(j or {}).get('msg', '网络异常')}")
  147. jwt = j["data"]["token"]
  148. uid = str(j["data"]["userInfo"]["id"])
  149. import cq2_pure_enter as P
  150. from combat_bot import send_init_frames
  151. for a in range(4):
  152. try:
  153. P.enter_game(s, jwt, args.zone)
  154. break
  155. except Exception:
  156. if a == 3:
  157. raise
  158. time.sleep(12)
  159. got = None
  160. for pt in ((args.port,) if args.port else (8021, 8061, 8001, 8041)):
  161. try:
  162. r = P.login_gate(args.zone, jwt, args.name, pt, uid)
  163. if r[0]:
  164. got = r
  165. break
  166. except SystemExit as e:
  167. log(f" 端口{pt}: {e}")
  168. except Exception as e:
  169. log(f" 端口{pt}: {str(e)[:60]}")
  170. if not got:
  171. raise SystemExit("[!] 登录服失败")
  172. key32, sid, rname, tkey, gp = got
  173. g = P.enter_game_gate(args.zone, rname, tkey, sid, gp, uid)
  174. bot = FinalWalkBot(g, sid, rname)
  175. P.heartbeat_loop(g, sid, sender=bot.send)
  176. t0 = time.time()
  177. while time.time() - t0 < 10:
  178. bot.pump(0.3)
  179. if 658 in bot.frame_log:
  180. break
  181. if 658 not in bot.frame_log:
  182. g.close()
  183. raise SystemExit("[!] SC658 超时")
  184. # 先收 SC51 拿到当前图名(5027 需要正确图名), 再 init(对齐真客户端时序)
  185. t_pre = time.time()
  186. while time.time() - t_pre < 3:
  187. bot.pump(0.2)
  188. if bot.me.get("mapname"):
  189. break
  190. send_init_frames(bot, uid)
  191. for _ in range(24):
  192. bot.pump(0.5)
  193. if bot.me["id"] and bot.me.get("mapname"):
  194. break
  195. log(f"进世界: map={bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']}")
  196. return g, bot
  197. def npc_talk_to_biqi(bot):
  198. t0 = time.time()
  199. while time.time() - t0 < 8 and not bot.npcs:
  200. bot.pump(0.4)
  201. npc = bot.find_guide_npc()
  202. if not npc:
  203. return bot.me.get("mapname") == "比奇大城"
  204. aid = npc["aid"]
  205. for idx in [npc["index"], 35, 34]:
  206. bot.pending_map_ack = "比奇大城"
  207. bot.send(1010, 0, 0, 0, bot.sid, json.dumps(
  208. {"UserID": aid, "index": idx}, separators=(",", ":")).encode())
  209. bot.send(5012, 0, 0, 0, bot.sid, b"0")
  210. bot.send(5027, 0, 0, 0, bot.sid, b"0")
  211. t1 = time.time()
  212. while time.time() - t1 < 8 and bot.alive:
  213. evs = bot.pump(0.3)
  214. if bot.me.get("mapname") == "比奇大城":
  215. bot.pending_map_ack = None
  216. return True
  217. talk = next((e[1] for e in evs if e[0] == "npc_talk"), None)
  218. if talk:
  219. acts = bot.parse_sui_acts(str(talk.get("sMsg", "")))
  220. act = next((a for a in acts if a not in ("exit", "close")), None)
  221. if act:
  222. bot.npc_click_act(talk, act)
  223. bot.pending_map_ack = None
  224. if bot.me.get("mapname") == "比奇大城":
  225. return True
  226. return bot.me.get("mapname") == "比奇大城"
  227. def renwu_to_wait(bot):
  228. """比奇/任意图 → @renwu_mov → 等待图可走区, 建链式起点+锚点。"""
  229. if 1 in bot.mission_bodies:
  230. body3031 = json.dumps(bot.mission_bodies[1], separators=(",", ":")).encode()
  231. else:
  232. body3031 = open(os.path.join(Q.HERE, "docs", "cs3031_template.txt"), "rb").read()
  233. bot.send(3031, 0, 0, 0, bot.sid, body3031)
  234. bot.pump(0.3)
  235. bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
  236. {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999},
  237. separators=(",", ":")).encode())
  238. # 黄金时序: 1011后立即 5012/5027×2+cTh×2, 不等传送
  239. for _ in range(2):
  240. for mid in (5012, 5027):
  241. bot.send(mid, 0, 0, 0, bot.sid, Q.WAIT_MAP.encode())
  242. bot.send(5012, 0, 0, 0, 32611848, b"cTh")
  243. # 等 SC634 权威落点(已修解析: recog=X, p1=Y)
  244. t0 = time.time()
  245. while time.time() - t0 < 6 and bot.alive:
  246. bot.pump(0.1)
  247. if bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] > 4:
  248. break
  249. x0, y0 = bot.me["x"], bot.me["y"]
  250. if y0 < 20:
  251. x0, y0 = 24, 27
  252. # M2封速系统(996引擎, 网络资料+抓包交叉定案):
  253. # MOVEINTERVAL/CHECKOVSPDINV/MAXOVSPDCNT=3 — 移动包间隔过快会累计超速计数,
  254. # 超限后所有移动包被丢(OVERSPEEDPUNISH)+SC28拉回, 且此前的高频实验帧
  255. # 早已污染计数 → 传送后必须静置洗计数, 走路间隔取保守值(≥1.4s/步,
  256. # 抓包实证真客户端点击间隔≈1.4s全通过)。
  257. log(f"传送落点({x0},{y0}) — 静置 10s 洗封速计数...")
  258. t_lock = time.time()
  259. while time.time() - t_lock < 10.0 and bot.alive:
  260. bot.pump(0.2)
  261. bot.chain_pos = (x0, y0)
  262. bot.anchor_pos = (x0, y0)
  263. log(f"链式建立: 起点=({x0},{y0}) 步间隔1.5s")
  264. return True
  265. def wander(bot, seconds, m):
  266. """网格感知游走: 发一步 CS3011 → 等 SC1 确认 → 再发下一步。
  267. (真客户端节拍: 点击后连续 3011, 服务器 SC1 逐帧确认)"""
  268. t_end = time.time() + seconds
  269. rep = time.time()
  270. dest = None
  271. last_step = 0.0
  272. while time.time() < t_end and bot.alive:
  273. evs = bot.pump(0.05)
  274. pulled = any(e[0].startswith("pull_back") for e in evs)
  275. if pulled:
  276. # SC28拉回=M2丢包: 退避3s并重置链位到服务器位置
  277. bot.chain_pos = (bot.me["x"], bot.me["y"])
  278. bot._pending_step = None
  279. last_step = time.time() + 3.0
  280. log(f" [退避] SC28拉回→({bot.chain_pos[0]},{bot.chain_pos[1]}), 3s后再试")
  281. now = time.time()
  282. if now < last_step + 1.5:
  283. continue
  284. if getattr(bot, "_pending_step", None) is not None:
  285. continue
  286. x, y = bot.chain_pos
  287. if dest is None or (abs(dest[0] - x) <= 1 and abs(dest[1] - y) <= 1):
  288. for _ in range(30):
  289. nx = x + random.randint(-10, 10)
  290. ny = y + random.randint(-10, 10)
  291. if (m.in_bounds(nx, ny) and m.walkable(nx, ny)
  292. and abs(nx - x) + abs(ny - y) > 3):
  293. dest = (nx, ny)
  294. log(f"[游走] 新目标 ({nx},{ny})")
  295. break
  296. else:
  297. dest = None
  298. if dest:
  299. dx = max(-1, min(1, dest[0] - x))
  300. dy = max(-1, min(1, dest[1] - y))
  301. if dx and dy and not (m.walkable(x + dx, y) or m.walkable(x, y + dy)):
  302. if m.walkable(x + dx, y):
  303. dy = 0
  304. elif m.walkable(x, y + dy):
  305. dx = 0
  306. d = next(i for i, (ox, oy) in enumerate(DIRS) if ox == dx and oy == dy)
  307. # renwu 后等待图任务态: 必须跑步(3013), 走(3011)被 M2 锁定
  308. if not bot.step_true(d, m, run=True):
  309. dest = None
  310. last_step = now
  311. if now - rep >= 15:
  312. rep = now
  313. log(f"[STAT] 链位={bot.chain_pos} 目标={dest} "
  314. f"确认={bot.stats['ok']} 拒绝={bot.stats['rej']}")
  315. log(f"[结束] 链位={bot.chain_pos} 确认={bot.stats['ok']} "
  316. f"拒绝={bot.stats['rej']}")
  317. def main():
  318. ap = argparse.ArgumentParser()
  319. ap.add_argument("-u", "--user", default="z1098210167")
  320. ap.add_argument("-p", "--password", default="yang535006")
  321. ap.add_argument("--zone", type=int, default=14504562)
  322. ap.add_argument("--name", default="斗士9906")
  323. ap.add_argument("--port", type=int, default=0)
  324. ap.add_argument("--minutes", type=float, default=2)
  325. ap.add_argument("--verbose", action="store_true", help="全帧分类日志")
  326. args = ap.parse_args()
  327. args.minutes = min(args.minutes, 2)
  328. m = map_load("0139")
  329. g, bot = enter(args)
  330. bot.verbose = args.verbose
  331. try:
  332. Q.P.enable_auto_heal(g, bot.sid, want_on=True)
  333. log("喝药保护: on")
  334. if bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] > 4:
  335. # 登录落点已在可走区(上次下线位置) — 直接走, 不做任何传送!
  336. bot.chain_pos = (bot.me["x"], bot.me["y"])
  337. log(f"[0] 落点已在可走区 @{bot.me['x']},{bot.me['y']} — 直接走")
  338. elif bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] <= 4:
  339. log("[1] 滞留带 → NPC传比奇")
  340. if not npc_talk_to_biqi(bot):
  341. log("[!] 未传比奇")
  342. return
  343. time.sleep(1)
  344. for _ in range(6):
  345. bot.pump(0.4)
  346. if bot.me.get("mapname") != Q.WAIT_MAP or bot.me["y"] <= 4:
  347. log("[2] → @renwu_mov 回等待图可走区")
  348. if not renwu_to_wait(bot):
  349. log("[!] 建链失败")
  350. return
  351. log(f"[3] 网格感知游动开始 @{bot.me['x']},{bot.me['y']}")
  352. wander(bot, args.minutes * 60, m)
  353. finally:
  354. g.close()
  355. if __name__ == "__main__":
  356. main()