wuzilong 5aadc8ec3a 提交” 1 kuukausi sitten
..
LuaBridgeCtl_SetXXTEAKeyGM_impl.c 5aadc8ec3a 提交” 1 kuukausi sitten
LuaNetworkController_SendMsg.c 5aadc8ec3a 提交” 1 kuukausi sitten
README.md 5aadc8ec3a 提交” 1 kuukausi sitten
SUMMARY.md 03f35f1f01 docs(cpp_reversed): 0.map额外区slot编码静态分析收官 + 全量密钥总账归档 1 kuukausi sitten
box_data_passing.md 5aadc8ec3a 提交” 1 kuukausi sitten
decode_header.c 5aadc8ec3a 提交” 1 kuukausi sitten
encode_header.c 5aadc8ec3a 提交” 1 kuukausi sitten
frame.py 5aadc8ec3a 提交” 1 kuukausi sitten
hdr_codec.py 5aadc8ec3a 提交” 1 kuukausi sitten
http_downloader.py 5aadc8ec3a 提交” 1 kuukausi sitten
init_keys_and_sign.c 5aadc8ec3a 提交” 1 kuukausi sitten
keys_inventory.md 03f35f1f01 docs(cpp_reversed): 0.map额外区slot编码静态分析收官 + 全量密钥总账归档 1 kuukausi sitten
label_batch.py 5aadc8ec3a 提交” 1 kuukausi sitten
map_format.md 03f35f1f01 docs(cpp_reversed): 0.map额外区slot编码静态分析收官 + 全量密钥总账归档 1 kuukausi sitten
map_parser.py 5aadc8ec3a 提交” 1 kuukausi sitten
mmoclient_interfaces.h 5aadc8ec3a 提交” 1 kuukausi sitten
msg_codec.py 5aadc8ec3a 提交” 1 kuukausi sitten
netClient_SendNetMessage.c 5aadc8ec3a 提交” 1 kuukausi sitten
netClient_recv_dispatch.c 5aadc8ec3a 提交” 1 kuukausi sitten
netClient_recv_loop.c 5aadc8ec3a 提交” 1 kuukausi sitten
path_find.py 5aadc8ec3a 提交” 1 kuukausi sitten
rsa.py 5aadc8ec3a 提交” 1 kuukausi sitten
rsa_investigation.md 5aadc8ec3a 提交” 1 kuukausi sitten
tolua_SendMsg_binding.c 5aadc8ec3a 提交” 1 kuukausi sitten
xor_deobfuscate.c 5aadc8ec3a 提交” 1 kuukausi sitten
xxtea.py 5aadc8ec3a 提交” 1 kuukausi sitten

README.md

mmoclient C++ 反编译还原产物

来源:996传奇盒子 主二进制(9.6MB arm64,符号strip) 工具:radare2 6.2.2 (af;pdc 伪C)+ 人工反汇编精读 + roundtrip 验证 阶段:2026-09-21 起,2026-09-23 三阶段全交付 完整总览见 SUMMARY.md

一句话总结

将 9.6MB stripped arm64 二进制的 C++ 自研层 (mmoclient, 33 类 418 方法) 还原为 9 个可运行 Python 模块 + 10 份伪 C + 1 个完整接口契约 + 158 项回归测试。

能还原到什么程度

C++ 编译有损(无符号/无RTTI/内联优化),不可能还原可编译源码; 本目录产出四档:

  1. .c 文件:r2 自动生成的伪C(寄存器名,供核对地址/调用关系)
  2. mmoclient_interfaces.h:从 tolua 注册表提取的 33 类 418 方法接口契约
  3. *.py 算法等价库:人工精读反汇编后重写的可运行等价实现(roundtrip 验证)
  4. 格式逆向报告 (*.md):数据文件格式 + 字段语义 + 验证样本

文件清单 (按子目录功能分组)

接口契约 (100% 准确)

  • mmoclient_interfaces.h — 33 类 418 方法 .pkg 风格声明

