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 @ 2020-12-25 14:04  非法关键字  阅读(421)  评论(0编辑  收藏  举报