# -*- coding: utf-8 -*- """ walk_final.py — 等待图走动·终版: 全帧分类日志 + 0139 网格感知游走。 2026-09-23 定案的行走规则(黄金抓包 cap_gold + Lua 真源 + SPY 实捕三方一致): 【服务器下行帧 · 走路相关】(Lua 处理器已核对, 见 gameMapController/netActorController) SC11/12/13 移动广播: body.UserID 查表, 自己的 → SetMoveTo+SetConfirmed(true) = 移动成功确认(本服布局: recog=X, p1=Y, p2=dir — SPY 抓包定案) SC28 MOVE_FAIL 拉回: setPosition(接受格) + ACTION_IDLE_LOCK SC1 行为确认: SetConfirmed(true) SC634 跨图传送: recog=X, p1=Y(旧代码按 p1/p2 解析 — 已修), body=图名JSON SC51 场景创建: 出生坐标(本服 recog=X, p1=Y) SC20102 服务器寻路目标(T=1) 可忽略: SC5020(buff)/SC41/SC881(表现)/SC10/30(视野雷达, 杀怪才用) 【客户端上行 · 每步三帧组】(黄金抓包逐帧还原) CS3011: recog=0, p1=dir, p2=pack_point(起点), p3=sid, body=图名 CS1 : recog=落点X, p1=落点Y, p2=dir, p3=32368232, body=encode6(+GOOD/ms) CS28 : recog=锚点X, p1=锚点Y, p2=0, p3=32368232, body=空(锚点=传送落点,恒定) 链式规则: 3011.p2 起点 = 上一步 CS1 落点; 断链或撞墙 → SC28 拉回 【地图规则】0139.map(等待图): y=0-2 滞留带(墙), y>=3 可走; 走进墙格必被 SC28 拒 — 游走必须查网格! 用法: .venv/bin/python walk_final.py --zone 14504562 --name 斗士9906 \ -u z1098210167 -p yang535006 --minutes 2 [--verbose] """ import argparse import json import os import random import time import requests as rq import urllib3 urllib3.disable_warnings() import login_996pc as L import quest_bot3 as Q from combat_bot import log, pack_point, DIRS, proto_map_name, lo16, hi16 from encode6bitbuf_final import encode6 from mir_map import load as map_load # 走路相关下行帧分类(其余静默计数) WALK_FRAMES = {11, 12, 13, 28, 1, 634, 51, 801, 20102, 800} IGNORE_FRAMES = {60000, 5037, -1, 41, 5020, 10, 30, 35, 881, 50, 14, 32, 55} class FinalWalkBot(Q.PushBot): """全帧分类日志 + 三帧组链式走动 + 网格感知。""" def __init__(self, *a, **kw): super().__init__(*a, **kw) self.good_ms = 98996961125 + random.randint(0, 5000) self.chain_pos = None # 链式当前位置(=上步CS1落点) self.anchor_pos = None # CS28 锚点(传送落点) self.verbose = False self.stats = {"ok": 0, "rej": 0, "walk_frames": {}} # ---------- 上行三帧组 ---------- # ---------- 上行: 只发 CS3011 ---------- def send(self, mid, recog, p1, p2, p3, body=b"", tail=None): if self.verbose and mid in (3011, 3013, 5012, 5027, 1010, 1011, 3031): x, y = lo16(p2), (p2 >> 16) & 0xFFFF log(f" [TX→S] CS{mid} recog={recog} p1={p1} " f"p2={p2}({x},{y}) p3={p3} tail={tail if tail is not None else 'auto'}") return super().send(mid, recog, p1, p2, p3, body, tail) def step_true(self, d, m=None, run=False): """移动帧(cap_unlock 双组抓包定案): CS3011(走1格) / CS3013(跑2格), 布局相同: recog=0, p1=dir, p2=pack_point(起点), p3=sid, body=图名。 ★ renwu_mov 传送后的等待图: 3011 被 M2 锁定(48帧全拒铁证), 3013 放行(真客户端 SC20102 后连跑3帧全确认) — 任务寻路强制跑步。 服务器回 SC1(+GOOD) 确认落点后才能走下一步。""" x, y = self.chain_pos dx, dy = DIRS[d] dist = 2 if run else 1 nx, ny = x + dx * dist, y + dy * dist if m is not None and not m.walkable(nx, ny): return False mid = 3013 if run else 3011 self.send(mid, 0, d, pack_point(x, y), self.sid, proto_map_name(self.me).encode()) self._pending_step = (nx, ny) # 等 SC1 确认后才更新链位 self.me["dir"] = d return True # ---------- 下行帧分类处理 ---------- def _dispatch(self, mid, recog, p1, p2, p3, body): """接收=RX(服务器下行)。tail 判源: 下行帧 tail 通常=0/乱数; body 密文态: 明文JSON/encode6(e6)/zlib(-1批量) 自动剥层(_jbody)。""" ev = super()._dispatch(mid, recog, p1, p2, p3, body) self.stats["walk_frames"][mid] = self.stats["walk_frames"].get(mid, 0) + 1 if not self.verbose: return ev y2 = p2 & 0x7FF if (p2 & 0x800) else p2 if mid in (11, 12, 13): j = self._jbody(body) or {} uid = str(j.get("UserID", "")) mine = uid in ((self.me.get("uid") or ""), (self.me.get("sid_short") or ""), str(self.me.get("id") or "")) tag = "★自己" if mine else "他人" if mine: self.stats["ok"] += 1 log(f" [S→RX] SC{mid} {tag}移动 UserID={uid[:12]} " f"@({recog},{p1}) dir={p2} [SetMoveTo+Confirmed]") elif mid == 28: if p3 == 32368232: # 我方 CS28 锚点帧的服务器侧回显(p3同魔数) — 非拒绝 log(f" [S→RX] SC28 锚点回显({recog},{p1}) — 非拒绝") else: self.stats["rej"] += 1 log(f" [S→RX] SC28 ✗真MOVE_FAIL 拉回({recog},{p1}) " f"dir={p3 & 0xFF} [IDLE_LOCK]") elif mid == 1: # SC1 = 服务器移动确认(下行): recog=落点X, p1=落点Y, p2=dir, # body=encode6(+GOOD/ms), tail=817 — 确认后才能发下一步 pend = getattr(self, "_pending_step", None) if pend is not None: self.chain_pos = pend self.me["x"], self.me["y"] = pend self.me["last_x"], self.me["last_y"] = pend self._pending_step = None self.stats["ok"] += 1 if self.verbose: log(f" [S→RX] SC1 ★移动确认({recog},{p1}) dir={p2} " f"[链位→{pend}]") else: log(f" [S→RX] SC1 确认({recog},{p1}) [无待确认步]") elif mid == 634: log(f" [S→RX] SC634 跨图传送→({recog},{p1}) " f"body={self._jbody(body) or body[:20]!r}") elif mid == 51: log(f" [S→RX] SC51 场景创建({recog},{p1})") elif mid in (800, 801): log(f" [S→RX] SC{mid} 传送({recog},{p1},{y2})") elif mid == 20102: j = self._jbody(body) or {} log(f" [S→RX] SC20102 服务器寻路目标 {j}") elif mid not in IGNORE_FRAMES: log(f" [S→RX] SC{mid} recog={recog} p=({p1},{y2},{p3}) " f"body={body[:24]!r} [未分类]") return ev def enter(args): j = None for a in range(5): try: s = rq.Session() j, _ = L.do_login(s, args.user, args.password) if j.get("code") == 200: break except Exception as e: log(f"登录重试{a + 1}: {str(e)[:60]}") time.sleep(12) if not j or j.get("code") != 200: raise SystemExit(f"[!] 登录失败: {(j or {}).get('msg', '网络异常')}") jwt = j["data"]["token"] uid = str(j["data"]["userInfo"]["id"]) import cq2_pure_enter as P from combat_bot import send_init_frames for a in range(4): try: P.enter_game(s, jwt, args.zone) break except Exception: if a == 3: raise time.sleep(12) got = None for pt in ((args.port,) if args.port else (8021, 8061, 8001, 8041)): try: r = P.login_gate(args.zone, jwt, args.name, pt, uid) if r[0]: got = r break except SystemExit as e: log(f" 端口{pt}: {e}") except Exception as e: log(f" 端口{pt}: {str(e)[:60]}") if not got: raise SystemExit("[!] 登录服失败") key32, sid, rname, tkey, gp = got g = P.enter_game_gate(args.zone, rname, tkey, sid, gp, uid) bot = FinalWalkBot(g, sid, rname) P.heartbeat_loop(g, sid, sender=bot.send) t0 = time.time() while time.time() - t0 < 10: bot.pump(0.3) if 658 in bot.frame_log: break if 658 not in bot.frame_log: g.close() raise SystemExit("[!] SC658 超时") # 先收 SC51 拿到当前图名(5027 需要正确图名), 再 init(对齐真客户端时序) t_pre = time.time() while time.time() - t_pre < 3: bot.pump(0.2) if bot.me.get("mapname"): break send_init_frames(bot, uid) for _ in range(24): bot.pump(0.5) if bot.me["id"] and bot.me.get("mapname"): break log(f"进世界: map={bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']}") return g, bot def npc_talk_to_biqi(bot): t0 = time.time() while time.time() - t0 < 8 and not bot.npcs: bot.pump(0.4) npc = bot.find_guide_npc() if not npc: return bot.me.get("mapname") == "比奇大城" aid = npc["aid"] for idx in [npc["index"], 35, 34]: bot.pending_map_ack = "比奇大城" bot.send(1010, 0, 0, 0, bot.sid, json.dumps( {"UserID": aid, "index": idx}, separators=(",", ":")).encode()) bot.send(5012, 0, 0, 0, bot.sid, b"0") bot.send(5027, 0, 0, 0, bot.sid, b"0") t1 = time.time() while time.time() - t1 < 8 and bot.alive: evs = bot.pump(0.3) if bot.me.get("mapname") == "比奇大城": bot.pending_map_ack = None return True talk = next((e[1] for e in evs if e[0] == "npc_talk"), None) if talk: acts = bot.parse_sui_acts(str(talk.get("sMsg", ""))) act = next((a for a in acts if a not in ("exit", "close")), None) if act: bot.npc_click_act(talk, act) bot.pending_map_ack = None if bot.me.get("mapname") == "比奇大城": return True return bot.me.get("mapname") == "比奇大城" def renwu_to_wait(bot): """比奇/任意图 → @renwu_mov → 等待图可走区, 建链式起点+锚点。""" if 1 in bot.mission_bodies: body3031 = json.dumps(bot.mission_bodies[1], separators=(",", ":")).encode() else: body3031 = open(os.path.join(Q.HERE, "docs", "cs3031_template.txt"), "rb").read() bot.send(3031, 0, 0, 0, bot.sid, body3031) bot.pump(0.3) bot.send(1011, 0, 0, 0, bot.sid, json.dumps( {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999}, separators=(",", ":")).encode()) # 黄金时序: 1011后立即 5012/5027×2+cTh×2, 不等传送 for _ in range(2): for mid in (5012, 5027): bot.send(mid, 0, 0, 0, bot.sid, Q.WAIT_MAP.encode()) bot.send(5012, 0, 0, 0, 32611848, b"cTh") # 等 SC634 权威落点(已修解析: recog=X, p1=Y) t0 = time.time() while time.time() - t0 < 6 and bot.alive: bot.pump(0.1) if bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] > 4: break x0, y0 = bot.me["x"], bot.me["y"] if y0 < 20: x0, y0 = 24, 27 # M2封速系统(996引擎, 网络资料+抓包交叉定案): # MOVEINTERVAL/CHECKOVSPDINV/MAXOVSPDCNT=3 — 移动包间隔过快会累计超速计数, # 超限后所有移动包被丢(OVERSPEEDPUNISH)+SC28拉回, 且此前的高频实验帧 # 早已污染计数 → 传送后必须静置洗计数, 走路间隔取保守值(≥1.4s/步, # 抓包实证真客户端点击间隔≈1.4s全通过)。 log(f"传送落点({x0},{y0}) — 静置 10s 洗封速计数...") t_lock = time.time() while time.time() - t_lock < 10.0 and bot.alive: bot.pump(0.2) bot.chain_pos = (x0, y0) bot.anchor_pos = (x0, y0) log(f"链式建立: 起点=({x0},{y0}) 步间隔1.5s") return True def wander(bot, seconds, m): """网格感知游走: 发一步 CS3011 → 等 SC1 确认 → 再发下一步。 (真客户端节拍: 点击后连续 3011, 服务器 SC1 逐帧确认)""" t_end = time.time() + seconds rep = time.time() dest = None last_step = 0.0 while time.time() < t_end and bot.alive: evs = bot.pump(0.05) pulled = any(e[0].startswith("pull_back") for e in evs) if pulled: # SC28拉回=M2丢包: 退避3s并重置链位到服务器位置 bot.chain_pos = (bot.me["x"], bot.me["y"]) bot._pending_step = None last_step = time.time() + 3.0 log(f" [退避] SC28拉回→({bot.chain_pos[0]},{bot.chain_pos[1]}), 3s后再试") now = time.time() if now < last_step + 1.5: continue if getattr(bot, "_pending_step", None) is not None: continue x, y = bot.chain_pos if dest is None or (abs(dest[0] - x) <= 1 and abs(dest[1] - y) <= 1): for _ in range(30): nx = x + random.randint(-10, 10) ny = y + random.randint(-10, 10) if (m.in_bounds(nx, ny) and m.walkable(nx, ny) and abs(nx - x) + abs(ny - y) > 3): dest = (nx, ny) log(f"[游走] 新目标 ({nx},{ny})") break else: dest = None if dest: dx = max(-1, min(1, dest[0] - x)) dy = max(-1, min(1, dest[1] - y)) if dx and dy and not (m.walkable(x + dx, y) or m.walkable(x, y + dy)): if m.walkable(x + dx, y): dy = 0 elif m.walkable(x, y + dy): dx = 0 d = next(i for i, (ox, oy) in enumerate(DIRS) if ox == dx and oy == dy) # renwu 后等待图任务态: 必须跑步(3013), 走(3011)被 M2 锁定 if not bot.step_true(d, m, run=True): dest = None last_step = now if now - rep >= 15: rep = now log(f"[STAT] 链位={bot.chain_pos} 目标={dest} " f"确认={bot.stats['ok']} 拒绝={bot.stats['rej']}") log(f"[结束] 链位={bot.chain_pos} 确认={bot.stats['ok']} " f"拒绝={bot.stats['rej']}") def main(): ap = argparse.ArgumentParser() ap.add_argument("-u", "--user", default="z1098210167") ap.add_argument("-p", "--password", default="yang535006") ap.add_argument("--zone", type=int, default=14504562) ap.add_argument("--name", default="斗士9906") ap.add_argument("--port", type=int, default=0) ap.add_argument("--minutes", type=float, default=2) ap.add_argument("--verbose", action="store_true", help="全帧分类日志") args = ap.parse_args() args.minutes = min(args.minutes, 2) m = map_load("0139") g, bot = enter(args) bot.verbose = args.verbose try: Q.P.enable_auto_heal(g, bot.sid, want_on=True) log("喝药保护: on") if bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] > 4: # 登录落点已在可走区(上次下线位置) — 直接走, 不做任何传送! bot.chain_pos = (bot.me["x"], bot.me["y"]) log(f"[0] 落点已在可走区 @{bot.me['x']},{bot.me['y']} — 直接走") elif bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] <= 4: log("[1] 滞留带 → NPC传比奇") if not npc_talk_to_biqi(bot): log("[!] 未传比奇") return time.sleep(1) for _ in range(6): bot.pump(0.4) if bot.me.get("mapname") != Q.WAIT_MAP or bot.me["y"] <= 4: log("[2] → @renwu_mov 回等待图可走区") if not renwu_to_wait(bot): log("[!] 建链失败") return log(f"[3] 网格感知游动开始 @{bot.me['x']},{bot.me['y']}") wander(bot, args.minutes * 60, m) finally: g.close() if __name__ == "__main__": main()