算法等价 (高准确,可运行)

  • hdr_codec.py — 24B 头 ↔ 32B 编码 (50 组随机 roundtrip ✓)
  • xxtea.py — XXTEA 加解密 (第三方 6.2.0 cross-verify ✓)
  • frame.py — 36B 头 + body 完整帧 (与 protocol_bot 字节级一致 ✓)
  • rsa.py — 客户端本地 RSA 接口 (已定论: 二进制中两方法均为空壳 stub, 无密钥)
  • msg_codec.py — 协议编解码统一 API (33 测试 ✓)
  • map_parser.py — WGS Mir2 .map 解析 (7/7 文件字节级 roundtrip ✓)
  • path_find.py — A* 寻路 (7/7 map e2e ✓, 189 格长直道代价 1890 完美)
  • http_downloader.py — 热更下载 (httpbin 1024B 实下载 ✓)
  • label_batch.py — 批量文本渲染 (数据层 ✓)

格式逆向报告

  • map_format.md — .map 文件 52B header + 12B cell + 0.map 扩展格式
  • rsa_investigation.md — RSA 探索全程记录 (7 次失败 → aaa 突破 → stub 定论)
  • box_data_passing.md — 996盒子→游戏数据传递机制 (env.json 双通道 + CS20 握手去向)
  • SUMMARY.md — 三阶段总览(必读)

结构伪C (r2 pdc 生成,变量名是猜测)

快速入门 (按使用场景)

A. 抓包分析 (5 行搞定)

from msg_codec import decode_captured
fields = decode_captured(pcap_packet[:48])
# {'msgId': 101, 'recog': 900000, 'p1': 12345, 'p2': 37504, ...}

B. 构造发包

from msg_codec import send, MSG_CS_CREATE_ROLE
body = b'{"chr":"\xe8\x90\x8c\xe6\x96\xb0","job":0,"sex":1,"server_name":"\xe6\xaf\x94\xe5\xa5\x87","sdkid":"37504"}'
sock.sendall(send(MSG_CS_CREATE_ROLE, 900000, 12345, 37504, 0, body))

C. 收包解帧

from msg_codec import receive, frame_to_dict
buf = b""
while chunk := sock.recv(65536):
    buf += chunk
    frames, buf = receive(buf)
    for f in frames:
        print(frame_to_dict(f))

D. 解析 .map 地图

from map_parser import load
m = load("scene/map/3.map")
print(f"{m.header.title!r}  {m.header.width}x{m.header.height}")

c = m.get_cell(500, 400)
print(f"bg={c.bg_img} obj={c.obj_img} walkable={c.is_walkable}")
print(m.stats())  # {'total': 800000, 'walkable': 703221, 'walkable_pct': 87.9, ...}

# 编辑并写回
m.get_cell(0, 0).bg_img = 999
with open("/tmp/modified.map", "wb") as f:
    f.write(m.to_bytes())

E. A* 寻路

from map_parser import load
from path_find import find_path, path_cost, path_is_valid

m = load("scene/map/3.map")

# 短距离 (≤48x32 包围盒) — 单次调用
path = find_path(m, 100, 100, 110, 100, dis_x=50, dis_y=50)

# 长距离 — 分段拼接
from path_find import find_path_extended
path = find_path_extended(m, 250, 200, 700, 600, chunk_size=20)

assert path_is_valid(m, path)
print(f"路径 {len(path)} 点, 代价 {path_cost(m, path)}")

F. 热更下载

import http_downloader as dl
dl.set_download_dir("/tmp/cache")
dl.set_download_url("https://patch.example.com/996/")
dl.set_timeout(30)
dl.set_speed_limit(1024 * 1024)

task = dl.async_download_ex("res/0.map", "scene/map/0.map", sha1="abc123...")
print(f"http={task.http_code} bytes={task.downloaded} ok={task.error is None}")

G. 批量文本

