UseRay.exe

Image of MVC software architecture pattern diagram

raylib的游戏架构图

绘制多个3d个物体做无无规则移动并且将ui与逻辑分离

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include <raylib.h>
#include <raymath.h>
#include <vector>
#include <iostream>

// 保持结构体对齐,确保 Search 程序读取步长为 24 字节
struct Enemy {
Vector3 pos;
Vector3 vel;
};

class GameModel {
public:
Camera3D camera = { 0 };
std::vector<Enemy> enemies;
bool isPaused = false;

GameModel() {
// --- 修正点 1: 初始化相机 ---
// 确保相机高度在 1.75f (人眼高度),位置在中心稍微偏移
camera.position = { 0.0f, 1.75f, 5.0f };
camera.target = { 0.0f, 1.75f, 0.0f };
camera.up = { 0.0f, 1.0f, 0.0f };
camera.fovy = 70.0f;
camera.projection = CAMERA_PERSPECTIVE;

// 初始化 10 个敌人
for (int i = 0; i < 10; i++) {
enemies.push_back({
Vector3{ (float)GetRandomValue(-15, 15), 0.0f, (float)GetRandomValue(-15, 15) },
Vector3{ (float)GetRandomValue(-10, 10) * 0.02f, 0.0f, (float)GetRandomValue(-10, 10) * 0.02f }
});
}
}
};

class GameController {
public:
void Update(GameModel& model) {
// --- 修正点 2: 键盘控制逻辑 ---
if (IsKeyPressed(KEY_SPACE)) model.isPaused = !model.isPaused;

// 允许使用 WASD + 鼠标 控制相机移动
// 这将更新 model.camera.position,从而让 Search 程序能搜到变动的坐标
UpdateCamera(&model.camera, CAMERA_FIRST_PERSON);

// 更新敌人逻辑
if (!model.isPaused) {
for (auto& enemy : model.enemies) {
enemy.pos = Vector3Add(enemy.pos, enemy.vel);
// 边界反弹
if (enemy.pos.x > 30.0f || enemy.pos.x < -30.0f) enemy.vel.x *= -1;
if (enemy.pos.z > 30.0f || enemy.pos.z < -30.0f) enemy.vel.z *= -1;

// 随机变向
if (GetRandomValue(0, 100) < 1) {
float angle = GetRandomValue(0, 360) * DEG2RAD;
float speed = 0.15f;
enemy.vel.x = cos(angle) * speed;
enemy.vel.z = sin(angle) * speed;
}
}
}

// --- 打印输出 (供 Search 程序参考) ---
if (!model.enemies.empty()) {
printf("CAM: [%.2f, %.2f, %.2f] | Target[0] X: %6.3f \r",
model.camera.position.x, model.camera.position.y, model.camera.position.z,
model.enemies[0].pos.x);
fflush(stdout);
}
}

void PrintMemoryMap(const GameModel& model) {
uintptr_t modelBase = (uintptr_t)&model;
uintptr_t vectorActualAddr = (uintptr_t)&model.enemies;

std::cout << "--- MEMORY MAP ---" << std::endl;
std::cout << "MODEL_BASE (Camera is near here): 0x" << std::hex << modelBase << std::endl;
std::cout << "VECTOR_PTR (Enemies list): 0x" << std::hex << vectorActualAddr << std::endl;

if (!model.enemies.empty()) {
// std::vector 的内部布局:Begin, End, Capacity
uintptr_t* vPtr = (uintptr_t*)vectorActualAddr;
std::cout << "VEC_BEGIN (Heap Addr): 0x" << std::hex << vPtr[0] << std::endl;
std::cout << "ENEMY[0] ADDR: 0x" << std::hex << (uintptr_t)&model.enemies[0].pos.x << std::endl;
}
std::cout << "----------------------------------------" << std::endl;
}
};

