# cg2d_hook_cnmlb_dll **Repository Path**: lover520f_admin/cg2d_hook_cnmlb_dll ## Basic Information - **Project Name**: cg2d_hook_cnmlb_dll - **Description**: 魔力宝贝 0.55 客户端增强注入 DLL(cnmlb.dll):binset 图档偏移修正/图档监视器/快速切图/图档扩容/速度控制等 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2026-10-26 - **Last Updated**: 2026-10-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # cg2d\_hook\_dll 魔力宝贝客户端(cg2d.exe 0.55 版)增强注入 DLL,成品为 `cnmlb.dll`。 ## 仓库文件说明 | 文件 | 作用 | | ------------------- | ---------------------------------------------- | | `cg2d_features.cpp` | 全部功能的唯一源码(单文件,UTF-8 编码,含 `/utf-8` 要求) | | `cg2d_features.def` | DLL 导出定义(导出 `cnmlb` / `ForceLoad`,配合 PE 导入表注入) | | `build_dll.bat` | 一键编译脚本(自动调用 VS x86 环境,产出 `cnmlb.dll`) | | `cnmlb.dll` | 编译成品(x86 32 位,直接可用) | | `binset.txt` | 图档偏移修正配置示例 | | `AI_BUILD_GUIDE.md` | **AI 引导式编译指南**:拉取仓库后 AI 照此向用户逐项提问功能开关,再编译/验证/报告 | | `BUILD_OPTIONS.md` | 功能清单(F0–F10、P1–P4)、编译规范与二进制验证清单 | | `README.md` | 本说明(功能 + 使用方法 + 交接文档) | | `_build.ps1` | 加固版一键编译脚本(UTF-8 BOM + 源码/DLL 双重校验 + 部署回读校验) | | `修复记录_2026-09-21_升级野跳崩溃.md` | 2026-09-21 升级野跳崩溃的完整定位与修复记录 | ## 注入方式 - 用 CFF Explorer 打开 `cg2d.exe` → `Import Table` → 新增导入 `cnmlb.dll`,导入函数填 `cnmlb`(或 `ForceLoad`),保存。 - 把 `cnmlb.dll` 放到 `cg2d.exe` 同目录。下次启动游戏即自动加载。 > 目标客户端固定为 **cg2d.exe 0.55 版(MD5** **`9A5DF018EA7DECF81DA8956EFABF6893`)**,代码内所有 RVA 都针对该版本。 > 注:CFF Explorer 改导入表后 MD5 会变,属正常(如现用客户端 MD5 `A56514698E3BF4699AEC16F3C35961A9`,代码段与 0.55 一致)。 ## 编译方法 ```bat rem 需要本机装有 VS(任意带 MSVC x86 工具链的版本) build_dll.bat ``` 脚本内部逻辑:调用 VS 开发者环境 `vcvarsall.bat x86`,然后 `cl /nologo /O2 /MT /LD /utf-8 /Fe:cnmlb.dll cg2d_features.cpp user32.lib ws2_32.lib gdi32.lib winmm.lib shell32.lib /link /DEF:cg2d_features.def`。 > 源码是 UTF-8 编码,编译必须带 `/utf-8`,否则中文界面/文字会乱码。 *** ## 功能清单与使用方法 ### 1. NPC 名字显示 patch RVA `0x437B7` 的 6 字节掩码判断,放开 NPC 名字显示。自动生效,无需操作。 ### 2. 弹窗自动居中 - 作用:当游戏窗口分辨率大于 640×480 时,把系统级弹窗(WS\_POPUP / 置顶 / 工具窗)自动移动到主窗口中央,避免偏在左上角。 - 机制:CBT hook 即时发现 + 每秒 EnumWindows 扫描,`SetWindowPos` 居中。基于 Windows 消息层,只处理真实系统弹窗;**游戏自绘 UI 弹窗不在此功能范围**(如需自绘弹窗对齐,需要移植渲染层 hook,见交接说明第 6 条)。 ### 3. 弹窗自动确认 + 自动关闭 - 作用:对检测到的系统弹窗自动确认并关闭,无需手动点击。 - 细节: - 依次向弹窗发送 `WM_COMMAND/IDOK`、`WM_CLOSE`(确认后直接把窗口关掉)、ENTER 与 SPACE 键(含 `keybd_event`,兼容 DirectInput 输入); - 另 hook 了 `MessageBoxA/W / MessageBoxIndirectA/W` 系列:对版权/提示类弹窗直接返回默认按钮结果——窗口根本不弹出(等同于自动确认 + 自动关闭)。 - 触发范围:EnumWindows 扫到的本进程 WS\_POPUP / 置顶 / 工具窗(与功能 2 同一套 CBT + 每秒扫描)。 - 机制:与功能 2 共用 CBT hook + EnumWindows 扫描。 ### 4. 战斗速度控制(1\~5 倍) 客户端收到(或发出)的文本封包中出现 `rsspd N`(N=1..5)即切换战斗速度。N 由服务端/脚本以明文文本形式随封包下发。 - **send 方向**:玩家点速度面板按钮 `sendPacket('rsspd '..i)` 主动发出,cnmlb 在 `send`/`WSASend` 钩子里扫描并触发速度切换。 - **recv 方向**:服务端 `Protocol.Send(player,'rsspd', 1)` 强制恢复 1x(PK 战 / Boss 战 / 退出战斗 / GM 加速命令 `/加速`),cnmlb 在 `recv`/`WSARecv` 钩子里复用同一份 `ScanForRsspd`,确保强制恢复生效。 两条路径共享同一份钩子实现(FEATURE 7 fasttrans 已有的 `HookedRecv`/`HookedWSARecv`),`g_RealRecv`/`g_RealWSARecv` 由 `InitFastTransHooks` 统一安装。 **合入 trvlcg 假加速修复**(2025-08 版): - `SpeedTimer` 频率 500ms → **200ms**(持续强制写速度字节,对抗引擎主循环重置)。 - 每次写后立即读回验证:实际值与期望不一致时记 3s 节流的 `[SPEED]` 诊断日志。 - `ApplyBattleSpeed(N)`:设 `g_battleSpeed = N`,下一 tick 由 `SpeedTimer` 写入引擎内存。 **前端 lua 校正循环**(合入 trvlcg 配套 lua 修复,防 UI 与服务端速度不一致): - onClick 加 `self._pendingSpd = i`(记录玩家点击)。 - nowspeed 分支开头加 sync 状态机:若 `self._syncingSpd=true` 跳过(防死循环);否则若 `pendingSpd ~= 速度` 则 `sendPacket('rsspd ' .. 速度)` 校正。 - 退出战斗分支清 `self._pendingSpd = nil` / `self._syncingSpd = false`(防跨战斗继承旧值)。 ### 5. 账号保持 / 记住登录 - 启动参数带 `/pw:<密码> /uu:<账号>`(如登录器拼到游戏命令行)。 - 首次登录时自动捕获登录封包;断线重连时重放捕获的 XCNY 登录包实现自动重登。 ### 6. binset 图档偏移修正(对应旧 rs\_hoho/rs\_blt 方案,完全自研) 读取 `binset.txt`(行格式 `最小图号|最大图号|X偏移|Y偏移`),把命中图号的绘制偏移量改写为 `原值 + 配置值`。 - 游戏内发送文本 `/binreload`:重新读取并应用(先回滚再应用,幂等,不叠加)。 - 游戏内发送文本 `/bincheck`:打开「图档监视器」窗口。 - 命中解析:图号先查 公共图号→运行时行号 映射表;查不到时按“大号自加图档”直接当表行号使用,并用行内宽高(1\~2048)校验防误写。 - 记录行内 offsetX/offsetY(int16)位于行偏移 `+0x0C / +0x0E`。 **图档监视器**(窗口标题「图档监视器」,使用游戏图标): - 顶部一行控件:`暂停/继续` 按钮、`最小/最大` 两个输入框、`启用筛选/关闭筛选` 按钮。 - 每行显示一个图档:`pub / gid / slot / x / y / w / h / off / q`(pub=公共图号,gid=表行自带唯一 id,slot=运行时行号,off=当前偏移,q=队列类型)。 - 双击任意行复制整行文本。 - 筛选:输入 pub 最小/最大后点「启用筛选」,把范围内出现过的 pub 逐行固定显示(一个 pub 一行、去重、从小到大排序、不闪烁不消失);再次点击可关闭筛选恢复正常模式(只显示变化行)。 - Tab 键可在输入框/按钮间切换焦点;窗口可拉伸,列表随窗口放大。 ### 7. 快速切图(fasttrans,默认关闭) - 作用:切图/过图不再播放大范围淡入淡出转场(把 12 个转场调用点改写为 `mov eax,1`)。 - 开关:默认关闭。由**服务端 lua 发包控制**:DLL 在客户端 **recv 方向**识别明文文本封包,收到 `fasttrans on` 即开启、`fasttrans off` 即还原原始字节(与战斗调速 rsspd 同一条下发通道)。 - 服务端下发示例(lua): ```lua -- 开启快速切图 Protocol.Send(player, 'fasttrans', 'on') -- 关闭快速切图 Protocol.Send(player, 'fasttrans', 'off') ``` - **建议做成 VIP 专属**:不要在登录/进服时对所有人下发 on,而是只对 VIP 玩家下发,其余玩家保持默认关闭。可封装成一个简单 lua(例如以 GM 命令或对话触发,仅 VIP 角色可用),示例骨架: ```lua -- 伪代码示意:服务端接收玩家命令,判断 VIP 后下发 if isVip(player) then Protocol.Send(player, 'fasttrans', arg == 'on' and 'on' or 'off') else -- 非 VIP:确保处于关闭状态 Protocol.Send(player, 'fasttrans', 'off') end ``` - 客户端不做任何权限判断(收到 on 就开),权限控制完全由服务端下发逻辑决定。 - 12 个调用点 RVA 与原始字节见源码 `FEATURE 7`(每个点写入前逐字节校验,不匹配跳过)。 ### 8. 图档容量扩容(上限 9,000,000) - 作用:客户端图号上限从原始 `2,500,000` 提升到 `9,000,000`,支持超大自制图档库。 - 代码定义:`#define kMaxGraphics 9000000`(`cg2d_features.cpp` L1104)。 - 注入启动即自动执行:先诊断原上限(`initImm`),9 个补丁点全部字节校验匹配后,由后台 worker(2ms 重试)改写: - init 立即数(RVA `0x57528`/imm `0x5752E`)与 7 处容量装载指令 → 9,000,000 - 两个 call 重定向:`0x577AA` → 清槽后写全局上限、`0x5BABA` → 坐标加载改堆缓冲(原 8000 字节栈缓冲只够 1000 条,扩容后必须动态分配) - 全局上限变量(由 init 指令 disp32 动态定位,回退 RVA `0xC3CFA0`) - 若加载到 cgmsv.dll 同步其 `0x191678` - 任何一点字节不匹配都会安全跳过,不会写坏客户端。换客户端版本前请先跑诊断验证。 *** ## 关键 RVA / 结构速查(0.55 cg2d.exe) | 用途 | 地址/说明 | | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | NPC 名掩码 | `0x437B7`(字节 3:0x02→0x12) | | GraphicInfo 运行时表指针 | `0x5A2970`,行宽 32 字节 | | 行内字段 | offsetX `+0x0C`、offsetY `+0x0E`、width `+0x10`、height `+0x12`、唯一 id(gid) `+0x1C` | | 公共图号→行号映射表指针 | `0x5A2974`(每号 4 字节) | | 各 bin 文件起始图号表 | `0x33F100`(每文件 4 字节,可据此反推某 pub 属于几号 bin) | | Sprite 绘制队列 | 数组 `0xBF4EB8`(40 字节/条:+0 x、+4 y、+8 resource、+0x24 queueType),计数 `0xC20EB8`,排序表 `0xC1CEB8` | | 图档上限 init | `0x57528`(`C7 05` 指令),立即数在 `0x5752E`;上限全局变量由该指令 disp32 定位(回退 `0xC3CFA0`) | | 容量装载点 | `0x5754B / 0x5755F / 0x57600 / 0x5761F / 0x59B15 / 0x59C18 / 0x59DB5` | | call 重定向 | `0x577AA`→原 `0x123350`;`0x5BABA`→原 `0x5C150` | | 图档限制信息表指针 | `0x5A4A58`(坐标加载用,行宽 36 字节) | | 坐标文件句柄表 | `0x5A2988`(每文件一个 FILE\*) | | 游戏内 fseek/fread/malloc | `0x13BC41 / 0x13BCCD / 0x13C3AC` | | 切图转场 12 callsite | `0x03CD72 0x03CD9D 0x03D410 0x05EEFD 0x11C65A 0x11C9BE 0x11C9EF 0x11CA95 0x11CC69 0x11D16E 0x11D1D3 0x11D40D` | | cgmsv.dll 上限 | `0x191678` | | 核心库镜像内上限副本(F9) | 核心库运行镜像 `+0x1B77E0`(初值 250万),定位锚为紧跟其后的 `A0 CF 03 01`(指向 exe 全局 `0x103CFA0`) | | 核心库报错例程(F10) | 核心库运行镜像 `+0x8FDCA`(`56 50 68 4C 80 14 10 ...`),报错体从入口 `+0x19` 起 27 字节 | *** ## 更新记录 ### 2026-10-17 — 新增 F9(方案 A)/ F10(方案 B),修复核心库缓存副本导致的弹窗 - **现象**:F8 已把 exe 全局上限与核心库检查指令都改成 900万,活体验证均正确,但游戏仍弹 `MAX_GRAPHICS=2500000,Need=2712060`,点确定即退出。 - **定位**(活体内存取证):弹窗体为核心库运行镜像 `+0x8FDCA`(`push esi; push eax; sprintf(buf,fmt,esi,eax) → MessageBoxA → exit`), 全内存搜索**无任何 call/jmp 指向它**(调用者在 Themida 保护代码内);全内存 `2500000` 字节仅命中 核心库镜像 `+0x1B77E0`(其后 8 字节紧跟指向 exe 全局的指针 `0x103CFA0`)→ 判定为核心库自持的 **缓存上限副本**,启动早期从全局读一次后不再更新。 - **F9(方案 A,默认开)**:巡逻全内存定位该副本(锚 `A0 CF 03 01`,前 8 字节须为 250万),改写为 `kMaxGraphics` 并常驻巡检防回写;日志标记 `corecopy patched/VERIFY-FAILED @地址`。 - **F10(方案 B,默认关,兜底)**:以 `56 50 68 4C 80 14 10` 签名定位报错例程,把入口 `+0x19` 起 27 字节(`sprintf 参数 push ×3 → call MessageBoxA → push 0 → call exit`)整段 NOP,函数落入自身尾声正常返回, **不弹窗也不退出**;带原字节版本校验,不符则安全跳过。 - 至 2026-10-17 现场实测:F9 生效后弹窗消失(用户确认「貌似可以了」);F10 尚未启用。 ### 2026-10-15 — 移除 dsound 代理注入方案 + F8 图档上限修复 - **删除 dsound 代理注入整套**(`dsound_proxy.c/.def`、`build_proxy.bat`、`dsound.dll`):方案已弃用,统一走 PE 导入表注入。 - **F8 修复**(弹窗报 `MAX_GRAPHICS=2500000,Need=...` 的根因排查): - 恢复 `RsBltNote` 日志(此前被清空为空函数,worker 补丁结果全盲写)→ 写 `\binset.log` - worker 从"60 秒后退出"改为**常驻巡检**(前 60s 每 50ms,之后每 500ms):补丁被还原自动补回、全局上限被改回即强制写回 - `RsPatchCg2dGlobalLimit` 去掉 postInit 门控,每轮强制对齐上限值 - 新增 `RsGraphicLimitIsApplied()` 只读探针,9 点状态变化时记 `applied/REVERTED` 日志 ### 2026-09-21 — 修复「升级/进战斗野跳崩溃」(eip = 0xC0D0A2B0) - **真凶**:`ApplyNpcTextCrashGuard()` 防护桩的两个回主流跳转 RVA 少了 `0x40000` (写成 `0x3C0B/0x3CB1` → 落进 P2 字符转换函数中间),导致执行垃圾代码后确定性野跳。 已修正为 `0x43C0B/0x43CB1`。 - **构建脚本加固**:`_build.ps1` 原为 UTF-8 无 BOM,PowerShell 5.1 按 GBK 读导致中文路径乱码、 `xcopy` 静默失败 → 一直在编译旧源码。现已加 BOM + 源码 MD5 校验 + DLL 内容校验 + 部署回读校验。 - **纠正误判**:lookup 返回的 `-88 (0xFFFFFFA8)` 是表 `0x554204` 里的正常负数偏移量(不是错误码), 已恢复原样返回;lookup 上界 `0x18A87 = 100999` 本就与 40 项表精确贴合,**未做修改**。 - **新增 P5 野跳安全网**:VEH 检出"取指违例 + EIP 不可执行"时归还最近合法返回帧(限 5 次,写日志)。 - **crash.txt 增强**:新增 `eip_exec=` / `ebp_obj_id=` / `ebp_obj_fn=` / `near_ret:` 字段。 - **补录功能**:dsound 代理注入整套(`dsound_proxy.c/.def`、`build_proxy.bat`、`dsound.dll`)此前未入库,本次一并纳入。 详见 `修复记录_2026-09-21_升级野跳崩溃.md`。 ## 交接说明(换电脑/继续开发前必读) 1. **本目录只保留**:源码、def、bat、dll 成品、binset.txt、README(游戏本体、图档 bin、lua 等都不在此仓库)。 2. **续作流程**:新电脑装 VS → 打开目录 → 改 `cg2d_features.cpp` → 跑 `build_dll.bat` → 把新 `cnmlb.dll` 拷到 cg2d.exe 同目录并保持 PE 导入表里有它。 3. **改功能前先备份客户端**:所有 RVA 只适用于 0.55 版 cg2d.exe(MD5 见上)。换版本先核对 RVA 与字节,补丁都带 expected 校验,不匹配会安全跳过(宁可无效不要写坏)。 4. **改中文文案**:源码必须保持 UTF-8,编译必须 `/utf-8`。 5. **持久日志 / 转储**(写 exe 同目录,共 **4 个**;每次 DLL 启动**全部覆盖清空**,不跨会话累加): - `binset.log`:图档上限补丁巡检与状态变化(`graphlimit` / `corelimit` / `corecopy` / `popupblock`)。 - `cnmlb_loaded.log`:加载、调速与野跳安全网记录。`SpeedTimer` 写后读回发现引擎重置速度字节时记 `[SPEED]` 行(3s 节流,格式 `[SPEED] t=… inBattle=… spd=… val=… actual=… (engine reset?)`)。 - `graphic_fail.log`:图档查找失败(id 返回 -88)的 hook 路径,同一 id 只记一次。 - `crash.txt`:VEH 崩溃现场(真实 EIP/寄存器/回溯);同一会话内后一次崩溃覆盖前一次。 实现:`TruncateStartupLogs()` 在 DllMain PROCESS_ATTACH 早期对上述 4 个文件执行 `CreateFileA(... CREATE_ALWAYS ...)`,清空并保证文件存在;本会话内后续以追加模式(`FILE_APPEND_DATA` / `OPEN_ALWAYS`)写入。无 `.dmp` 转储文件,P4 只写文本。 6. **参考实现**:弹窗对齐、SpriteProbe、图档扩容思路源自一套 .NET 登录器(本地参考源码目录,未上传此仓库)。仓库内的图档监视器与 binset 偏移修正是本项目自研、可直接继续扩展。 7. **图号获取**:binset 规则里的图号可通过 `/bincheck` 打开「图档监视器」观察(pub 列),或对目标对象触发后从监视器筛选范围收集。