from label_batch import LabelBatchNode, LabelBatchString
node = LabelBatchNode(font_atlas_path="fonts/arial_14.png")
for i, dmg in enumerate([100, -50, 200, -75]):
    s = LabelBatchString(text=f"{dmg:+d}", x=i*40, y=200,
                          color=(255, 80, 80, 255) if dmg < 0 else (80, 255, 80, 255))
    node.add_label_string(s)
print(f"batch: {len(node.strings)} strings, 1 draw call")

H. XXTEA 脚本密文

from xxtea import encrypt, decrypt, SCRIPT_KEY_RAW
plain = decrypt(cipher_bytes, SCRIPT_KEY_RAW.ljust(16, b"\x00"))

I. RSA (已定论: 空壳 stub)

from rsa import encrypt_pub, decrypt_pri
encrypt_pub(b"data")  # NotImplementedError: 密钥未从二进制提取

算法摘要

头编码 (24B → 32B)

明文字节 b → ENC_TABLE[b] ^ 9 → 位流重组(8bit 窗口每次吐 6bit) → +0x3C
24B 明文 → 32B 密文 (全 0x3C..0x7B 可打印)
ENC_TABLE: 256B, __DATA @0x1008ae5f4

地图格式

52B header + (w*h × 12B cells)
header: width(2) + height(2) + title_len(1) + title(N) + tail(34 raw)
cell: bg_img(2) + mid_img(2) + obj_img(2) + ani_idx(2) + door_flag(1) + light(1) + area(1) + ani_tick(1)
扩展格式 (title_len=15, 仅 0.map): header + 980000B 2B/cell 稀疏索引 + cells

寻路 (A*)

8 方向, 直线 10 / 对角 14, 启发式 = 欧氏距离²
二叉堆优先队列, 网格内最优, 对角穿越自动阻止
寻路范围限制 48×32 (与 Lua updateLimits 一致, 长距离需分段)

测试

# 阶段 1 算法库 (33) + 阶段 2 地图解析 (38+8 extra_areas+10 header语义) + 阶段 3 寻路 (41) + 原有 (28) = 158 总
cd /path/to/传奇
.venv/bin/python -m pytest tests/ -m "not live" -v

# 实际下载测试
python3 cpp_reversed/http_downloader.py
# 期望: saved 1024B to /tmp/httpdl_test/test.bin

未还原部分 (诚实记录)

缺口 难度 解法
RSA 客户端本地密钥对 中-高 ✅ 已定论: r2 aaa 完整分析证实两方法为空壳 stub (返回空串), 密钥不存在于二进制, 证据链见 rsa_investigation.md 与 rsa.py INSTALL_HINT
0.map 额外 980000B slot 编码 中 精读 tileSceneManager3:LoadMapData 循环,确定 4 个图槽位布局
header 18-51 字段精确含义 中 ✅ 已解 (2026-09-23): 字段已语义化 (ambient_id/editor_build/format_id), 含字节序勘误, 详见 map_format.md §二
mapData2DRectGrid/Tile3/LightBuffer 中 与运行时内存表示有关,需场景运行时抓 dump
ObjC 壳层 (SDKController) 中 属另一文档范围,需 arm64 ObjC ABI 工具

完整版本与上下文见 SUMMARY.md 和 map_format.md。

路线图 (给后续读者)

  1. 先用 msg_codec.py 做抓包分析 — 它 1 行能解出 msgId/recog/params
  2. 再用 map_parser.py 解析地图 — 7 个样本可立即分析
  3. 如果做 bot, 直接用 path_find.find_path() 替代手写 A*
  4. RSA 已有定论: 两方法是空壳 stub (rsa_investigation.md), encrypt_pub/decrypt_pri 保持 NotImplementedError 是正确终态
  5. 要继续还原 0.map 额外区, 入口是 map_format.md §四 "待精读" 段
  6. 要对比 .c 伪代码和 Python 实现, 任何 *.c 文件都对应一个 Python 模块

最后修改: 2026-09-23 17:09