Hook raylib关键函数

DLL 劫持(Hook) EndDrawing 函数。

强行注入思路

如果你想这个搜索框注入到别人的 raylib 程序里:

  • 需要用到 CreateRemoteThread
  • 需要找到主程序调用 EndDrawing 的内存地址。
  • 需要把那个地址的指令跳转(JMP)到你的 DLL 函数地址。

raylib主程序

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#define RAYGUI_IMPLEMENTATION
#include "raylib.h"

#ifndef _DEBUG
#pragma comment(lib, "raylib.lib")
#pragma comment(lib, "winmm.lib")
// #pragma comment(lib, "opengl32.lib")
// #pragma comment(lib, "gdi32.lib")
// #pragma comment(lib, "shell32.lib")
// #pragma comment(lib, "user32.lib")
#endif // DEBIG

int main() {
// 纯净的 Raylib 程序,不包含任何插件加载代码
InitWindow(800, 600, "Target Process (To be Injected)");
SetTargetFPS(60);

while (!WindowShouldClose()) {
BeginDrawing();
ClearBackground(DARKBLUE); // 换个颜色,方便区分
DrawText("I am a standalone program.", 190, 200, 20, RAYWHITE);
DrawText("I have no searchbox code in my source.", 190, 230, 20, LIGHTGRAY);
EndDrawing();
}

CloseWindow();
return 0;
}

dll

dllmain.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#define RAYGUI_IMPLEMENTATION
#include <raylib.h>

// 2. 隔离 Windows 污染
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#define VC_EXTRALEAN


// 在包含 windows.h 之前,把冲突的宏定义先关掉或改名
#define NOGDI // 减少无用定义
#define NOUSER // 减少无用定义
#define WIN32_LEAN_AND_MEAN
// 如果还是冲突,强行取消 Windows 定义的同名宏
// 3. 关键:解除冲突
#undef ShowCursor
#undef CloseWindow
#undef Rectangle // Windows 也有 Rectangle 函数,以后你可能会遇到它

#include <windows.h>
#include <MinHook.h>

#ifndef _DEBUG
#pragma comment(lib, "raylib.lib")
#pragma comment(lib, "winmm.lib")
// #pragma comment(lib, "opengl32.lib")
// #pragma comment(lib, "gdi32.lib")
// #pragma comment(lib, "shell32.lib")
// #pragma comment(lib, "user32.lib")
#endif // DEBIG

extern void SearchOnTick();

// 1. 定义原始 EndDrawing 的指针,用于 Hook 后的还原调用
typedef void (*EndDrawingPtr)();
EndDrawingPtr pOriginalEndDrawing = nullptr;


// 2. 我们的拦截函数(这就是你原来的 SearchOnTick)
void DetourEndDrawing() {
// 调用你之前的搜索框逻辑
// 注意:在这里不需要 BeginDrawing/EndDrawing,因为我们就在 EndDrawing 内部
SearchOnTick();

// 最后必须执行原始的 EndDrawing,否则程序会卡死且不渲染
pOriginalEndDrawing();
}

// 3. 注入后的初始化线程
DWORD WINAPI InjectThread(LPVOID lpParam) {
if (MH_Initialize() != MH_OK) return 1;

// 关键:寻找目标程序的 EndDrawing 地址
// 假设目标是动态链接 raylib.dll
HMODULE hRaylib = GetModuleHandleA("raylib.dll");
if (hRaylib) {
void* targetAddr = (void*)GetProcAddress(hRaylib, "EndDrawing");

// 创建钩子:拦截 targetAddr,跳转到 DetourEndDrawing
MH_CreateHook(targetAddr, &DetourEndDrawing, reinterpret_cast<LPVOID*>(&pOriginalEndDrawing));
MH_EnableHook(targetAddr);
}

return 0;
}


// 4. DLL 入口点:注入器一旦 LoadLibrary,这里就会自动触发

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hModule);
CreateThread(nullptr, 0, InjectThread, hModule, 0, nullptr);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

