Detorus学习7 - DX9篇
前言
上篇文章介绍了Dll的注入,本篇就直接以上篇实现的``DetourInjector来注入下面的主角
DetourD3D9.dll`。
测试效果在x86
、x64
有通过,工具集Windows 10 企业版 LTSC 1809 + VS2019 16.8.2
,依赖库版本信息:
detours:x64-windows 4.0.1-1 Detours is a software package for monitoring and... detours:x86-windows 4.0.1-1 Detours is a software package for monitoring and... directxsdk:x64-windows jun10 DirectX SDK directxsdk:x86-windows jun10 DirectX SDK imgui:x64-windows 1.79#3 Bloat-free Immediate Mode Graphical User interfa... imgui:x86-windows 1.79#3 Bloat-free Immediate Mode Graphical User interfa... imgui[dx9-binding]:x64-windows Make available DirectX9 binding imgui[dx9-binding]:x86-windows Make available DirectX9 binding imgui[win32-binding]:x64-windows Make available Win32 binding imgui[win32-binding]:x86-windows Make available Win32 binding
准备工作
在实现DetourD3D9.dll
之前,有些DirectX9
的必备知识简单的过一下,DirectX9
是微软的一个基于组件对象模型封装的OOP
的底层多媒体API
,这里主要是针对绘图部分进行说明,要使用它进行绘图,主要分为下面几个步骤:
- 获取
IDirect3D9
接口,该接口代表了3D设备,利用它可以获取系统中图形设备的信息和创建接口IDirect3DDevice9
- 初始化
D3DPRESENT_PARAMENTERS
结构,该结构是创建IDirect3DDevice9
的必备参数 - 得到
IDirect3DDevice9
,并用该接口做具体的绘制或通过它创建的对象进行绘制 - 通过调用
IDirect3DDevice9::EndScense
完成绘制,并将绘制数据提交到3D设备,最终渲染到屏幕
这个项目工程依赖了DirectX SDK
、ImGui
、ImGui[win32-binding]
、ImGui[dx9-binding]
:
vcpkg install directxsdk:x86-windows directxsdk:x64-windows vcpkg install imgui:x86-windows imgui:x64-windows vcpkg install imgui[win32-binding]:x86-windows imgui[win32-binding]:x64-windows --recurse vcpkg install imgui[dx9-binding]:x86-windows imgui[dx9-binding]:x64-windows --recurse
项目工程介绍
-
目录结构
DetourD3D9.dll ├─framework.h #头文件的引入 ├─wnd.h\wnd.cpp #关于窗口部分的代码 ├─draw.h\draw.cpp #关于绘制部分的代码 ├─dllmain.h\dllmain.cpp -
使用
MS Detours
来作用拦截工具库,Dear IMGUI
作用UI
绘制工具库,以下是库的引入情况:#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 // Windows 头文件 #include <windows.h> #include <stdio.h> #include <tchar.h> #include <detours/detours.h> #include <directxsdk/d3d9.h> #include <directxsdk/d3dx9.h> #include <imgui.h> #include <imgui_impl_dx9.h> #include <imgui_impl_win32.h>
DllMain的实现
说明:
Hook Dx9
和之前的Hook WindowsAPI
存在一些有差别的地方,因为DllMain有些限制:
- 在DllMain中不能调用
Loadlibrary
- 在DllMain中不能调用
CoInitialize
或CoInitializeEx
而
DirextX
刚好是基于COM
的,根据Detours Sample
中的commem
示例,它对进程的入口函数进行的拦截,我们尝试使用过此方法,由于对DirectX
以纯COM
方式来操作不算太熟悉,而且拦截入口后,hkEntryPoint
是在目标进程的窗口创建之前就执行的,这里用到上面提到的Direct3D9
绘制步骤中的D3DPRESENT_PARAMENTERS
拿不到窗口句柄则对初始化造成的麻烦,当然也是可以创建一个虚拟窗口来初始化D3DPRESENT_PARAMENTERS
,我们的目的是取到EndScense
的地址, 这里为了省事,直接获取了目标进程的主窗口的句柄,下面给出创建虚拟窗口的代码部分:
// 创建一个窗口用于初始化D3D WNDCLASSEX wc = {}; wc.cbSize = sizeof(wc); wc.style = CS_OWNDC; wc.hInstance = GetModuleHandle(NULL); wc.lpfnWndProc = DefWindowProc; wc.lpszClassName = _T("DummyWindow"); if (RegisterClassEx(&wc) == 0) { printf("注册窗口类失败,错误代码:%u\n", GetLastError()); return FALSE; } HWND hwnd = CreateWindowEx(0, wc.lpszClassName, _T(""), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, wc.hInstance, NULL); if (hwnd == NULL) { printf("创建窗口失败,错误代码:%u\n", GetLastError()); return FALSE; } // 初始化D3D IDirect3D9* pD3d = Direct3DCreate9(D3D_SDK_VERSION); if (pD3d == NULL) { printf("创建D3D失败,错误代码:%u\n", GetLastError()); DestroyWindow(hwnd); return FALSE; } D3DPRESENT_PARAMETERS dpp = {}; dpp.Windowed = TRUE; dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; IDirect3DDevice9* pDevice; if (FAILED(pD3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &dpp, &pDevice))) { printf("创建设备失败,错误代码:%u\n", GetLastError()); pD3d->Release(); DestroyWindow(hwnd); return FALSE; } // EndScene是IDirect3DDevice9第43个函数 endSceneAddr = (*(void ***)pDevice)[42]; // TODO Detour pD3d->Release(); pDevice->Release(); DestroyWindow(hwnd);
因为是使用MS Detorus
的CreateProcessWithDllEx
来注入代码,那么就需要导出DetourFinishHelperProcess
为导出序号1,Export.def
:
LIBRARY EXPORTS DetourFinishHelperProcess @1 NONAME
在DLL_PROCESS_ATTACH
中使用CreateThread
创建一个主线程,在线程中对EndScense
进行截获:
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { if (DetourIsHelperProcess()) { return TRUE; } switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: DetourRestoreAfterWith(); #ifdef _DEBUG AllocConsole(); FILE* stream; freopen_s(&stream, "CONIN$", "r", stdin); freopen_s(&stream, "CONOUT$", "w", stdout); #endif // _DEBUG //DllMain中不能调用LoadLibrary,因此对入口函数进程拦截 TrueEntryPoint = (int (WINAPI*)())DetourGetEntryPoint(NULL); RawEntryPoint = TrueEntryPoint; DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(LPVOID&)TrueEntryPoint, hkEntryPoint); DetourTransactionCommit(); DisableThreadLibraryCalls(hModule); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MainThread, hModule, 0, NULL); break; case DLL_PROCESS_DETACH: DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); if (TureEndScense != NULL) { DetourDetach(&(LPVOID&)TureEndScense, hkEndScense); } DetourDetach(&(LPVOID&)TrueEntryPoint, hkEntryPoint); DetourTransactionCommit(); // 释放ImGui ImGui_ImplWin32_Shutdown(); ImGui_ImplDX9_Shutdown(); ImGui::DestroyContext(); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return TRUE; }
在MainThread
中直接获取了当前进程的窗口句柄来对D3DPRESENT_PARAMENTERS
进行初始化来创建IDirect3DDevice9
,如果想使用虚拟窗口来进行创建可以查看本标签的说明部分,贴上MainThread
的代码:
DWORD WINAPI MainThread(LPVOID arg) { WaitForSingleObject(arg, INFINITE); IDirect3D9* pD3d = Direct3DCreate9(D3D_SDK_VERSION); if (pD3d == NULL) { printf("Direct3DCreate9失败,错误代码:%u\n", GetLastError()); return 0; } gHwnd = GetMainHWnd(GetCurrentProcessId()); if (gHwnd == NULL) { printf("获取主窗口句柄失败,错误代码:%u\n", GetLastError()); return FALSE; } #ifdef _WIN64 OriginProc = (WNDPROC)SetWindowLongPtr(RelationHwnd, GWLP_WNDPROC, (LONG_PTR)WndProc); #else OWndProc = (WNDPROC)SetWindowLongPtr(gHwnd, GWL_WNDPROC, (LONG_PTR)WndProc); #endif // _WIN64 D3DPRESENT_PARAMETERS dpp{ 0 }; dpp.hDeviceWindow = gHwnd; dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; dpp.Windowed = TRUE; IDirect3DDevice9* pDevice; HRESULT hr = pD3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, dpp.hDeviceWindow, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &dpp, &pDevice); if (FAILED(hr)) { printf("pD3d->CreateDevice失败,错误代码:%u\n", GetLastError()); pD3d->Release(); return FALSE; } //printf("BackBufferWidth = %u, BackBufferHeight = %u\n", dpp.BackBufferWidth, dpp.BackBufferHeight); gWndWidth = dpp.BackBufferWidth; gWndHeight = dpp.BackBufferHeight; void** pVtbl = *reinterpret_cast<void***>(pDevice); pDevice->Release(); // EndScene是IDirect3DDevice9第43个函数 TureEndScense = (TEndScense)pVtbl[42]; DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(LPVOID&)TureEndScense, hkEndScense); return DetourTransactionCommit(); }
对窗口信息的访问
这个部分没有太多说明的地方,主要是wnd.cpp
中的内容,里面提供了一个获取目标进程主窗口句柄的函数:
static BOOL CALLBACK cbEnumProc(HWND hwnd, LPARAM lparam) { DWORD pid; GetWindowThreadProcessId(hwnd, &pid); EPINFO* pwi = (EPINFO*)lparam; if (pid == pwi->dwPID) { HWND _hwnd = GetParent(hwnd); if (_hwnd != NULL) { pwi->hwnd = _hwnd; return FALSE; } } return TRUE; } // 获取主窗口句柄 HWND GetMainHWnd(DWORD pid) { EPINFO wi{ 0 }; wi.dwPID = pid; EnumWindows(cbEnumProc, (LPARAM)&wi); return wi.hwnd; }
自定义绘制
在调用原始的EndScense
之前,进行自己的绘制,捕获窗口消息WM_SIZE
来实时的得到窗口的宽高信息以方便ImGui
更理想的绘制:
#include "pch.h" #include "draw.h" #include "dllmain.h" WNDPROC OWndProc = NULL; static BOOL ImguiInitialized = FALSE; extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { case WM_SIZE: { gWndWidth = LOWORD(lparam); gWndHeight = HIWORD(lparam); printf("width = %d, height = %d\n", gWndWidth, gWndHeight); break; } default: break; } if (ImGui_ImplWin32_WndProcHandler(hwnd, msg, wparam, lparam)) { return TRUE; } return CallWindowProc(OWndProc, hwnd, msg, wparam, lparam); } HRESULT WINAPI hkEndScense(IDirect3DDevice9* pdevice) { //D3DRECT rect = { 25, 25, 100, 100 }; //pdevice->Clear(1, &rect, D3DCLEAR_TARGET, D3DCOLOR_ARGB(255, 255, 1, 0), 0.0f, 0); if (!ImguiInitialized) { ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.Fonts->GetGlyphRangesChineseSimplifiedCommon(); ImGui_ImplWin32_Init(gHwnd); ImguiInitialized = TRUE; } ImGui_ImplDX9_Init(pdevice); ImGui_ImplDX9_NewFrame(); ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); //TODO ImGuiWindowFlags windowFlags = 0; windowFlags |= ImGuiWindowFlags_NoTitleBar; windowFlags |= ImGuiWindowFlags_NoScrollbar; windowFlags |= ImGuiWindowFlags_NoMove; windowFlags |= ImGuiWindowFlags_NoResize; windowFlags |= ImGuiWindowFlags_NoCollapse; windowFlags |= ImGuiWindowFlags_NoNav; windowFlags |= ImGuiWindowFlags_NoBackground; windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus; if (!ImGui::Begin("MyIMGUI", NULL, windowFlags)) { ImGui::End(); } ImGui::Button("Button", ImVec2(40, 40)); ImGui::Text("Hello World."); ImVec2 wndSize = ImGui::GetWindowSize(); // 将窗口固定在右下角距边50 ImGui::SetWindowPos("MyIMGUI", ImVec2(gWndWidth - wndSize.x - 50, gWndHeight - wndSize.y - 50)); ImGui::End(); ImGui::ShowDemoWindow(); // ends the Dear ImGui frame. automatically called by Render(). //ImGui::EndFrame(); ImGui::Render(); ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData()); ImGui_ImplDX9_Shutdown(); return TureEndScense(pdevice); }
实际效果与源码
托管仓库为私密,不方便公开,需要源码工程的,可留言
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