返回首页
MOD TECH DOC / 3Dmigoto / A0.3Dmigoto/A0-1.运行模型:代理 DLL、Hook 与 HackerDevice.md

运行模型:代理 DLL、Hook 与 HackerDevice

3Dmigoto 不是传统意义上的“资源包加载器”。它的核心工作方式是把自己放进游戏的 DirectX 调用链里,让游戏仍然以为自己在调用系统的 `d3d11.dll` / `dxgi.dll`,但关键对象已经被 3Dmigoto 包装过。换句话说,MOD 能生效不是因为游戏主动支持 MOD,而是因为 3Dmigoto 截获了渲染 API 的对象创建、资源绑定、着色器绑定和 draw call。

运行模型:代理 DLL、Hook 与 HackerDevice

3Dmigoto 不是传统意义上的“资源包加载器”。它的核心工作方式是把自己放进游戏的 DirectX 调用链里,让游戏仍然以为自己在调用系统的 d3d11.dll / dxgi.dll,但关键对象已经被 3Dmigoto 包装过。换句话说,MOD 能生效不是因为游戏主动支持 MOD,而是因为 3Dmigoto 截获了渲染 API 的对象创建、资源绑定、着色器绑定和 draw call。

这也是为什么基于 XXMI / GIMI / WWMI / EFMI / APMI 的角色 MOD,本质上都要回答同一个底层问题:某个游戏角色在 GPU 管线中被拆成哪些 DrawIndexed、绑定了哪些 VB/IB/SRV/CB/UAV、当前 VS/PS/CS hash 是什么,以及我们能在哪个时机把原资源换成自定义资源。

DLL 入口不是终点,而是接管入口

在 D3D11 路径中,DirectX11/D3D11Wrapper.cpp 定义了全局 Globals StaticGGlobals *G,并在 InitializeDLL() 中完成配置读取、目标进程检查、NVAPI 初始化、日志初始化等步骤。这里有一个容易被忽略的点:InitializeDLL() 并不等于“MOD 已经生效”,它只是让 3Dmigoto 具备接管后续 D3D11 对象的条件。

关键逻辑包括:

层级本地源码位置作用
DLL 包装层DirectX11/D3D11Wrapper.cpp伪装成 DirectX DLL,加载配置,转发真实系统 DLL 调用
设备包装层DirectX11/HookedDevice.cpphook ID3D11Device1 vtable,把游戏创建资源/着色器的调用导向 3Dmigoto
上下文包装层DirectX11/HookedContext.cpphook ID3D11DeviceContext1 vtable,把 draw、bind、clear、dispatch 等上下文调用导向 3Dmigoto
高层设备逻辑DirectX11/HackerDevice.*创建/替换资源、创建 shader、管理 swap chain 与资源池
高层上下文逻辑DirectX11/HackerContext.*在 draw 前后运行 override、记录当前管线状态、执行命令列表

HookedDevice.cppHookedContext.cpp 里都能看到同一种设计:文件开头明确说自己只负责 hook 和转发,真正功能交给 HackerDevice / HackerContext。这说明 3Dmigoto 的架构有两层:底层负责“把调用截住”,上层负责“解释 ini 并改变渲染状态”。

vtable hook:为什么游戏没有感知到 MOD 逻辑

D3D11 的对象是 COM 接口。游戏拿到的是 ID3D11DeviceID3D11DeviceContextIDXGISwapChain 这样的接口指针。3Dmigoto 的做法是替换或者包装这些接口的 vtable,使游戏调用:

context->DrawIndexed(...)
context->PSSetShaderResources(...)
device->CreateTexture2D(...)

时,实际先进入 3Dmigoto 的包装函数。包装函数可以选择:

  1. 直接转发给原始 D3D11 对象。
  2. 在转发前记录当前状态。
  3. 在转发前替换资源或 shader。
  4. 跳过原 draw,改为执行自定义 draw。
  5. 在转发后恢复状态或执行 post command。

这就是“无源码 MOD”的根基。游戏没有开放角色换模接口,但 GPU 渲染调用必须经过 D3D11;3Dmigoto 在这个边界层工作。

HackerContext 是 draw call 的闸门

角色 MOD 最关心的是 draw call。DirectX11/HackerContext.cpp 中可以看到多个 draw 包装函数,例如:

