|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
import json
|
|
|
import os
|
|
|
import random
|
|
|
+import socket
|
|
|
import sys
|
|
|
import time
|
|
|
from typing import Any
|
|
|
@@ -11,7 +12,7 @@ from typing import Any
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
sys.path.insert(0, os.path.join(ROOT, "protocol_bot"))
|
|
|
|
|
|
-import requests as rq # noqa: E402
|
|
|
+import requests # noqa: E402
|
|
|
import login_996pc as L # type: ignore # noqa: E402
|
|
|
import cq2_pure_enter as P # type: ignore # noqa: E402
|
|
|
from encode6bitbuf_final import encode6, decode6 # type: ignore # noqa: E402
|
|
|
@@ -42,6 +43,7 @@ class Host:
|
|
|
self.jwt = ""
|
|
|
self.uid = ""
|
|
|
self._login_port = 0
|
|
|
+ self._tail = 0
|
|
|
|
|
|
def build_2001_body(self, uid: str, jwt: str) -> bytes:
|
|
|
return (f"{uid}/".encode() + encode6(jwt.encode()) + b"/" +
|
|
|
@@ -52,8 +54,8 @@ class Host:
|
|
|
"job": job, "sex": sex, "account": uid,
|
|
|
"sdkid": CHANNEL}, ensure_ascii=False).encode()
|
|
|
|
|
|
- def http_login(self) -> dict:
|
|
|
- s = rq.Session()
|
|
|
+ def http_login(self) -> requests.Session:
|
|
|
+ s = requests.Session()
|
|
|
j, _ = L.do_login(s, self.user, self.password)
|
|
|
if j.get("code") != 200:
|
|
|
raise SystemExit(f"[!] 登录失败: {j.get('msg')}")
|
|
|
@@ -61,7 +63,7 @@ class Host:
|
|
|
self.uid = str((j["data"].get("userInfo") or {}).get("id") or "")
|
|
|
self.env.data.update({"uid": self.uid, "token": self.jwt,
|
|
|
"new_boxtoken": self.jwt, "isBoxLogin": 1})
|
|
|
- return j
|
|
|
+ return s
|
|
|
|
|
|
def boot_lua(self) -> None:
|
|
|
self.lua = bootstrap_runtime(self.nc, self.ctl, self.sched, self.env, self.state_dir)
|
|
|
@@ -125,32 +127,33 @@ class Host:
|
|
|
break
|
|
|
except (socket.timeout, ConnectionResetError, OSError):
|
|
|
d = b""
|
|
|
- consumed = 0
|
|
|
- found_frame: Frame | None = None
|
|
|
- found_pre: int | None = None
|
|
|
- while consumed + 48 <= len(d):
|
|
|
+ # 半包处理: 每轮把 wire._buf 前置到 data 再扫描, 扫剩的写回 _buf。
|
|
|
+ data = wire._buf + d
|
|
|
+ wire._buf = b""
|
|
|
+ while len(data) >= 48:
|
|
|
try:
|
|
|
- m = struct.unpack("<9i", decode6(d[consumed:consumed + 48]))
|
|
|
+ m = struct.unpack("<9i", decode6(data[:48]))
|
|
|
except Exception:
|
|
|
break
|
|
|
blen = m[7]
|
|
|
+ if len(data) < 48 + blen: # 帧体不完整, 整帧留待下轮
|
|
|
+ break
|
|
|
if m[1] == target_mid:
|
|
|
- found_frame = Frame(recog=m[2], msg_id=m[1], p1=m[3], p2=m[4],
|
|
|
- p3=m[5], tail=m[8],
|
|
|
- body=d[consumed + 48:consumed + 48 + blen])
|
|
|
- found_pre = m[0]
|
|
|
- after = d[consumed + 48 + blen:]
|
|
|
- wire._buf = wire._buf + d[:consumed] + after
|
|
|
- return found_frame, found_pre
|
|
|
- consumed += 48 + blen
|
|
|
- wire._buf = wire._buf + d
|
|
|
+ wire._buf = data[48 + blen:]
|
|
|
+ return Frame(recog=m[2], msg_id=m[1], p1=m[3], p2=m[4],
|
|
|
+ p3=m[5], tail=m[8],
|
|
|
+ body=data[48:48 + blen]), m[0]
|
|
|
+ # 未命中帧 → 丢弃并继续扫下一帧(一次收到多帧的场景)
|
|
|
+ data = data[48 + blen:]
|
|
|
+ wire._buf = wire._buf + data
|
|
|
return None, None
|
|
|
|
|
|
def run_create_role(self, name: str | None = None, timeout: float = 30) -> dict:
|
|
|
"""登录链逐帧驱动(对照 cq2_pure_enter.login_gate 唯一真源,逐帧注释)。
|
|
|
|
|
|
2001(tail=0,recog=random) → 等 529 取 key32(decode6 split /) →
|
|
|
- 104(body=encode6(key32),tail=1) → 等 530 取 sid(recog 字段,cq2_pure_enter:138) →
|
|
|
+ 104(body=encode6(key32),tail=1) → 等 530 取 sid(m[0]/pre 字段,
|
|
|
+ cq2_pure_enter:138) →
|
|
|
nc.set_sdata(sid) → 100(uid/sid/zone/channel/gameid/key32,tail=2) →
|
|
|
等 520 角色 JSON(decode6 后 json.loads) →
|
|
|
角色列表含 name→复用;否则 101(tail=3) → 等 521(decode6 后 split /)
|
|
|
@@ -201,9 +204,11 @@ class Host:
|
|
|
name = "萌新" + str(random.randint(1000, 9999))
|
|
|
chr_info = next((c for c in (roles or {}).get("list", [])
|
|
|
if c.get("chr") == name), None)
|
|
|
- retries = 0
|
|
|
- while chr_info is None and retries <= 5:
|
|
|
- self._send(101, 0, 0, 0, sid, 3,
|
|
|
+ self._tail = 2
|
|
|
+ attempts = 0
|
|
|
+ while chr_info is None and attempts < 5:
|
|
|
+ self._tail += 1
|
|
|
+ self._send(101, 0, 0, 0, sid, self._tail,
|
|
|
self.build_101_body(name, self.uid))
|
|
|
ticket: str | None = None
|
|
|
got_522 = False
|
|
|
@@ -216,17 +221,17 @@ class Host:
|
|
|
ticket = decode6(f.body).decode("utf-8", "replace")
|
|
|
except Exception:
|
|
|
ticket = f.body.decode("utf-8", "replace")
|
|
|
- if f.msg_id == 522:
|
|
|
+ if ticket is None and f.msg_id == 522:
|
|
|
got_522 = True
|
|
|
if ticket or got_522:
|
|
|
break
|
|
|
- if got_522:
|
|
|
- retries += 1
|
|
|
- name = "萌新" + str(random.randint(1000, 9999))
|
|
|
- continue
|
|
|
if ticket:
|
|
|
chr_info = {"chr": name, "UserID": ticket.split("/", 1)[1]}
|
|
|
break
|
|
|
+ if got_522:
|
|
|
+ attempts += 1
|
|
|
+ name = "萌新" + str(random.randint(1000, 9999))
|
|
|
+ continue
|
|
|
break
|
|
|
if chr_info is None:
|
|
|
lst = (roles or {}).get("list") or []
|
|
|
@@ -240,9 +245,12 @@ class Host:
|
|
|
|
|
|
def run_zone(zone_id: int, user: str, password: str, name: str | None = None,
|
|
|
port: int = 8041) -> dict:
|
|
|
- """一站式: HTTP 登录 → 网关连接 → 创角 → 返回票据。"""
|
|
|
+ """一站式: HTTP 登录 → enter_game → 网关连接 → 创角 → 返回票据。"""
|
|
|
h = Host(zone_id, user, password)
|
|
|
- h.http_login()
|
|
|
+ session = h.http_login()
|
|
|
+ j2 = P.enter_game(session, h.jwt, zone_id)
|
|
|
+ if j2.get("code") != 200:
|
|
|
+ raise SystemExit(f"[!] enter_game 失败: {j2.get('msg') or j2}")
|
|
|
h.connect_login_gate(port=port)
|
|
|
info = h.run_create_role(name)
|
|
|
return {"uid": h.uid, "jwt": h.jwt, "zone": h.zone, **info}
|