# mmoclient C++ 反编译还原产物 > 来源:`996传奇盒子` 主二进制(9.6MB arm64,符号strip) > 工具:radare2 6.2.2 (`af;pdc` 伪C)+ 人工反汇编精读 + roundtrip 验证 > 阶段:2026-09-21 起,2026-09-23 三阶段全交付 > 完整总览见 [`SUMMARY.md`](./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 生成,变量名是猜测) - `tolua_SendMsg_binding.c` @0x10007cd54 - `LuaNetworkController_SendMsg.c` @0x10005f8cc - `netClient_SendNetMessage.c` @0x10003d408 - `encode_header.c` / `decode_header.c` @0x10004b4e4/94 - `netClient_recv_loop.c` / `recv_dispatch.c` @0x100023df8 / 0x10003cd30 - `xor_deobfuscate.c` @0x100043b34 - `init_keys_and_sign.c` @0x100043bdc (555 行,含全部密钥对) - `LuaBridgeCtl_SetXXTEAKeyGM_impl.c` @0x100044b88 ## 快速入门 (按使用场景) ### A. 抓包分析 (5 行搞定) ```python from msg_codec import decode_captured fields = decode_captured(pcap_packet[:48]) # {'msgId': 101, 'recog': 900000, 'p1': 12345, 'p2': 37504, ...} ``` ### B. 构造发包 ```python 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. 收包解帧 ```python 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 地图 ```python 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* 寻路 ```python 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. 热更下载 ```python 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. 批量文本 ```python 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 脚本密文 ```python from xxtea import encrypt, decrypt, SCRIPT_KEY_RAW plain = decrypt(cipher_bytes, SCRIPT_KEY_RAW.ljust(16, b"\x00")) ``` ### I. RSA (已定论: 空壳 stub) ```python 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 一致, 长距离需分段) ``` ## 测试 ```bash # 阶段 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`](./SUMMARY.md) 和 [`map_format.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