实现思路

  1. 修改主程序绘制一个非矩形的3d物体,让此物体做无规律移动。
  2. 修改外部绘制程序,绘制方框包裹3d物体跟随3d物体移动。

useray.exe

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
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
#include <raylib.h>
#include <raymath.h>
#include <iostream>

int main() {
SetConfigFlags(FLAG_WINDOW_UNDECORATED);
InitWindow(1280, 720, "FPS: Realistic Scale Demo");

// 1. 玩家相机
Camera3D camera = { 0 };
camera.position = Vector3{ 10.0f, 1.75f, 10.0f };
camera.target = Vector3{ 0.0f, 1.75f, 0.0f };
camera.up = Vector3{ 0.0f, 1.0f, 0.0f };
camera.fovy = 70.0f;
camera.projection = CAMERA_PERSPECTIVE;

// 2. 敌人状态
Vector3 enemyPos = { 0.0f, 0.0f, 0.0f };
Vector3 enemyVel = { 0.12f, 0.0f, 0.08f };
#include <stddef.h> // 引入 offsetof
// --- 关键:打印地址 ---
// 使用 std::hex 以十六进制格式输出,方便在 Cheat Engine 或 Search.exe 中使用
std::cout << "========================================" << std::endl;
std::cout << "ENEMY POSITION ADDRESS: 0x" << std::hex << (uintptr_t)&enemyPos << std::endl;
// 打印偏移量,方便 Search.exe 使用
std::cout << "[OFFSET] enemyPos.x: +0x" << offsetof(enemyPos, x) << std::endl;
std::cout << "[OFFSET] enemyPos.y: +0x" << offsetof(enemyPos, y) << std::endl;
std::cout << "[OFFSET] enemyPos.z: +0x" << offsetof(enemyPos, z) << std::endl;
// 获取 Raylib 内部矩阵的方法 (rlgl)
// 注意:你需要包含 #include "rlgl.h" 才能调用这些
// 这里我们先打印 enemyPos,因为矩阵通常在 rlgl 内部比较深的地方
// 打印矩阵地址(这就是你要的 CAMERA_ADDR)
std::cout << "CAMERA_ADDR: 0x" << std::hex << (uintptr_t)&camera << std::endl;


// --- 新增:暂停开关 ---
bool isPaused = false;

SetTargetFPS(60);
DisableCursor();

while (!WindowShouldClose()) {
// --- 逻辑:按空格键切换暂停/继续 ---
if (IsKeyPressed(KEY_SPACE)) isPaused = !isPaused;

UpdateCamera(&camera, CAMERA_FIRST_PERSON);

// --- 逻辑:只有在非暂停状态下才更新位置 ---
if (!isPaused) {
enemyPos = Vector3Add(enemyPos, enemyVel);

if (enemyPos.x > 30.0f || enemyPos.x < -30.0f) enemyVel.x *= -1;
if (enemyPos.z > 30.0f || enemyPos.z < -30.0f) enemyVel.z *= -1;

if (GetRandomValue(0, 100) < 2) {
float angle = GetRandomValue(0, 360) * DEG2RAD;
enemyVel.x = cos(angle) * 0.15f;
enemyVel.z = sin(angle) * 0.15f;
}
}

// --- 渲染 ---
BeginDrawing();
ClearBackground(BLACK);
BeginMode3D(camera);
DrawPlane(Vector3 { 0, 0, 0 }, Vector2 { 100, 100 }, DARKGRAY);
// 绘制敌人
DrawCapsule(enemyPos, Vector3Add(enemyPos, Vector3 { 0, 1.8f, 0 }), 0.3f, 8, 4, RED);
EndMode3D();

// 状态提示
DrawText(isPaused ? "STATUS: PAUSED (SPACE to Resume)" : "STATUS: RUNNING (SPACE to Pause)", 20, 20, 20, isPaused ? YELLOW : GREEN);
DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, 2, SKYBLUE);
EndDrawing();
}
CloseWindow();
return 0;
}

search.exe架构

Search.exe 拆解为四个核心角色:

  1. Context (Model): 存放每一帧读到的原始数据(敌人坐标、矩阵、PID等)。
  2. Logic (Controller): 负责读内存、算矩阵、执行坐标转换。
  3. View (UI): 负责画方框、文字、准星。
  4. Main (Entry): 负责生命周期调度。

search.exe