class GameView {
public:
void Draw(const GameModel& model) {
BeginDrawing();
ClearBackground(BLACK);

BeginMode3D(model.camera);
DrawPlane({ 0, 0, 0 }, { 100, 100 }, DARKGRAY); // 地面
DrawGrid(20, 1.0f); // 辅助网格

for (const auto& enemy : model.enemies) {
// 画个胶囊体代表敌人
DrawCapsule(enemy.pos, Vector3Add(enemy.pos, { 0, 1.8f, 0 }), 0.3f, 8, 4, RED);
}
EndMode3D();

// UI 绘制
DrawText("WASD: Move | Mouse: Look | Space: Pause", 20, 20, 20, RAYWHITE);
DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, 2, GREEN); // 准星

EndDrawing();
}
};

int main() {
// 初始化窗口
const int screenWidth = 1280;
const int screenHeight = 720;
InitWindow(screenWidth, screenHeight, "UseRay - Moveable Camera");

SetTargetFPS(60);
DisableCursor(); // 捕捉鼠标用于第一人称控制

GameModel model;
GameController controller;
GameView view;

controller.PrintMemoryMap(model);

while (!WindowShouldClose()) {
controller.Update(model);
view.Draw(model);
}

CloseWindow();
return 0;
}

UseRay.exe重构版:核心系统分离

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#include <raylib.h>
#include <raymath.h>
#include <vector>
#include <iostream>

// --- 1. 基础类型定义 ---
enum EntityType { ENTITY_PLAYER, ENTITY_ENEMY, ENTITY_ITEM };

class Entity {
public:
Vector3 position;
Vector3 velocity;
EntityType type;
bool active = true;

virtual void Update() = 0; // 纯虚函数,强制子类实现
virtual ~Entity() {}
};

// --- 2. 具体实体类 ---
class Player : public Entity {
public:
float health = 100.0f;
Camera3D* refCamera; // 玩家持有相机的引用

Player(Camera3D* cam) {
type = ENTITY_PLAYER;
position = cam->position;
refCamera = cam;
}

void Update() override {
// 同步相机位置到实体位置(逻辑层更新数据)
this->position = refCamera->position;
}
};

class Enemy : public Entity {
public:
int id;
Enemy(int id) : id(id) {
type = ENTITY_ENEMY;
// 随机初始位置
position = { (float)GetRandomValue(-15, 15), 0.0f, (float)GetRandomValue(-15, 15) };
// 随机移动速度
velocity = { (float)GetRandomValue(-10, 10) * 0.01f, 0.0f, (float)GetRandomValue(-10, 10) * 0.01f };
}

void Update() override {
// 纯逻辑位移计算
position = Vector3Add(position, velocity);

// 简易碰撞边界检测
if (position.x > 20.0f || position.x < -20.0f) velocity.x *= -1;
if (position.z > 20.0f || position.z < -20.0f) velocity.z *= -1;
}
};

// --- 3. 中央世界管理器 (The World/Level) ---
class GameWorld {
public:
Player* localPlayer = nullptr;
std::vector<Entity*> entities;
Camera3D camera = { 0 };

GameWorld() {
// 初始化视角
camera.position = { 0.0f, 1.75f, 5.0f };
camera.target = { 0.0f, 1.75f, 0.0f };
camera.up = { 0.0f, 1.0f, 0.0f };
camera.fovy = 70.0f;
camera.projection = CAMERA_PERSPECTIVE;

// 创建逻辑对象
localPlayer = new Player(&camera);
entities.push_back(localPlayer);

for (int i = 0; i < 10; i++) {
entities.push_back(new Enemy(i));
}
}

~GameWorld() {
for (auto e : entities) delete e;
entities.clear();
}

void UpdateAll() {
// 驱动相机(Raylib 内部逻辑)
UpdateCamera(&camera, CAMERA_FIRST_PERSON);

// 驱动所有实体逻辑更新
for (auto& e : entities) {
if (e->active) e->Update();
}
}

// 渲染层实现:将逻辑数据转化为图形
void Render() {
// 1. 环境渲染
DrawGrid(20, 1.0f);
DrawPlane({ 0.0f, 0.0f, 0.0f }, { 40.0f, 40.0f }, DARKGRAY);

// 2. 实体渲染
for (auto e : entities) {
if (!e->active) continue;

switch (e->type) {
case ENTITY_ENEMY:
{
// 注意:Raylib 的 DrawCube 是中心对齐
// position.y 为 0 时,1.8f 高度的方块中心应在 0.9f 处
Vector3 renderPos = { e->position.x, 0.9f, e->position.z };
DrawCube(renderPos, 1.0f, 1.8f, 1.0f, RED);
DrawCubeWires(renderPos, 1.0f, 1.8f, 1.0f, MAROON);
break;
}
case ENTITY_PLAYER:
// 第一人称不画身体,画个位置标记(仅调试可见)
break;

}
}
}
};

