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);
}
实际效果与源码
托管仓库为私密,不方便公开,需要源码工程的,可留言