“透视(ESP)”功能的核心逻辑。要实现它,Search.exe 需要完成两个最具挑战性的任务:

  1. 读取数据:从主程序(UseRay.exe)的内存中读出敌人的 3D 坐标 ($x, y, z$)。
  2. 坐标转换:将 3D 坐标通过矩阵运算转换成你屏幕上的 2D 坐标 ($x, y$)。

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
29
30
31
32
33
#include <raylib.h>
#include "ESPContext.h"
#include "ESPLogic.h"
#include "ESPView.h"
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" ) // 设置入口地址
int main() {
// 1. 初始化 Overlay 窗口特性
SetConfigFlags(FLAG_WINDOW_UNDECORATED | FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_TOPMOST | FLAG_WINDOW_MOUSE_PASSTHROUGH);
InitWindow(1280, 720, "Raylib_ESP_MVC");
SetTargetFPS(60);

ESPContext ctx;
ESPLogic logic;
ESPView view;

while (!WindowShouldClose()) {
// --- 逻辑:读内存、计算坐标 ---
logic.Update(ctx);

// --- 渲染:把结果画出来 ---
BeginDrawing();
ClearBackground(BLANK);

view.Draw(ctx);

EndDrawing();
}

CloseWindow();
return 0;
}


ESPContext.h

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
#pragma once
#include <raylib.h>
#include <vector>

struct ESPContext {
// 进程信息
unsigned int targetPid = 0;
bool isGameRunning = false;

// 渲染参数
float screenWidth = 1280.0f;
float screenHeight = 720.0f;

// 每一帧抓取的数据
struct Entity {
Vector3 pos;
Vector2 screenFoot;
Vector2 screenHead;
float distance;
bool onScreen;
};

std::vector<Entity> entities; // 支持以后扩展多个敌人
Matrix viewProjMatrix;
};

ESPView.h

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
#pragma once
#include "ESPContext.h"

class ESPView {
public:
void Draw(const ESPContext& ctx) {
if (!ctx.isGameRunning) {
DrawText("WAITING FOR GAME...", 10, 10, 20, GRAY);
return;
}

for (const auto& entity : ctx.entities) {
if (!entity.onScreen) continue;

float height = entity.screenFoot.y - entity.screenHead.y;
float width = height / 2.0f;

// 绘制骨架方框
DrawRectangleLinesEx(
{ entity.screenHead.x - width / 2, entity.screenHead.y, width, height },
2.0f, RED
);

// 绘制距离标签
DrawText(TextFormat("[%.1fm]", entity.distance),
(int)entity.screenFoot.x - 20, (int)entity.screenFoot.y + 5,
15, YELLOW);
}

// 辅助:画准星
DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, 2, GREEN);
}
};

ESPLogic.h

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
89
90
91
92
93
94
95
96
#pragma once
#include "ESPContext.h"
#include "WindowUtils.h"
#include <raymath.h>

class ESPLogic {
public:
// 构造时绑定目标信息
ESPLogic() : _game("FPS: Realistic Scale Demo", "GLFW30") {}

void Update(ESPContext& ctx) {
// 1. 利用类成员进行进程有效性检查
if (!_game.IsTargetValid()) {
ctx.isGameRunning = false;
ctx.targetPid = 0;
return;
}

ctx.targetPid = _game.GetPid();

// 2. 获取最新的客户区信息(同步窗口大小与位置)
ClientAreaInfo info = _game.GetClientInfo();
ctx.isGameRunning = info.isVisible;
ctx.screenWidth = (float)info.width;
ctx.screenHeight = (float)info.height;

if (ctx.isGameRunning) {
// 同步 Overlay 窗口位置到游戏客户区
_game.SyncOverlayPosition(GetWindowHandle());

// 3. 执行核心数据刷新
RefreshGameData(ctx);
}
}

private:
ProcessManager _game; // 核心管理类作为成员

void RefreshGameData(ESPContext& ctx) {
// 填入你的硬编码地址
uintptr_t ENEMY_ADDR = 0x2d1296f1f8;
uintptr_t CAMERA_ADDR = 0x2d1296f1a8;

// 2. 使用封装好的模板函数读取内存
Vector3 enemyPos = _game.Read<Vector3>(ENEMY_ADDR);
RemoteCamera remoteCam = _game.Read<RemoteCamera>(CAMERA_ADDR);

// 3. 重构矩阵 (保持你调整好的 3D 转 2D 逻辑)
Camera cam = {
{ remoteCam.position.x, remoteCam.position.y, remoteCam.position.z },
{ remoteCam.target.x, remoteCam.target.y, remoteCam.target.z },
{ remoteCam.up.x, remoteCam.up.y, remoteCam.up.z },
remoteCam.fovy,
remoteCam.projection
};

Matrix view = GetCameraMatrix(cam);
float aspect = ctx.screenWidth / ctx.screenHeight;
Matrix proj = MatrixPerspective(cam.fovy * DEG2RAD, aspect, 0.01f, 1000.0f);
ctx.viewProjMatrix = MatrixMultiply(view, proj);

// 4. 坐标转换并存入 Context
ctx.entities.clear();

ESPContext::Entity e;
e.pos = enemyPos;

float w_foot, w_head;
// 使用你调试成功的 2.1f 高度
e.onScreen = WorldToScreen(e.pos, ctx.viewProjMatrix, ctx.screenWidth, ctx.screenHeight, e.screenFoot, w_foot);
bool headOnScreen = WorldToScreen(Vector3Add(e.pos, { 0, 2.1f, 0 }), ctx.viewProjMatrix, ctx.screenWidth, ctx.screenHeight, e.screenHead, w_head);

// 只有脚在屏幕内才添加(或者你可以根据需求调整逻辑)
if (e.onScreen) {
e.distance = w_foot;
ctx.entities.push_back(e);
}
}

// 内部数学工具函数
bool WorldToScreen(Vector3 pos, Matrix vp, float sw, float sh, Vector2& out, float& w_out) {
float x = pos.x * vp.m0 + pos.y * vp.m4 + pos.z * vp.m8 + vp.m12;
float y = pos.x * vp.m1 + pos.y * vp.m5 + pos.z * vp.m9 + vp.m13;
float w = pos.x * vp.m3 + pos.y * vp.m7 + pos.z * vp.m11 + vp.m15;

w_out = w;
if (w < 0.1f) return false;

float ndc_x = x / w;
float ndc_y = y / w;

out.x = (sw / 2.0f) * (1.0f + ndc_x);
out.y = (sh / 2.0f) * (1.0f - ndc_y);
return true;
}
};

