C/C++ImGUI劫持Dx9绘制窗体

ImGUI 是一个无任何第三方依赖的图形化界面组件,其支持多种绘图引擎,ImGUI可用于绘制辅助菜单功能,注入游戏内部方便快捷。

ImGUI下载:https://github.com/ocornut/imgui/releases/tag/v1.60

下载好以后用户需自行配置ImGUI到项目中,并配置D3Dx9开发工具包,此处的IMGUI需要取出imgui-1.60\examples\directx9_example里面的imgui_impl_dx9并放入根目录。

并将其导入到项目目录下,添加现有项,导入所有的包。

注入测试代码如下:

#include <Windows.h>
#include <d3dx9.h>
#include "imgui.h"
#include "imgui_impl_dx9.h"
#include "imgui_internal.h"
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma pack(push)
#pragma pack(1)
#ifndef _WIN64

// 配置使用UTF8
#pragma execution_character_set("utf-8")

struct JmpCode
{
private:
const BYTE jmp;
DWORD address;

public:
JmpCode(DWORD srcAddr, DWORD dstAddr): jmp(0xE9)
{
setAddress(srcAddr, dstAddr);
}

void setAddress(DWORD srcAddr, DWORD dstAddr)
{
address = dstAddr - srcAddr - sizeof(JmpCode);
}
};
#else
struct JmpCode
{
private:
BYTE jmp[6];
uintptr_t address;

public:
JmpCode(uintptr_t srcAddr, uintptr_t dstAddr)
{
static const BYTE JMP[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
memcpy(jmp, JMP, sizeof(jmp));
setAddress(srcAddr, dstAddr);
}

void setAddress(uintptr_t srcAddr, uintptr_t dstAddr)
{
address = dstAddr;
}
};
#endif
#pragma pack(pop)

// 修改函数前面5个字节实现跳转
void hook(void* originalFunction, void* hookFunction, BYTE* oldCode)
{
JmpCode code((uintptr_t)originalFunction, (uintptr_t)hookFunction);
DWORD oldProtect, oldProtect2;
VirtualProtect(originalFunction, sizeof(code), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(oldCode, originalFunction, sizeof(code));
memcpy(originalFunction, &code, sizeof(code));
VirtualProtect(originalFunction, sizeof(code), oldProtect, &oldProtect2);
}

void unhook(void* originalFunction, BYTE* oldCode)
{
DWORD oldProtect, oldProtect2;
VirtualProtect(originalFunction, sizeof(JmpCode), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(originalFunction, oldCode, sizeof(JmpCode));
VirtualProtect(originalFunction, sizeof(JmpCode), oldProtect, &oldProtect2);
}

void* endSceneAddr = NULL;
BYTE endSceneOldCode[sizeof(JmpCode)];

// 窗口句柄
WNDPROC oWndProc = nullptr;
HWND Window = nullptr;

// 是否第一次初始化
BOOL is_init = FALSE;

// 单选框设置状态
bool show_another_window = false;

// imgui 窗体消息用的
extern LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Imgui窗体消息循环
LRESULT CALLBACK hkWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_KEYDOWN:
switch (wParam)
{
// 按下F9弹窗
case VK_F9:
MessageBox(0, 0, 0, 0);
break;
}
break;
}
//消息 操作imgui用的
if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam)) return 1;

return CallWindowProc(oWndProc, hWnd, uMsg, wParam, lParam);
}