HackerContext::DrawIndexed(...)
HackerContext::Draw(...)
HackerContext::DrawIndexedInstanced(...)
HackerContext::Dispatch(...)

这些函数的基本结构是:

DrawContext c = DrawContext(...);
BeforeDraw(c);

if (!c.call_info.skip)
    mOrigContext1->DrawIndexed(...);

AfterDraw(c);

这段结构非常重要。它解释了 3Dmigoto 的 MOD 不是“替换一个文件就结束”,而是围绕一次 draw call 建立了前后两个执行窗口:

时机可以做什么
BeforeDraw查找当前 shader hash、运行 [ShaderOverride] pre command、执行 handling = skip、绑定自定义 VB/IB/Texture
原始 draw如果没有被 skip,则游戏原 draw 正常执行
AfterDraw运行 post command,恢复被临时修改的 shader / stereo / resource 状态

很多“模型消失”“F6 关闭后异常”“只隐藏不重绘”等问题,都是因为这个三段结构没有被正确处理:pre 阶段 skip 了原 draw,但没有在同一状态下补 draw;或者 post 阶段没有把被替换的资源恢复;或者开关变量只控制了部分 command list。

ShaderOverride 为什么能驱动角色 MOD

BeforeDraw() 会查找当前绑定的 VS/HS/DS/GS/PS hash,并在 G->mShaderOverrideMap 中寻找匹配项。命中后会调用:

ProcessShaderOverride(&i->second, isPixelShader, &data);

ProcessShaderOverride() 的核心是:

RunCommandList(mHackerDevice, this, &shaderOverride->command_list, &data->call_info, false);

所以 [ShaderOverrideXXX] 的意义不是“替换 shader”这么简单。它实际上是“当某个 shader hash 出现在当前 draw 管线中时,运行一段命令列表”。这段命令列表可以检查纹理、替换资源、跳过 draw、执行自定义 shader、重新 draw。角色 MOD 通常把 VS hash 当作角色部件的入口,是因为角色的 VS 更稳定地描述了该 mesh 的几何处理路径。

Device hook 负责资源生命周期

如果只截 draw,不截资源创建,就无法知道纹理 hash、VB/IB hash,也无法安全创建自定义资源。HookedDevice.cpp 包装了 CreateBufferCreateTexture1D/2D/3DCreateShaderResourceView 等函数。3Dmigoto 借此在资源创建时记录 descriptor、初始数据 hash、资源句柄关系。

这解释了一个常见现象:同样是 TextureOverride hash = xxxxxxxx,有时能匹配,有时不能匹配。因为 hash 不只是文件名,它与资源创建时的描述、初始数据、后续污染追踪都有关。若游戏在运行中更新纹理、复制资源、动态生成 RenderTarget,3Dmigoto 需要额外追踪才能保持 hash 可解释。

从蓝原/鸣潮/终末地工作流看底层共性

我们当前围绕蓝原构建 APMI,本质上是在给一个 Unity 游戏建立 3Dmigoto 语义层:

高层工具概念3Dmigoto 底层概念
Component一组能被识别的 draw / IA 状态 / IB 范围
FrameAnalysisFrameAnalysisContext 对 draw、shader、resource 的 dump
导出 MOD生成 [Resource][TextureOverride][ShaderOverride][CommandList]
热重载重新读取 ini,刷新 command list 与 override map
F6 开关全局变量 / key binding / condition 控制命令列表路径
模型消失原 draw 被 skip,但替代 draw 没有正确执行或资源绑定不完整

如果要真正理解 MOD 为什么有效,不能只看生成的 .ini。必须把 .ini 看成 3Dmigoto 在 BeforeDraw -> Draw -> AfterDraw 中执行的一套脚本。

参考

  • 3Dmigoto 开源仓库:https://github.com/bo3b/3Dmigoto
  • 3Dmigoto 官方 Wiki:https://github.com/bo3b/3Dmigoto/wiki
  • 本地源码:F:\Code\3Dmigoto-master\DirectX11\D3D11Wrapper.cpp
  • 本地源码:F:\Code\3Dmigoto-master\DirectX11\HookedDevice.cpp
  • 本地源码:F:\Code\3Dmigoto-master\DirectX11\HookedContext.cpp
  • 本地源码:F:\Code\3Dmigoto-master\DirectX11\HackerContext.cpp