WindowUtils.h

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
#pragma once
#include <stdint.h>
#include <string>

// 客户区信息包
struct ClientAreaInfo {
int x, y;
int width, height;
bool isVisible;
};

// 内存数据结构
struct MyVector3 { float x, y, z; };
struct RemoteCamera {
MyVector3 position;
MyVector3 target;
MyVector3 up;
float fovy;
int projection;
};

class ProcessManager {
public:
ProcessManager(const std::string& title, const std::string& className);
~ProcessManager();

// 进程与窗口状态
bool IsTargetValid();
uint32_t GetPid() const { return _pid; }
ClientAreaInfo GetClientInfo();

// 窗口同步逻辑
void SyncOverlayPosition(void* overlayHandle);

// 内存读取模板
template <typename T>
T Read(uintptr_t address) {
T buffer = {};
ReadMemoryInternal(address, &buffer, sizeof(T));
return buffer;
}

private:
bool ReadMemoryInternal(uintptr_t address, void* buffer, size_t size);

std::string _windowTitle;
std::string _className;
uint32_t _pid = 0;
void* _processHandle = nullptr; // 内部存 HANDLE
};

WindowUtils.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
#include "WindowUtils.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

ProcessManager::ProcessManager(const std::string& title, const std::string& className)
: _windowTitle(title), _className(className) {
}

ProcessManager::~ProcessManager() {
if (_processHandle) CloseHandle((HANDLE)_processHandle);
}

bool ProcessManager::IsTargetValid() {
HWND hwnd = FindWindowA(_className.empty() ? NULL : _className.c_str(), _windowTitle.c_str());
if (!hwnd) return false;

if (_pid == 0) {
DWORD pid = 0;
GetWindowThreadProcessId(hwnd, &pid);
_pid = (uint32_t)pid;
_processHandle = (void*)OpenProcess(PROCESS_VM_READ, FALSE, pid);
}
return _processHandle != nullptr;
}

ClientAreaInfo ProcessManager::GetClientInfo() {
ClientAreaInfo info = { 0, 0, 0, 0, false };
HWND hwnd = FindWindowA(_className.empty() ? NULL : _className.c_str(), _windowTitle.c_str());

if (hwnd && IsWindow(hwnd)) {
info.isVisible = (GetForegroundWindow() == hwnd);

RECT cRect;
GetClientRect(hwnd, &cRect);
info.width = cRect.right - cRect.left;
info.height = cRect.bottom - cRect.top;

POINT pt = { 0, 0 };
ClientToScreen(hwnd, &pt);
info.x = pt.x;
info.y = pt.y;
}
return info;
}

void ProcessManager::SyncOverlayPosition(void* overlayHandle) {
HWND myHwnd = (HWND)overlayHandle;
ClientAreaInfo info = GetClientInfo();

if (info.width > 0 && info.isVisible) {
if (!IsWindowVisible(myHwnd)) ShowWindow(myHwnd, SW_SHOWNOACTIVATE);

// 只有位置变化时才移动,减少抖动
static int lastX = 0, lastY = 0;
if (info.x != lastX || info.y != lastY) {
SetWindowPos(myHwnd, HWND_TOPMOST, info.x, info.y, info.width, info.height, SWP_NOACTIVATE);
lastX = info.x; lastY = info.y;
}
}
else {
if (IsWindowVisible(myHwnd)) ShowWindow(myHwnd, SW_HIDE);
}
}

bool ProcessManager::ReadMemoryInternal(uintptr_t address, void* buffer, size_t size) {
if (!_processHandle) return false;
SIZE_T read;
return ReadProcessMemory((HANDLE)_processHandle, (LPCVOID)address, buffer, size, &read) && (read == size);
}