HRESULT STDMETHODCALLTYPE MyEndScene(IDirect3DDevice9* thiz)
{
// 是否第一次执行
if (is_init == FALSE)
{
// 初始化ImGUI字体
is_init = TRUE;
ImGuiIO& io = ImGui::GetIO();
io.DeltaTime = 1.0f / 60.0f;
D3DDEVICE_CREATION_PARAMETERS d3dcp{ 0 };
thiz->GetCreationParameters(&d3dcp);
ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\simkai.ttf", 14.0f, NULL, io.Fonts->GetGlyphRangesChinese());
ImGui_ImplDX9_Init(d3dcp.hFocusWindow, thiz);
ImGui::StyleColorsDark();

// 挂钩EndScene函数
unhook(endSceneAddr, endSceneOldCode);
HRESULT hr = thiz->EndScene();
hook(endSceneAddr, MyEndScene, endSceneOldCode);
return hr;

}

// -------------------------------------------------------------
// Imgui绘制过程

// 定义静态变量
static float f = 0.0f;
static int counter = 0;
static char sz[256] = { 0 };

ImGui_ImplDX9_NewFrame();
ImGui::Begin("LyShark.com 辅助GUI主菜单");

ImGui::Checkbox("弹出子窗口", &show_another_window);
ImGui::SliderFloat("浮点条", &f, 0.0f, 1.0f);

ImGui::InputText("输入内容", sz, 256, 0, 0, 0);

if (ImGui::Button("点我触发"))
{
counter++;
}

ImGui::SameLine();
ImGui::Text("触发次数 = %d", counter);

ImGui::Text("当前FPS: %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();

// 子窗体菜单
if (show_another_window)
{
ImGui::Begin("我是子窗体", &show_another_window);
ImGui::Text(" 您好,LyShark, 这是一个测试窗体 !");

// 按钮状态
if (ImGui::Button("关闭窗体"))
{
show_another_window = false;
}

ImGui::End();
}
ImGui::EndFrame();

// 把imgui东西刷新上去
ImGui::Render();
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());

// 结束绘制,取消hook
unhook(endSceneAddr, endSceneOldCode);
HRESULT hr = thiz->EndScene();
hook(endSceneAddr, MyEndScene, endSceneOldCode);
return hr;
}

// 该函数作用是得到endSceneAddr地址
DWORD WINAPI initHookThread(LPVOID dllMainThread)
{

WaitForSingleObject(dllMainThread, INFINITE);
CloseHandle(dllMainThread);

WNDCLASSEX wc = {};
wc.cbSize = sizeof(wc);
wc.style = CS_OWNDC;
wc.hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = DefWindowProc;
wc.lpszClassName = L"Window";

if (RegisterClassEx(&wc) == 0)
{
return 0;
}

HWND hwnd = CreateWindowEx(0, wc.lpszClassName, L"", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, NULL, NULL, wc.hInstance, NULL);
if (hwnd == NULL)
{
return 0;
}

// 初始化D3D
IDirect3D9* d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
if (d3d9 == NULL)
{
DestroyWindow(hwnd);
return 0;
}

D3DPRESENT_PARAMETERS pp = {};
pp.Windowed = TRUE;
pp.SwapEffect = D3DSWAPEFFECT_COPY;

IDirect3DDevice9* device;
if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &pp, &device)))
{
d3d9->Release();
DestroyWindow(hwnd);
return 0;
}

// hook EndScene
// EndScene是IDirect3DDevice9第43个函数
endSceneAddr = (*(void***)device)[42];
hook(endSceneAddr, MyEndScene, endSceneOldCode);

// 释放
d3d9->Release();
device->Release();
DestroyWindow(hwnd);
return 0;
}

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
DWORD dwCurProcessId = *((DWORD*)lParam);
DWORD dwProcessId = 0;

GetWindowThreadProcessId(hwnd, &dwProcessId);
if (dwProcessId == dwCurProcessId && GetParent(hwnd) == NULL)
{
*((HWND *)lParam) = hwnd;
return FALSE;
}
return TRUE;
}

HWND GetMainWindow()
{
DWORD dwCurrentProcessId = GetCurrentProcessId();
if (!EnumWindows(EnumWindowsProc, (LPARAM)&dwCurrentProcessId))
{
return (HWND)dwCurrentProcessId;
}
return NULL;
}

// dll 入口
BOOL APIENTRY DllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{

switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:

// 获取自身线程句柄
HANDLE curThread;
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &curThread, SYNCHRONIZE, FALSE, 0))
return FALSE;

// 在另一个线程初始化COM组件
CloseHandle(CreateThread(NULL, 0, initHookThread, curThread, 0, NULL));

// 获取自身进程窗口句柄
Window = GetMainWindow();

// 设置窗口循环消息
oWndProc = (WNDPROC)SetWindowLongA(Window, GWL_WNDPROC, (LONG)hkWndProc);
if (oWndProc == nullptr)
return 1;

// 创建IMGUI上下文
ImGui::CreateContext();
break;

case DLL_PROCESS_DETACH:
if (endSceneAddr != NULL)
unhook(endSceneAddr, endSceneOldCode);
break;

break;

case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

打开一个带有Dx9引擎的图形化界面,并将生成的DLL注入到游戏内,即可弹出内部GUI菜单。