// --- 4. 全局单例入口 ---
class GameEngine {
public:
static GameEngine* instance;
GameWorld* world = nullptr;

static GameEngine* GetInstance() {
if (!instance) instance = new GameEngine();
return instance;
}

void Init() {
world = new GameWorld();
}

// 简单的清理机制
static void Shutdown() {
if (instance) {
delete instance->world;
delete instance;
instance = nullptr;
}
}
};

GameEngine* GameEngine::instance = nullptr;
#include <cstddef>
void PrintRealOffsets() {
// 实例化一个测试对象(如果是单例,直接用 GetInstance)
GameEngine* engine = GameEngine::GetInstance();
GameWorld* world = engine->world;
Entity* player = world->localPlayer;

printf("--- Real-time Memory Offsets (x64) ---\n");

// 1. GameEngine -> World
printf("Engine->World Offset: 0x%llX\n",
(uintptr_t)&engine->world - (uintptr_t)engine);

// 2. GameWorld -> Camera
printf("World->Camera Offset: 0x%llX\n",
(uintptr_t)&world->camera - (uintptr_t)world);

// 3. Entity -> Position (注意虚函数表的影响)
printf("Entity->Position Offset: 0x%llX\n",
(uintptr_t)&player->position - (uintptr_t)player);
}
// --- 5. 运行主程序 ---
int main() {
// 屏幕与输入初始化
InitWindow(1280, 720, "Professional Game Architecture - V4 Separated Render");
SetTargetFPS(60);
DisableCursor();

GameEngine::GetInstance()->Init();

// 内存分析打印(逆向工程基址参考)
std::cout << "--- ARCHITECTURE ADDRESS MAP ---" << std::endl;
std::cout << "[Base] G_Engine Instance: 0x" << std::hex << &GameEngine::instance << std::endl;
std::cout << "[L2] World Pointer: 0x" << std::hex << GameEngine::GetInstance()->world << std::endl;
std::cout << "[L3] Player Object: 0x" << std::hex << GameEngine::GetInstance()->world->localPlayer << std::endl;

while (!WindowShouldClose()) {
// --- 逻辑步 ---
GameEngine::GetInstance()->world->UpdateAll();

// --- 渲染步 ---
BeginDrawing();
ClearBackground(BLACK);

BeginMode3D(GameEngine::GetInstance()->world->camera);
GameEngine::GetInstance()->world->Render();
EndMode3D();

// UI 渲染(逻辑层之外的视觉装饰)
DrawRectangle(10, 10, 250, 80, Fade(SKYBLUE, 0.5f));
DrawFPS(20, 20);
DrawText("WASD/Mouse: Move", 20, 40, 20, WHITE);
DrawText("Architecture: Separated Logic/Render", 20, 60, 10, YELLOW);

EndDrawing();
}

GameEngine::Shutdown();
CloseWindow();
return 0;
}

search.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


