| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- #!/usr/bin/env python3
- """
- 996传奇客户端 协议编解码 统一 API
- 来源: cpp_reversed/ 目录下各模块汇总
- - hdr_codec: 24B 头编解码 (Encode6BitBuf 算法核心)
- - frame: 36B 头 + body 完整帧装拆
- - xxtea: 脚本密文加解密 (LuaBridgeCtl::SetXXTEAKeyGM 配套)
- - rsa: 客户端本地 RSA 密钥对 (RsaEncryptPub / RsaDecryptPri)
- 定位: 这是 cpp_reversed/ 体系的"使用层入口", 所有"我想发一条消息" / "我想解一条抓包"
- 的操作都从这里走。底层模块仍可单独 import 使用。
- 依赖: 同目录的 hdr_codec.py / frame.py / xxtea.py / rsa.py
- 跨目录 (protocol_bot/) 仅在 self-test 交叉验证时使用
- """
- from __future__ import annotations
- import os
- import sys
- from typing import Optional, Dict # noqa: F401
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
- from hdr_codec import ( # type: ignore
- ENC_TABLE,
- encode_header, decode_header, parse_header,
- )
- from frame import ( # type: ignore
- Frame, HDR_STRUCT, HEADER_PLAIN_SIZE, HEADER_ENC_SIZE,
- pack_frame, unpack_stream,
- MSG_CS_LOGIN, MSG_CS_CREATE_ROLE, MSG_CS_SELECT_ROLE,
- MSG_SC_ROLE_LIST, MSG_SC_CREATE_ROLE_OK, MSG_SC_CREATE_ROLE_FAIL,
- MSG_SC_GAME_SERVER_ADDR, MSG_CS_HEARTBEAT,
- MSG_SC_DISCONNECTED, MSG_ILLEGAL, MSG_BATCH,
- )
- from xxtea import ( # type: ignore
- encrypt as xxtea_encrypt, decrypt as xxtea_decrypt,
- encrypt_padded, decrypt_padded,
- SCRIPT_KEY_RAW, SCRIPT_SIGN,
- )
- from rsa import encrypt_pub, decrypt_pri, PUB_KEY_N, PUB_KEY_E, PRI_KEY_D # type: ignore
- __all__ = [
- # 头编解码 (24B 内部表示)
- "encode_header", "decode_header", "parse_header", "ENC_TABLE",
- # 完整帧
- "Frame", "pack_frame", "unpack_stream", "send", "receive",
- # XXTEA
- "xxtea_encrypt", "xxtea_decrypt", "encrypt_padded", "decrypt_padded",
- "SCRIPT_KEY_RAW", "SCRIPT_SIGN",
- # RSA
- "encrypt_pub", "decrypt_pri",
- # 常用消息号
- "MSG_CS_LOGIN", "MSG_CS_CREATE_ROLE", "MSG_CS_SELECT_ROLE",
- "MSG_SC_ROLE_LIST", "MSG_SC_CREATE_ROLE_OK", "MSG_SC_CREATE_ROLE_FAIL",
- "MSG_SC_GAME_SERVER_ADDR", "MSG_CS_HEARTBEAT",
- "MSG_SC_DISCONNECTED", "MSG_ILLEGAL", "MSG_BATCH",
- # 头结构常量
- "HDR_STRUCT", "HEADER_PLAIN_SIZE", "HEADER_ENC_SIZE",
- ]
- # ============== 高层 API ==============
- def send(msg_id: int, recog: int, p1: int, p2: int, p3: int,
- body: bytes = b"", tail: int = 0) -> bytes:
- """
- 构造一条要发送的线协议消息
- :return: 48B 编码头 + body, 可直接 sendall
- """
- return pack_frame(msg_id, recog, p1, p2, p3, tail, body)
- def receive(buffer: bytes) -> tuple[list[Frame], bytes]:
- """
- 从累积的字节流中切出所有完整帧
- :return: (frames, rest) — frames 是完整帧, rest 是不足一帧的尾部残留 (供下次 recv 拼)
- """
- return unpack_stream(buffer)
- def frame_to_dict(f: Frame) -> dict:
- """Frame → 人类可读的 dict (供日志/调试)"""
- return {
- "msg_id": f.msg_id,
- "recog": f.recog,
- "p1": f.p1,
- "p2": f.p2,
- "p3": f.p3,
- "tail": f.tail,
- "body_len": len(f.body),
- "body_preview": f.body[:32].hex() + ("..." if len(f.body) > 32 else ""),
- }
- def decode_captured(enc_bytes: bytes) -> dict:
- """
- 抓包分析: 编码字节 → 明文头 dict (供 Wireshark 抓 TCP 流后 1-行解析)
- 接受两种输入:
- - 32B (旧 24B 头, 用 hdr_codec 解析)
- - 48B (新 36B 头, 用 frame 解析, 包含 body_len / tail)
- 自动识别: 长度 >= 48 → 走 wire 格式; 否则按 hdr_codec 24B 格式
- """
- if len(enc_bytes) >= HEADER_ENC_SIZE:
- # wire 格式 36B 头: 0, msg_id, recog, p1, p2, p3, 0, body_len, tail
- plain36 = _decode_full_to_36(enc_bytes[:HEADER_ENC_SIZE])
- zero, msg_id, recog, p1, p2, p3, zero2, body_len, tail = HDR_STRUCT.unpack(plain36)
- return {
- "msgId": msg_id, "recog": recog,
- "p1": p1, "p2": p2, "p3": p3,
- "body_len": body_len, "tail": tail,
- }
- elif len(enc_bytes) >= 32:
- # 24B 旧格式 (hdr_codec)
- plain24 = decode_header(bytes(enc_bytes[:32]))
- return parse_header(plain24)
- else:
- raise ValueError(f"至少需要 32B 编码头, 实际={len(enc_bytes)}")
- def _decode_full_to_36(enc48: bytes) -> bytes:
- """48B 编码头 → 36B 明文头 (与 frame._decode_full 相同, 此处独立以避免循环引用)"""
- from frame import _decode_full
- return _decode_full(enc48, HEADER_PLAIN_SIZE)
- # ============== 反汇编地址速查 (供"我想精读某函数实现"用) ==============
- ADDR = {
- "encode_header": "0x10004b4e4", # 头编码 (24B→32B)
- "decode_header": "0x10004b594", # 头解码 (32B→24B)
- "SendNetMessage": "0x10003d408", # 加密+入队
- "LuaNetworkController_SendMsg": "0x10005f8cc",
- "tolua_SendMsg_binding": "0x10007cd54",
- "netClient_recv_loop": "0x100023df8",
- "netClient_recv_dispatch": "0x10003cd30",
- "LuaBridgeCtl_SetXXTEAKeyGM": "0x100044b88",
- "init_keys_and_sign": "0x100043bdc",
- "xor_deobfuscate": "0x100043b34",
- }
- if __name__ == "__main__":
- import sys
- import os
- _PARENT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- if _PARENT not in sys.path:
- sys.path.insert(0, _PARENT)
- from protocol_bot.encode6bitbuf_final import build_frame, encode6
- from frame import HDR_STRUCT
- print("msg_codec.py — 996传奇客户端 协议编解码 统一 API")
- print("=" * 60)
- print()
- # 测试 1: send → receive roundtrip
- body = b'{"chr":"\xe8\x90\x8c\xe6\x96\xb0","job":0,"sex":1}'
- sent = send(MSG_CS_CREATE_ROLE, 900000, 12345, 37504, 0, body)
- print(f"[1] send(): msg_id={MSG_CS_CREATE_ROLE}, body={len(body)}B")
- print(f" wire: {len(sent)}B = 48 header + {len(body)} body")
- print(f" hex: {sent.hex()}")
- print(f" ascii: {''.join(chr(b) if 0x3C<=b<=0x7B else '.' for b in sent[:48])}")
- print()
- frames, rest = receive(sent)
- assert rest == b""
- assert len(frames) == 1
- f = frames[0]
- print(f"[2] receive(): {len(frames)} frame, rest={rest!r}")
- print(f" decoded: {frame_to_dict(f)}")
- assert f.body == body
- print()
- # 测试 3: 编码头与 protocol_bot 字节级一致
- sent_no_body = send(MSG_CS_CREATE_ROLE, 900000, 12345, 37504, 0, b"")
- other = build_frame(MSG_CS_CREATE_ROLE, 900000, 12345, 37504, 0)
- assert sent_no_body == other, "与 protocol_bot 不一致"
- print(f"[3] wire format 与 protocol_bot 字节级一致 ✓")
- print()
- # 测试 4: parse_header 抓包分析
- enc = sent[:48] # 前 48B 编码头 (wire 格式)
- fields = decode_captured(enc)
- print(f"[4] decode_captured(48B wire): {fields}")
- assert fields["msgId"] == MSG_CS_CREATE_ROLE
- assert fields["recog"] == 900000
- assert fields["p1"] == 12345
- print()
- # 测试 4b: decode_captured 也支持 24B 旧格式
- legacy24 = encode_header(b"\x00" * 24) # 占位
- legacy_dict = decode_captured(legacy24)
- print(f"[4b] decode_captured(32B legacy): keys={sorted(legacy_dict.keys())} ✓")
- print()
- # 测试 5: XXTEA 自洽 (脚本密文场景)
- from xxtea import encrypt as xe, decrypt as xd
- test_key = b"0123456789abcdef"
- test_plain = b"Hello XXTEA!!! " # 16 字节 (8 倍数)
- enc = xe(test_plain, test_key)
- dec = xd(enc, test_key)
- assert dec == test_plain
- print(f"[5] XXTEA roundtrip: '{test_plain.decode()}' → {enc.hex()} → '{dec.decode()}' ✓")
- print()
- # 测试 6: 多包粘包
- f1 = send(MSG_CS_LOGIN, 0, 0, 0, 0, b"*#acct/role/12345/9/1/0", 9)
- f2 = send(MSG_SC_ROLE_LIST, 0, 0, 0, 0, b"key32/session123", 0)
- multi = f1 + f2
- frames, rest = receive(multi)
- assert rest == b""
- assert len(frames) == 2
- assert frames[0].msg_id == MSG_CS_LOGIN
- assert frames[1].msg_id == MSG_SC_ROLE_LIST
- print(f"[6] 多包粘包: 2 帧 [{frames[0].msg_id}, {frames[1].msg_id}] ✓")
- print()
- # 测试 7: RSA 接口(应抛 NotImplementedError, 因密钥未提取)
- try:
- encrypt_pub(b"test")
- print("[7] FAIL: 应抛 NotImplementedError")
- except NotImplementedError as e:
- print(f"[7] RSA encrypt_pub 接口: NotImplementedError (密钥待提取) ✓")
- print()
- print("=" * 60)
- print("All msg_codec self-tests passed.")
- print()
- print("可用入口:")
- print(" send(msg_id, recog, p1, p2, p3, body) → 48B 头 + body")
- print(" receive(buffer) → (frames, rest)")
- print(" frame_to_dict(f) → 调试 dict")
- print(" decode_captured(32B 编码头) → 抓包解析 dict")
|