searchbox_dll.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include "IPlugin.h"
#include <vector>

// 插件内部私有变量(被封存在 DLL 内存空间中)
static char inputText[64] = { 0 };
static int letterCount = 0;
static bool active = false;

// --- 接口实现函数 ---

void SearchOnInit() {
// 可以在这里初始化资源,比如加载搜索框专用的字体或音效
TraceLog(LOG_INFO, "[SearchBox DLL] Plugin Initialized.");
}

void SearchOnTick() {
if (IsKeyPressed(KEY_F2)) active = !active;
if (!active) return;

// --- 原有的输入处理逻辑 ---
int key = GetCharPressed();
while (key > 0) {
if (key >= 32 && key <= 125 && letterCount < 63) {
inputText[letterCount++] = (char)key;
inputText[letterCount] = '\0';
}
key = GetCharPressed();
}
if (IsKeyPressed(KEY_BACKSPACE) && letterCount > 0) inputText[--letterCount] = '\0';

// --- 新增:回车事件处理 ---
if (IsKeyPressed(KEY_ENTER)) {
if (letterCount > 0) {
// 1. 打印输出(会显示在主程序的控制台里)
TraceLog(LOG_INFO, "[SearchBox] Searching for: %s", inputText);

// 2. 这里可以添加更多反应,比如:
// - 改变一个全局状态
// - 触发搜索逻辑
// - 甚至是调用主程序传进来的回调函数

// 3. 执行完后清空并关闭
letterCount = 0;
inputText[0] = '\0';
active = false;
}
}

// --- 绘图逻辑保持不变 ---
Rectangle box = { GetScreenWidth() / 2.0f - 200, 20, 400, 40 };
DrawRectangleRounded(box, 0.3f, 8, Fade(BLACK, 0.8f));
DrawRectangleRoundedLines(box, 0.3f, 8, active ? SKYBLUE : GRAY);
DrawText(inputText, (int)box.x + 15, (int)box.y + 10, 20, RAYWHITE);
}

void SearchOnUnload() {
// 清理资源
TraceLog(LOG_INFO, "[SearchBox DLL] Plugin Unloaded.");
}

// --- 唯一的导出函数:将上述函数打包交给宿主 ---

extern "C" __declspec(dllexport) PluginInterface* GetPluginAPI() {
static PluginInterface api = {
{"Global Search", "1.0"},
SearchOnInit,
SearchOnTick,
SearchOnUnload
};
return &api;
}

IPlugin.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef IPLUGIN_H
#define IPLUGIN_H

#include "raylib.h"

// 插件元数据
struct PluginInfo {
const char* name;
const char* version;
};
typedef struct {
PluginInfo info;
void (*OnInit)();
void (*OnTick)();
void (*OnUnload)();
} PluginInterface;

typedef PluginInterface* (*GetPluginAPI_Func)();

#endif

静态链接与动态链接的坑

动态链接成功,静态链接失败。

核心挑战:输入状态(Input State)

静态库版本还有一个巨大的坑:全局变量隔离

  • 动态库版:主程序和 DLL 都链接到同一个 raylib.dll,它们共享同一个键盘状态缓冲区。
  • 静态库版:主程序里有一套 Raylib 变量,你的 DLL 里也有一套。当你在主程序按 F2 时,主程序的那套变量变了,但 DLL 里的那套没变,所以 IsKeyPressed 永远返回 false

方案 A:劫持窗口过程 (Hook WndProc)

不要依赖 Raylib 的输入函数。直接拦截 Windows 的原始消息,这不依赖任何库的状态。

方案 B:特征码搜索变量而非函数

你需要搜索的不是 EndDrawing,而是 Raylib 内部存放 Core 状态的全局指针。强行把 DLL 里的指针指向 EXE 里的内存地址。但这需要极高的逆向工程功底。