PTTRDLL: DX11 覆盖层 + IL2CPP 挂钩的练手 DLL
PTTRDLL 是我自己写的一个覆盖层 DLL,目标是一款叫 PTTR 的 Unity IL2CPP 游戏。做它没什么宏大理由,就是我一直想把两件我各自熟一点的事串起来做一遍:图形层的 Hook,和 IL2CPP 的逆向。刚好手头有个真游戏能当练手场,那就注入进去,在画面上叠一层 ImGui 菜单,顺手把玩家状态读出来改一改——血量、增益、升级、坐标。
先说个容易看岔的地方:GitHub 把这仓库的主语言标成了 C#,那是因为 Data/ 里塞了一份巨大的 IL2CPP dump.cs,体积把统计带偏了。真正能编译出来的东西是 C++。
从画面这一层切进去
想在游戏画面上画东西,最干净的路子是接管它的呈现流程。我用 MinHook 挂了 IDXGISwapChain::Present(vtable 第 8 个)和 ResizeBuffers(第 13 个),再加一个 D3D11CreateDeviceAndSwapChain。
真正卡人的是:注入那会儿游戏的 swap chain 还没建出来,拿不到它的 vtable。我的做法是先自己造一个临时的 dummy swap chain,从这个假货身上把 vtable 里的函数地址解析出来,再去挂真的那份。这一步想明白,整个 DX Hook 就通了,后面都是顺水推舟。
覆盖层用的是 Dear ImGui 的 Win32 + DX11 后端,画了个暗色调的 ClickGUI。窗口 resize 的时候一定记得重建 RTV,不然画面直接崩。开关绑在 INSERT 键,通过 Hook WndProc 切显隐。ImGui 和 MinHook 都是 vendored 进来跟项目一起编的,不接外部包管理,少一层心智负担。
再切进游戏逻辑
画面搞定,下一层是读写玩家状态,得跟 IL2CPP 正面打交道。我按固定 RVA 挂了 GameAssembly.dll 里 PTTRPlayer 的几个方法:Update、Awake、OnEnable、OnDestroy、Die、set_health。靠 Awake / OnEnable 抓到本地玩家实例,Update 里做常驻读写,Die 和 set_health 用来卡住那些我不想让它发生的状态变化。
坐标要多绕一步:先 il2cpp_resolve_icall 解出 get_transform 和 get_position_Injected,才能把 XYZ 读出来丢到菜单上显示。
所谓无敌模式,内核就是锁血,再顺手套上可调的血量 / 增益 / 升级数值,菜单里实时回显当前读数。config.h 留了一组编译期默认(god mode 默认开、血 10000、boost 9999、upgrade 99),运行时在覆盖层里拖 slider 也能改。
踩过的坑
最贵的一课是野指针。往一个已经失效的游戏对象里读写,host 进程当场就没。所以所有对游戏内存的读写我都用 __try/__except 包一层,踩到坏指针记条日志就算了,别拖着游戏陪葬。再配一个只追加的文件日志,注入那一瞬的初始化、渲染各阶段都看得清清楚楚——调这种东西没日志基本等于闭眼开车。
另一个天生的脆点:那些 RVA_* 偏移是从某个特定游戏版本的 dump 里抠出来的,游戏一更新就全废,得重新对。这也是为什么它注定是个私人玩具,而不是什么通用工具。
就这样
个人项目,一直 WIP,代码 MIT 扔着。留它主要是给自己当份参考:DX11 Hook 和 IL2CPP 逆向的套路,过阵子忘了就翻回来看。它谈不上完整,但“从画面一路切到游戏逻辑”那种一层层往里钻的手感,我是真喜欢。