#include <raylib.h>
#include "ESPContext.h"
#include "ESPLogic.h"
#include "ESPView.h"

// ----------------------------------------------------------------
// 链接选项:在发布模式下隐藏控制台窗口
// ----------------------------------------------------------------
#ifndef _DEBUG
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#endif

int main() {
// 1. 初始化 Overlay 窗口属性
// FLAG_WINDOW_UNDECORATED: 无边框
// FLAG_WINDOW_TRANSPARENT: 窗口背景透明
// FLAG_WINDOW_TOPMOST: 窗口置顶(确保画在游戏上面)
// FLAG_WINDOW_MOUSE_PASSTHROUGH: 鼠标穿透(不影响点击游戏)
SetConfigFlags(FLAG_WINDOW_UNDECORATED | FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_TOPMOST | FLAG_WINDOW_MOUSE_PASSTHROUGH);

// 初始化渲染窗口(建议与游戏分辨率一致,或后续动态同步)
InitWindow(1280, 720, "Raylib_ESP_Overaly");
SetTargetFPS(144); // 刷新率建议略高于游戏或保持一致

// 2. 初始化 MVC 三大核心组件
ESPContext ctx; // 存储所有每一帧的渲染数据
ESPLogic logic; // 负责读内存、矩阵变换
ESPView view; // 负责在窗口画框

// 3. 主循环
while (!WindowShouldClose()) {

// --- 第一阶段:逻辑更新 ---
// 这一步会根据基址 [UseRay.exe + 0x1D0321] 读取内存
// 并自动计算所有敌人的屏幕位置
logic.Update(ctx);

// --- 第二阶段:绘制渲染 ---
BeginDrawing();

// 必须清理背景为 BLANK (全透明),否则会看到之前的残留或黑屏
ClearBackground(BLANK);

// 根据 ctx 里的实体列表画方框
view.Draw(ctx);

// (可选) 绘制调试信息
if (ctx.isGameRunning) {
view.RenderText("SYSTEM ACTIVE", { 10, (float)GetScreenHeight() - 25 },10,GREEN,true);
}
else {

view.RenderText("SYSTEM ACTIVE", { 10, (float)GetScreenHeight() - 25 }, 15, RED);
}

EndDrawing();
}

// 4. 清理资源并退出
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
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
#pragma once
#include <raylib.h>
#include <vector>
#include <string>

// ----------------------------------------------------------------
// ESPContext.h - 核心数据模型
// ----------------------------------------------------------------

struct ESPContext {
// --- 1. 环境与状态 ---
unsigned int targetPid = 0;
bool isGameRunning = false;

// 渲染窗口参数(用于 WorldToScreen 缩放)
float screenWidth = 1280.0f;
float screenHeight = 720.0f;

// --- 2. 核心数学数据 ---
// 从相机地址 (localPlayer + 0x34) 处读取的 16 个 float
// 它是 ViewMatrix 和 ProjectionMatrix 的乘积
Matrix viewProjMatrix = { 0 };

// --- 3. 实体对象模型 ---
// 经过 Logic 层处理后的“成品”数据,View 层直接遍历即可绘制
struct Entity {
// 原始世界坐标 (从偏移 0x28, 0x2C, 0x30 读取)
Vector3 worldPos;

// 投影后的屏幕坐标
Vector2 screenFoot; // 底部中心(用于计算方框位置)
Vector2 screenHead; // 顶部中心(用于计算方框高度)

// 辅助信息
float distance; // 距离,用于调整方框厚度或显示文本
bool onScreen; // 是否在视野内
bool isLocalPlayer; // 是否是玩家自己 (通常 i=0)
};

// 当前关卡/世界中的所有有效实体
std::vector<Entity> entities;

// --- 4. 配置项 (可选扩展) ---
struct Config {
bool showBox = true;
bool showDistance = true;
bool showSnaplines = false;
Color boxColor = RED;
} config;

// 清理函数:每一帧开始前重置实体列表
void Clear() {
entities.clear();
}
};

// ----------------------------------------------------------------
// 内存偏移量定义 (建议放在这里或 Logic 顶部作为常量)
// ----------------------------------------------------------------
namespace Offsets {
// --- Global Bases ---
const uintptr_t G_Engine = 0x25558;

// --- Engine Class ---
const uintptr_t Engine_WorldPtr = 0x0;

// --- World Class ---
const uintptr_t World_LocalPlayer = 0x0;
const uintptr_t World_EntityList = 0x10; // Vector _Myfirst
const uintptr_t World_EntityListEnd = 0x18; // Vector _Mylast
const uintptr_t World_Camera = 0x28; // Camera3D struct

// --- Entity Class ---
const uintptr_t Entity_Position = 0x8; // Vector3
const uintptr_t Entity_Health = 0x20; // 假设值,供以后扩展
}
namespace Offsets {
std::string TargetProcess = "UseRay.exe";
}

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

class ESPView {
public:
/**
* @brief 核心绘制函数
* 每一帧由 main 循环调用,根据 Context 中的数据进行渲染
*/
void Draw(const ESPContext& ctx) {
if (!ctx.isGameRunning) return;

// 左上角状态展示
DrawRectangle(10, 10, 220, 80, Fade(BLACK, 0.6f));
RenderText(TextFormat("ESP: ACTIVE"), { 20, 20 }, 20, LIME);
RenderText(TextFormat("TARGETS: %d", (int)ctx.entities.size()), { 20, 45 }, 18, YELLOW);
for (const auto& entity : ctx.entities) {
if (!entity.onScreen || entity.isLocalPlayer) continue;

// 1. 获取基础高度(头脚间距)
float rawHeight = std::abs(entity.screenFoot.y - entity.screenHead.y);
if (rawHeight < 2.0f) continue;

// 2. 添加“呼吸空间” (Padding)
// 增加高度,让头顶和脚底不至于贴着边框
float boxHeight = rawHeight * 1.2f;

// 调整宽度比例:0.6 到 0.7 之间通常看起来最舒服
float boxWidth = boxHeight * 0.65f;

// 3. 重新计算左上角位置 (为了居中,需要向左偏移宽度的一半,向上偏移高度的增量)
// 这里的偏移量 (boxHeight - rawHeight) / 2 是为了让方框在垂直方向也均匀扩开
float verticalOffset = (boxHeight - rawHeight) / 2.0f;
Vector2 topLeft = {
entity.screenHead.x - (boxWidth / 2.0f),
entity.screenHead.y - verticalOffset
};

// --- 开始绘制 ---
// 绘制方框
DrawRectangleLinesEx({ topLeft.x, topLeft.y, boxWidth, boxHeight }, 2.0f, RED);

// 绘制四角高亮 (可选:这种风格比全框更高级,不会遮挡视线)
// DrawESPCorners(topLeft, boxWidth, boxHeight, 2.0f, RED);
}
// 绘制中心准星
DrawCircle((float)GetScreenWidth() / 2, (float)GetScreenHeight() / 2, 2, GREEN);
}

void RenderText(const char* text, Vector2 pos, float size, Color color, bool center = false) {
Font f = GetFontDefault();
float spacing = 2.0f;
Vector2 textSize = MeasureTextEx(f, text, size, spacing);
Vector2 origin = center ? Vector2{ textSize.x / 2, 0 } : Vector2{ 0, 0 };

DrawTextPro(f, text, pos, origin, 0.0f, size, spacing, color);
}
};

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#pragma once
#include "ESPContext.h"
#include "WindowUtils.h"
#include <raymath.h>
#include <vector>
#include <iostream>

class ESPLogic {
public:
// 构造函数:初始化目标游戏信息
ESPLogic() : _game("Professional Game Architecture - V4 Separated Render", "GLFW30") {}

/**
* @brief 每一帧的核心更新逻辑
*/

void Update(ESPContext& ctx) {
static uintptr_t cachedModuleBase = 0;

// 1. 进程存活检查
if (!_game.IsTargetValid()) {
ctx.isGameRunning = false;
cachedModuleBase = 0;
_game.SyncOverlayPosition(GetWindowHandle());
return;
}

// 2. 窗口状态与大小同步
ClientAreaInfo client = _game.GetClientInfo();
ctx.isGameRunning = client.isVisible;
ctx.screenWidth = (float)client.width;
ctx.screenHeight = (float)client.height;

// 3. 执行窗口隐藏/显示逻辑 (决定 SW_SHOW 还是 SW_HIDE)
_game.SyncOverlayPosition(GetWindowHandle());

// 4. 数据处理核心
if (ctx.isGameRunning) {
// 只有在窗口显示时才确保基址存在,进一步节省后台 CPU
if (cachedModuleBase == 0) {
cachedModuleBase = _game.GetModuleBase(Offsets::TargetProcess);
}

if (cachedModuleBase != 0) {
RefreshGameData(ctx, cachedModuleBase);
}
}
else {
// 处于后台时不仅清空列表,也可以重置某些状态
ctx.entities.clear();
}
}

private:
ProcessManager _game;

/**
* @brief 按照指针链读取内存并转换坐标
*/
// 在 ESPLogic.h 中修改函数签名
void RefreshGameData(ESPContext& ctx, uintptr_t moduleBase) {
ctx.Clear();

// 1. 获取基础指针
uintptr_t engineInstance = _game.Read<uintptr_t>(moduleBase + Offsets::G_Engine);
if (!engineInstance) return;
uintptr_t gameWorld = _game.Read<uintptr_t>(engineInstance + Offsets::Engine_WorldPtr);
if (!gameWorld) return;

// 2. 读取相机并合成矩阵 (0x28 偏移)
Camera3D gameCam = _game.Read<Camera3D>(gameWorld + Offsets::World_Camera);
Matrix view = GetCameraMatrix(gameCam);
Matrix projection = MatrixPerspective(gameCam.fovy * (PI / 180.0f),
(double)ctx.screenWidth / ctx.screenHeight,
0.01, 1000.0);
ctx.viewProjMatrix = MatrixMultiply(view, projection);

// 3. 遍历实体 (假设 Vector 在 0x10)
uintptr_t listStart = _game.Read<uintptr_t>(gameWorld + Offsets::World_EntityList);
uintptr_t listEnd = _game.Read<uintptr_t>(gameWorld + Offsets::World_EntityListEnd);
int count = (int)((listEnd - listStart) / 8);

uintptr_t localPlayer = _game.Read<uintptr_t>(gameWorld + Offsets::World_LocalPlayer);

for (int i = 0; i < count; i++) {
uintptr_t entPtr = _game.Read<uintptr_t>(listStart + (i * Offsets::Entity_Position));
if (!entPtr || entPtr == localPlayer) continue;

Vector3 pos = _game.Read<Vector3>(entPtr + Offsets::Entity_Position);

ESPContext::Entity ent;
ent.worldPos = pos;
ent.isLocalPlayer = false;

// 关键:计算屏幕坐标
bool footOn = WorldToScreen(pos, ctx.viewProjMatrix, ent.screenFoot, ctx.screenWidth, ctx.screenHeight);
Vector3 headWorld = { pos.x, pos.y + 1.8f, pos.z };
bool headOn = WorldToScreen(headWorld, ctx.viewProjMatrix, ent.screenHead, ctx.screenWidth, ctx.screenHeight);

// 只要脚在屏幕内,就强制标记为 true 并存入列表
if (footOn) {
ent.onScreen = true;
// 计算距离用于显示
Vector3 myPos = _game.Read<Vector3>(localPlayer + Offsets::Entity_Position);
ent.distance = Vector3Distance(myPos, pos);
ctx.entities.push_back(ent);
}
}
}


/**
* @brief 经典的 WorldToScreen 数学转换函数
*/

bool WorldToScreen(Vector3 pos, Matrix mat, Vector2& screenPos, float width, float height) {
// 1. W分量判定(裁剪)
float w = pos.x * mat.m3 + pos.y * mat.m7 + pos.z * mat.m11 + mat.m15;
if (w < 0.01f) return false;

// 2. X, Y 计算
float x = pos.x * mat.m0 + pos.y * mat.m4 + pos.z * mat.m8 + mat.m12;
float y = pos.x * mat.m1 + pos.y * mat.m5 + pos.z * mat.m9 + mat.m13;

float invW = 1.0f / w;
x *= invW;
y *= invW;

// 3. 映射到像素空间 (确保 width/height 是 1280/720 而不是 0)
screenPos.x = (width / 2.0f) + (x * width / 2.0f);
screenPos.y = (height / 2.0f) - (y * height / 2.0f); // 注意这里是减法,对应坐标系翻转

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

// ----------------------------------------------------------------
// 窗口与显示信息包
// ----------------------------------------------------------------
struct ClientAreaInfo {
int x, y;
int width, height;
bool isVisible;
};

// ----------------------------------------------------------------
// ProcessManager: 负责所有与目标进程相关的底层操作
// ----------------------------------------------------------------
class ProcessManager {
public:
ProcessManager(const std::string& title, const std::string& className = "");
~ProcessManager();

// 状态检查
bool IsTargetValid();
uint32_t GetPid() const { return _pid; }
ClientAreaInfo GetClientInfo();

// 内存读取模板:支持直接读取结构体、矩阵、基本类型
template <typename T>
T Read(uintptr_t address) {
T buffer = {};
if (address < 0x10000) return buffer; // 简单的无效地址保护
ReadMemoryInternal(address, &buffer, sizeof(T));
return buffer;
}

// 获取模块(如 UseRay.exe)的基址
uintptr_t GetModuleBase(const std::string& moduleName);
uintptr_t GetModuleBaseSnap(const std::string& moduleName);
uintptr_t FindPattern(const char* pattern, const char* mask);
void SyncOverlayPosition(void* overlayHandle);
private:
// 底层内存读取封装
bool ReadMemoryInternal(uintptr_t address, void* buffer, size_t size);

std::string _windowTitle;
std::string _className;
void* _processHandle = nullptr;
uint32_t _pid = 0;
};

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
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include "WindowUtils.h"
#include <vector>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tlhelp32.h>
#include <dwmapi.h>
#include <locale>
#include <codecvt>
#include <psapi.h>


#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "dwmapi.lib")

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

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

/**
* @brief 检查目标进程是否有效,若无效则尝试重新获取句柄
*/
bool ProcessManager::IsTargetValid() {
if (_processHandle) {
DWORD exitCode = 0;
if (GetExitCodeProcess(_processHandle, &exitCode) && exitCode == STILL_ACTIVE) {
return true;
}
CloseHandle(_processHandle);
_processHandle = nullptr;
_pid = 0;
}

// 通过窗口名查找
HWND hwnd = FindWindowA(_className.empty() ? NULL : _className.c_str(), _windowTitle.c_str());
if (!hwnd) return false;

GetWindowThreadProcessId(hwnd, (LPDWORD)&_pid);
_processHandle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, _pid);

return _processHandle != nullptr;
}

/**
* @brief 获取窗口客户区的坐标和大小
*/



ClientAreaInfo ProcessManager::GetClientInfo() {
ClientAreaInfo info = { 0, 0, 0, 0, false };
// 建议:在初始化时保存游戏 HWND,不要每帧 FindWindow
HWND hwnd = FindWindowA(_className.empty() ? NULL : _className.c_str(), _windowTitle.c_str());

if (hwnd && IsWindow(hwnd)) {
// 1. 检查游戏是否被最小化
bool isMinimized = IsIconic(hwnd);

// 2. 检查当前前台窗口
HWND foregroundHwnd = GetForegroundWindow();

// 只有当游戏是前台窗口且没被最小化时,才认为可见
// 增加检测:确保前台窗口不是 ESP 窗口本身
info.isVisible = (!isMinimized) && (foregroundHwnd == 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;
}

/**
* @brief 核心:获取模块(如 UseRay.exe)的基址
*/
uintptr_t ProcessManager::GetModuleBaseSnap(const std::string& moduleName) {
uintptr_t dwModuleBase = 0;
// 显式调用 W 版本快照
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, _pid);

if (hSnapshot != INVALID_HANDLE_VALUE) {
MODULEENTRY32W me; // 显式使用 W 结构体
me.dwSize = sizeof(MODULEENTRY32W);

// 转换 string 为 wstring
std::wstring wName(moduleName.begin(), moduleName.end());

if (Module32FirstW(hSnapshot, &me)) {
do {
if (wName == me.szModule) {
dwModuleBase = (uintptr_t)me.modBaseAddr;
break;
}
} while (Module32NextW(hSnapshot, &me));
}
CloseHandle(hSnapshot);
}
return dwModuleBase;
}
uintptr_t ProcessManager::GetModuleBase(const std::string& moduleName) {
if (!_processHandle) return 0;

HMODULE hMods[1024];
DWORD cbNeeded;
std::wstring wModuleName(moduleName.begin(), moduleName.end());

// EnumProcessModulesEx 比快照方案更轻量,直接枚举当前进程已加载的模块
if (EnumProcessModulesEx(_processHandle, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL)) {
for (unsigned int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
wchar_t szModName[MAX_PATH];

// 获取模块文件的基础名称 (不含路径)
if (GetModuleBaseNameW(_processHandle, hMods[i], szModName, sizeof(szModName) / sizeof(wchar_t))) {
if (wModuleName == szModName) {
return (uintptr_t)hMods[i];
}
}
}
}

return 0;
}
/**
* @brief 底层内存读取:使用 RPM
*/
bool ProcessManager::ReadMemoryInternal(uintptr_t address, void* buffer, size_t size) {
if (!_processHandle || address == 0) return false;

SIZE_T bytesRead = 0;
return ReadProcessMemory(_processHandle, (LPCVOID)address, buffer, size, &bytesRead) && bytesRead == size;
}

// 在 ProcessManager.cpp 中实现
uintptr_t ProcessManager::FindPattern(const char* pattern, const char* mask) {
MODULEINFO mInfo = { 0 };
K32GetModuleInformation(_processHandle, (HMODULE)GetModuleBase("UseRay.exe"), &mInfo, sizeof(mInfo));

uintptr_t base = (uintptr_t)mInfo.lpBaseOfDll;
uintptr_t size = (uintptr_t)mInfo.SizeOfImage;

std::vector<char> moduleMemory(size);
ReadMemoryInternal(base, moduleMemory.data(), size);

for (uintptr_t i = 0; i < size - strlen(mask); i++) {
bool found = true;
for (uintptr_t j = 0; j < strlen(mask); j++) {
if (mask[j] != '?' && pattern[j] != moduleMemory[i + j]) {
found = false;
break;
}
}
if (found) return base + i;
}
return 0;
}


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);
}

// 确保 ESP 始终覆盖游戏客户区,且保持在最顶层
SetWindowPos(myHwnd, HWND_TOPMOST, info.x, info.y, info.width, info.height, SWP_NOACTIVATE);
}
else {
// 游戏不在前台或已最小化:立即隐藏
if (IsWindowVisible(myHwnd)) {
ShowWindow(myHwnd, SW_HIDE);
}
}
}