Detorus学习7 - DX9篇

前言

上篇文章介绍了Dll的注入,本篇就直接以上篇实现的``DetourInjector来注入下面的主角DetourD3D9.dll`。

测试效果在x86x64有通过,工具集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,这里主要是针对绘图部分进行说明,要使用它进行绘图,主要分为下面几个步骤:

  1. 获取IDirect3D9接口,该接口代表了3D设备,利用它可以获取系统中图形设备的信息和创建接口IDirect3DDevice9
  2. 初始化D3DPRESENT_PARAMENTERS结构,该结构是创建IDirect3DDevice9的必备参数
  3. 得到IDirect3DDevice9,并用该接口做具体的绘制或通过它创建的对象进行绘制
  4. 通过调用IDirect3DDevice9::EndScense完成绘制,并将绘制数据提交到3D设备,最终渲染到屏幕

这个项目工程依赖了DirectX SDKImGuiImGui[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

项目工程介绍

  1. 目录结构

    DetourD3D9.dll
    ├─framework.h #头文件的引入
    ├─wnd.h\wnd.cpp #关于窗口部分的代码
    ├─draw.h\draw.cpp #关于绘制部分的代码
    ├─dllmain.h\dllmain.cpp
  2. 使用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有些限制:

  1. 在DllMain中不能调用Loadlibrary
  2. 在DllMain中不能调用CoInitializeCoInitializeEx

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 DetorusCreateProcessWithDllEx来注入代码,那么就需要导出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);
}

实际效果与源码

托管仓库为私密,不方便公开,需要源码工程的,可留言

posted @   非法关键字  阅读(530)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示