| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- """test_tp_minimal.py — 传送最小变量实验: 字节级复刻真客户端传送链。
- 2026-09-29 抓包对照分析(cap_tp.jsonl)得出的 3 个差异, 本脚本全部修正:
- 1. 1011 body 键序: {"UserID":..,"index":..,"Act":..}(真) ≠ bot 旧序(UserID,Act,index)
- 2. 传送窗口零噪音: 不发 3199/9102/5286(真客户端窗口内只有 1010/1011×2/ACK+心跳)
- 3. 时序对齐: 1010 后 2.0s 点矿区, 再 1.15s 点进入地图, 再 20ms 发 ACK
- ACK body='比奇矿区'(中文, 与真客户端一致)
- 成功判据: 发完 ACK 后 10s 内 world.me.map_name 变为 比奇矿区。
- """
- from __future__ import annotations
- import json
- import logging
- import sys
- import time
- import requests
- from pbot.config import Account
- from pbot.game.messages import protocol_map_name
- from pbot.game.session import enter_world, apply_batch
- from pbot.login.box_client import enter_game_client, login
- from pbot.transport.gateway import login_gate_with_retry
- from pbot.types import ZoneId
- from pbot.bot.wander import DIRS
- _LOG = logging.getLogger("tp_min")
- PORTS = (8021, 8061, 8001, 8041)
- TP_AID = "10002"
- TP_POS = (342, 338)
- def js(objects: dict) -> bytes:
- """按传入键序序列化(真客户端字节级复刻的关键)。"""
- return json.dumps(objects, separators=(",", ":")).encode()
- def main() -> int:
- logging.basicConfig(level=logging.INFO,
- format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
- stream=sys.stdout)
- http = requests.Session()
- http.trust_env = False
- http.verify = False
- jwt, uid = login(Account(username="longwu988", password="Longwu1988"))
- _ = enter_game_client(http, jwt, ZoneId(14570166))
- _k, sid, name, ticket, gport = login_gate_with_retry(
- ZoneId(14570166), jwt, uid, "新角色", PORTS)
- stop = [False]
- conn, world, upd = enter_world(ZoneId(14570166), name, ticket, sid,
- gport, uid, stop=stop)
- _LOG.info("出生 %s @(%d,%d)", world.me.map_name, world.me.x, world.me.y)
- if world.me.map_name == "比奇矿区":
- _LOG.info("[i] 已在矿区, 无需传送(实验目的达成?)")
- stop[0] = True
- conn.close()
- return 0
- if world.me.map_name != "盟重土城":
- _LOG.error("[✗] 出生 %s 非盟重, 实验终止", world.me.map_name)
- stop[0] = True
- conn.close()
- return 1
- try:
- # ---- 走到传送员(真客户端: 9 步 3013 后点菜单) ----
- _LOG.info("[走] → 传送员%s", TP_POS)
- for _ in range(60):
- me = world.me
- if max(abs(TP_POS[0] - me.x), abs(TP_POS[1] - me.y)) <= 1:
- break
- dx = max(-1, min(1, TP_POS[0] - me.x))
- dy = max(-1, min(1, TP_POS[1] - me.y))
- h = next((i for i, (ox, oy) in enumerate(DIRS)
- if ox == dx and oy == dy), 4)
- mn = protocol_map_name(me.map_id, me.map_name)
- conn.send_frame(3013, 0, h, (me.y << 16) | me.x, sid,
- mn.encode(), pre=((me.y + dy * 2) << 16) | (me.x + dx * 2))
- time.sleep(0.55)
- apply_batch(world, upd, conn.recv_frames(0.4))
- _LOG.info("[走✓] @(%d,%d) — 静置1s(对齐真客户端节奏)",
- world.me.x, world.me.y)
- time.sleep(1.0)
- apply_batch(world, upd, conn.recv_frames(0.8))
- # ---- [帧1] CS1010 开菜单(字节级同真客户端) ----
- conn.send_frame(1010, 0, 0, 0, sid, js({"UserID": TP_AID, "index": 2}))
- _LOG.info("[帧1] CS1010 开菜单 — 等 2.0s(真客户端含一次心跳)")
- time.sleep(2.0)
- apply_batch(world, upd, conn.recv_frames(1.5))
- _LOG.info("菜单到达: %s", world.npc_dialog is not None)
- # ---- [帧2] CS1011 点【比奇矿区】(键序: UserID,index,Act) ----
- body2 = js({"UserID": "999999999", "index": 999999999,
- "Act": "@jiuxian_move_1,1,1"})
- conn.send_frame(1011, 0, 0, 0, sid, body2)
- _LOG.info("[帧2] CS1011 @jiuxian_move_1,1,1 (blen=%d, 键序=UserID,index,Act)",
- len(body2))
- time.sleep(1.15)
- apply_batch(world, upd, conn.recv_frames(0.8))
- # ---- [帧3] CS1011 点【进入地图】 ----
- body3 = js({"UserID": "999999999", "index": 999999999,
- "Act": "@act_move,1"})
- conn.send_frame(1011, 0, 0, 0, sid, body3)
- _LOG.info("[帧3] CS1011 @act_move,1 (blen=%d)", len(body3))
- # ---- [帧4/5] ACK 5012+5027('比奇矿区') — 20ms 内 ----
- time.sleep(0.02)
- conn.send_frame(5012, 0, 0, 0, sid, "比奇矿区".encode())
- conn.send_frame(5027, 0, 0, 0, sid, "比奇矿区".encode())
- _LOG.info("[帧4/5] ACK 5012+5027('比奇矿区') — 等落地 12s")
- # ---- 判定 ----
- for i in range(24):
- time.sleep(0.5)
- apply_batch(world, upd, conn.recv_frames(0.4))
- if world.me.map_name == "比奇矿区":
- _LOG.info("[✓✓✓] 传送成功! %s @(%d,%d) (耗时 %.1fs)",
- world.me.map_name, world.me.x, world.me.y,
- (i + 1) * 0.5)
- stop[0] = True
- conn.close()
- return 0
- if (i + 1) % 4 == 0:
- _LOG.info(" ... %.1fs 仍在 %s", (i + 1) * 0.5,
- world.me.map_name)
- _LOG.error("[✗] 12s 未落地 — 字节级复刻仍静默, 剩余变量: 会话冷却")
- stop[0] = True
- conn.close()
- return 1
- except KeyboardInterrupt:
- stop[0] = True
- conn.close()
- return 130
- if __name__ == "__main__":
- sys.exit(main())
|