ZLYNX

Blog

16 min read

Hướng dẫn code cấu trúc project Overlay ESP C++

Share:

Tài liệu này hướng dẫn cách code lại cấu trúc project theo đúng kiểu source hiện tại: mỗi file chịu một nhiệm vụ rõ ràng, luồng chính chạy nhiều thread, dữ liệu entity được quét ở thread nền và overlay Direct2D render ở thread riêng.

Phần đọc/ghi bộ nhớ nên được xem như một interface dữ liệu. Khi học hoặc test lại cấu trúc, hãy dùng dữ liệu giả hoặc môi trường bạn có quyền kiểm thử trước, rồi mới thay MemoryClient bằng backend hợp lệ của bạn.

1. Chia project thành các tầng

Trước khi code, chia project thành 7 tầng:

FileVai trò
struct.hKiểu toán học cơ bản: vector, rotator, khoảng cách
offset.hHằng số offset/config đọc dữ liệu
driver.hLớp giao tiếp nguồn dữ liệu bộ nhớ
overlay.h / overlay.cppTạo cửa sổ overlay và API vẽ Direct2D
util.hEntity model, camera model, ma trận, WorldToScreen, cache tên
global.hState dùng chung toàn app
ESP.hLogic runtime: menu, hotkey, scanner, renderer
main.cppBootstrap: init backend, init overlay, tạo thread

Thứ tự code hợp lý là:

  1. struct.h
  2. offset.h
  3. overlay.hoverlay.cpp
  4. driver.h
  5. util.h
  6. global.h
  7. ESP.h
  8. main.cpp

Lý do: các file sau phụ thuộc vào kiểu dữ liệu và API của file trước.

2. Code struct.h

File này nên thật nhỏ, chỉ chứa kiểu toán học dùng ở mọi nơi.

Mục tiêu:

  1. Tạo Vector3 để lưu tọa độ 3D.
  2. Tạo các operator cộng, trừ, nhân, chia.
  3. Có hàm Length, DistTo, Dot.
  4. Có helper đổi đơn vị nếu cần.

Khung code:

1#pragma once 2#include <cmath> 3 4struct Vector3 5{ 6 double x, y, z; 7 8 Vector3 operator-(Vector3 other) { return { x - other.x, y - other.y, z - other.z }; } 9 Vector3 operator+(Vector3 other) { return { x + other.x, y + other.y, z + other.z }; } 10 Vector3 operator*(double value) { return { x * value, y * value, z * value }; } 11 12 double Length() 13 { 14 return std::sqrt((x * x) + (y * y) + (z * z)); 15 } 16 17 double DistTo(Vector3 other) 18 { 19 return (*this - other).Length(); 20 } 21 22 double Dot(Vector3& other) 23 { 24 return x * other.x + y * other.y + z * other.z; 25 } 26}; 27 28struct Vector4 29{ 30 double w, x, y, z; 31}; 32 33inline float ToMeters(double value) 34{ 35 return static_cast<float>(value / 39.62); 36}

Sau bước này, các file khác có thể dùng Vector3 cho vị trí actor, camera và phép chiếu màn hình.

3. Code offset.h

File này chỉ nên chứa offset/config, không viết logic trong đây.

Khung:

1#pragma once 2#include <cstdint> 3 4namespace offsets 5{ 6 inline uintptr_t GWorld = 0x0; 7 inline uintptr_t GNames = 0x0; 8 inline uintptr_t GObjects = 0x0; 9 10 inline uintptr_t OwningGameInstance = 0x0; 11 inline uintptr_t LocalPlayers = 0x0; 12 inline uintptr_t PlayerController = 0x0; 13 inline uintptr_t AcknowledgedPawn = 0x0; 14 inline uintptr_t PlayerCameraManager = 0x0; 15 inline uintptr_t CameraCachePrivate = 0x0; 16 17 inline uintptr_t PersistentLevel = 0x0; 18 inline uintptr_t ActorArray = 0x0; 19 inline uintptr_t ActorCount = 0x0; 20 inline uintptr_t ActorID = 0x0; 21 22 inline uintptr_t RootComponent = 0x0; 23 inline uintptr_t RelativeLocation = 0x0; 24}

Trong project thật của bạn, các giá trị đang được điền sẵn. Khi game/app mục tiêu thay đổi version, chỉ sửa file này để hạn chế chạm vào logic.

4. Code overlay.h

overlay.h là hợp đồng vẽ. Header chỉ khai báo class.

Các hàm cần có:

1#pragma once 2#include <Windows.h> 3#include <d2d1.h> 4 5class FOverlay 6{ 7public: 8 static int ScreenHeight; 9 static int ScreenWidth; 10 11 void create_window(); 12 BOOL window_init(); 13 BOOL init_d2d(); 14 void d2d_shutdown(); 15 16 void begin_scene(); 17 void end_scene(); 18 void clear_scene(); 19 void clear_screen(); 20 21 HWND retrieve_window(); 22 23 void draw_text(float x, float y, D2D1::ColorF color, const char* text, ...); 24 void draw_line(float x1, float y1, float x2, float y2, D2D1::ColorF color); 25 void draw_box(D2D1_RECT_F rect, D2D1::ColorF color); 26 void draw_circle(float x, float y, float radius, float segments, D2D1::ColorF color); 27};

Ý tưởng là mọi nơi khác chỉ gọi g_overlay->draw_text(...) hoặc g_overlay->draw_line(...), không cần biết Direct2D được khởi tạo ra sao.

5. Code overlay.cpp

Implementation của overlay gồm 4 nhóm:

  1. Tạo hoặc lấy window.
  2. Set style trong suốt, click-through, top-most.
  3. Khởi tạo Direct2D/DirectWrite.
  4. Viết primitive vẽ.

Skeleton:

1#include "overlay.h" 2#include <dwmapi.h> 3#include <dwrite.h> 4#include <cstdio> 5 6#pragma comment(lib, "Dwmapi.lib") 7#pragma comment(lib, "d2d1.lib") 8#pragma comment(lib, "Dwrite.lib") 9 10static HWND win = nullptr; 11ID2D1Factory* d2d_factory = nullptr; 12ID2D1HwndRenderTarget* target = nullptr; 13IDWriteFactory* write_factory = nullptr; 14IDWriteTextFormat* format = nullptr; 15 16int FOverlay::ScreenHeight = 0; 17int FOverlay::ScreenWidth = 0; 18 19void FOverlay::create_window() 20{ 21 // Đăng ký WNDCLASSEX, sau đó CreateWindowExA với WS_EX_TOPMOST, 22 // WS_EX_LAYERED và WS_EX_TRANSPARENT. 23} 24 25BOOL FOverlay::window_init() 26{ 27 // Tìm overlay window có sẵn hoặc tự tạo window mới. 28 // Sau đó set transparency và top-most. 29 return TRUE; 30} 31 32BOOL FOverlay::init_d2d() 33{ 34 // D2D1CreateFactory 35 // DWriteCreateFactory 36 // CreateTextFormat 37 // CreateHwndRenderTarget 38 return TRUE; 39} 40 41void FOverlay::begin_scene() 42{ 43 target->BeginDraw(); 44} 45 46void FOverlay::end_scene() 47{ 48 target->EndDraw(); 49} 50 51void FOverlay::clear_scene() 52{ 53 target->Clear(); 54}

Sau đó thêm draw_line, draw_text, draw_box, draw_circle. Mỗi hàm nên tạo brush tạm, vẽ, rồi Release brush để tránh leak.

6. Code driver.h như một data backend

Trong project của bạn, driver.h đang là backend đọc/ghi bộ nhớ. Để cấu trúc sạch hơn, hãy nhìn nó như một lớp MemoryClient: nhiệm vụ là init, attach, đọc raw, ghi raw và đọc typed value.

Interface tối thiểu:

1#pragma once 2#include <Windows.h> 3#include <cstdint> 4 5class DRV 6{ 7public: 8 static inline HANDLE driver_handle = INVALID_HANDLE_VALUE; 9 uint32_t process_id = 0; 10 uintptr_t process_base = 0; 11 12 static bool Init(); 13 bool Attach(const char* process_name); 14 uintptr_t GetModuleBase(); 15 16 template<typename T> 17 T rpm(uintptr_t address) 18 { 19 T buffer{}; 20 ReadRaw(address, &buffer, sizeof(T)); 21 return buffer; 22 } 23 24 bool ReadRaw(uintptr_t address, void* buffer, size_t size); 25 bool WriteRaw(uintptr_t address, void* buffer, size_t size); 26 27 static bool IsValidUserAddress(uintptr_t address, size_t size = 1); 28};

Điểm quan trọng khi code lớp này:

  1. Luôn kiểm tra handle hợp lệ.
  2. Luôn kiểm tra buffer != nullptr.
  3. Luôn kiểm tra địa chỉ và size trước khi đọc/ghi.
  4. rpm<T> chỉ là wrapper mỏng quanh ReadRaw.
  5. Không để logic render hoặc entity filter nằm trong file này.

Ví dụ guard địa chỉ:

1static bool IsValidUserAddress(uintptr_t address, size_t size = 1) 2{ 3 constexpr uintptr_t min_user_address = 0x10000; 4 constexpr uintptr_t max_user_address = 0x00007FFFFFFFFFFF; 5 6 if (address < min_user_address || address > max_user_address) 7 return false; 8 9 if (size == 0) 10 return false; 11 12 const uintptr_t end = address + size - 1; 13 return end >= address && end <= max_user_address; 14}

Khi học lại cấu trúc, bạn có thể thay implementation thật bằng mock:

1bool DRV::ReadRaw(uintptr_t, void* buffer, size_t size) 2{ 3 std::memset(buffer, 0, size); 4 return true; 5}

Như vậy overlay, menu, render loop vẫn test được mà chưa cần backend thật.

7. Code util.h

util.h là nơi đặt model trung gian và hàm chuyển tọa độ.

Đầu tiên tạo entity model:

1#pragma once 2#include "struct.h" 3#include <mutex> 4#include <string> 5#include <vector> 6 7struct EntityList 8{ 9 uintptr_t instance = 0; 10 uintptr_t root_component = 0; 11 uintptr_t PlayerState = 0; 12 uintptr_t Pawn = 0; 13 14 Vector3 origin{}; 15 Vector3 TopLocation{}; 16 17 std::string name; 18 float health = 0.0f; 19 float dist = 0.0f; 20 int objectId = 0; 21 int team = 0; 22}; 23 24inline std::vector<EntityList> entityList; 25inline std::mutex entityListMutex;

Tiếp theo tạo camera model:

1struct FMinimalViewInfo 2{ 3 Vector3 Location; 4 Vector3 Rotation; 5 float FOV; 6}; 7 8struct FCameraCacheEntry 9{ 10 float Timestamp; 11 char pad_4[0xc]; 12 FMinimalViewInfo POV; 13};

Sau đó code matrix và WorldToScreen. Hàm này nhận camera và tọa độ world, trả về tọa độ màn hình:

1static Vector3 WorldToScreen(FMinimalViewInfo camera, Vector3 worldLocation) 2{ 3 Vector3 screen(0, 0, 0); 4 5 // 1. Tạo rotation matrix từ camera.Rotation. 6 // 2. Tính delta = worldLocation - camera.Location. 7 // 3. Chiếu delta lên trục camera. 8 // 4. Nếu điểm nằm sau camera thì return zero. 9 // 5. Dùng FOV và screen center để tính x/y. 10 11 return screen; 12}

Trong source hiện tại, WorldToScreen đã làm đủ các bước này. Khi refactor, nên giữ hàm này độc lập để unit test bằng camera giả.

8. Code global.h

global.h giữ state runtime. Không nên đặt hàm xử lý phức tạp trong file này.

Nhóm biến nên có:

1#pragma once 2#include "struct.h" 3#include "overlay.h" 4#include "driver.h" 5 6inline DRV* DBD = new DRV(); 7 8inline bool esp = false; 9inline bool espLine = false; 10inline bool distanceESp = false; 11inline bool name = false; 12inline bool mate = false; 13inline bool crosshair = false; 14 15inline bool showmenu = true; 16inline bool rendering = true; 17 18inline int frame = 0; 19inline FOverlay* g_overlay = nullptr; 20 21inline float ScreenCenterX = 0.0f; 22inline float ScreenCenterY = 0.0f; 23inline float FOV = 90.0f; 24inline float distanceMax = 1200.0f; 25 26inline uint64_t process_base = 0; 27inline uint32_t process_id = 0;

Nếu dùng C++17 trở lên, inline global variables giúp tránh lỗi multiple definition khi include header ở nhiều file.

9. Code hotkey trong ESP.h

Tạo hàm Update để đọc phím và đổi state.

Pattern:

1void Update() 2{ 3 while (true) 4 { 5 if (rendering) 6 { 7 if ((GetAsyncKeyState(VK_F1) & 1) && showmenu) 8 esp = !esp; 9 10 if ((GetAsyncKeyState(VK_F2) & 1) && showmenu) 11 distanceESp = !distanceESp; 12 13 if ((GetAsyncKeyState(VK_F4) & 1) && showmenu) 14 espLine = !espLine; 15 16 if ((GetAsyncKeyState(VK_F5) & 1) && showmenu) 17 name = !name; 18 } 19 20 if (GetAsyncKeyState(VK_INSERT) & 1) 21 showmenu = !showmenu; 22 23 Sleep(5); 24 } 25}

Với các giá trị số như FOVdistanceMax, luôn clamp:

1if ((GetAsyncKeyState(VK_LEFT) & 1) && showmenu && FOV > 40) 2 FOV -= 1.0f; 3 4if ((GetAsyncKeyState(VK_RIGHT) & 1) && showmenu && FOV < 170) 5 FOV += 1.0f;

10. Code menu overlay

Menu chỉ đọc state và vẽ text.

1void RenderMenu() 2{ 3 if (!showmenu || !rendering) 4 return; 5 6 g_overlay->draw_text(5, 5, D2D1::ColorF(255, 20, 20, 255), "SHOW/HIDE [INSERT]"); 7 8 g_overlay->draw_text( 9 5, 10 20, 11 esp ? D2D1::ColorF(0, 255, 0, 255) : D2D1::ColorF(255, 0, 0, 255), 12 esp ? "F1 ESP : ON" : "F1 ESP : OFF" 13 ); 14}

Code menu nên đơn giản, không đọc entity, không đọc memory, không xử lý toán.

11. Code crosshair

Crosshair không cần entity, chỉ cần tâm màn hình:

1void RenderCrosshair() 2{ 3 if (!crosshair) 4 return; 5 6 const float size = 8.0f; 7 const float gap = 2.0f; 8 auto color = D2D1::ColorF(0, 100, 255, 255); 9 10 g_overlay->draw_line(ScreenCenterX - size, ScreenCenterY, ScreenCenterX - gap, ScreenCenterY, color); 11 g_overlay->draw_line(ScreenCenterX + gap, ScreenCenterY, ScreenCenterX + size, ScreenCenterY, color); 12 g_overlay->draw_line(ScreenCenterX, ScreenCenterY - size, ScreenCenterX, ScreenCenterY - gap, color); 13 g_overlay->draw_line(ScreenCenterX, ScreenCenterY + gap, ScreenCenterX, ScreenCenterY + size, color); 14}

Đây là phần dễ test nhất sau khi overlay chạy.

12. Code scanner entity

Scanner nên chạy ở thread riêng. Nó đọc dữ liệu thô, convert thành EntityList, rồi thay thế snapshot chung.

Pattern an toàn:

1void ScannerLoop() 2{ 3 while (true) 4 { 5 if (!esp) 6 { 7 Sleep(10); 8 continue; 9 } 10 11 std::vector<EntityList> temp; 12 13 // 1. Đọc dữ liệu từ backend. 14 // 2. Validate pointer hoặc record. 15 // 3. Tính distance. 16 // 4. Lọc entity cần render. 17 // 5. Push vào temp. 18 19 { 20 std::lock_guard<std::mutex> lock(entityListMutex); 21 entityList = temp; 22 } 23 24 Sleep(15); 25 } 26}

Quy tắc quan trọng:

  1. Không render trực tiếp trong scanner.
  2. Không giữ mutex khi đang đọc dữ liệu chậm.
  3. Dùng temp local, cuối vòng mới swap/copy vào entityList.
  4. Luôn validate dữ liệu trước khi push.
  5. Dùng set/map để chống duplicate nếu nguồn dữ liệu có thể lặp.

Với môi trường test, bạn có thể code scanner giả trước:

1void ScannerLoopMock() 2{ 3 while (true) 4 { 5 std::vector<EntityList> temp; 6 7 temp.push_back(EntityList{ 8 .origin = Vector3{ 100.0, 100.0, 0.0 }, 9 .TopLocation = Vector3{ 100.0, 100.0, 125.0 }, 10 .name = "Mock Entity", 11 .dist = 25.0f 12 }); 13 14 { 15 std::lock_guard<std::mutex> lock(entityListMutex); 16 entityList = temp; 17 } 18 19 Sleep(100); 20 } 21}

Khi overlay và renderer đã ổn, thay mock bằng scanner thật.

13. Code renderer entity

Renderer chỉ lấy snapshot entity rồi vẽ.

1void RenderESP() 2{ 3 if (!esp) 4 return; 5 6 std::vector<EntityList> copy; 7 8 { 9 std::lock_guard<std::mutex> lock(entityListMutex); 10 copy = entityList; 11 } 12 13 for (const auto& entity : copy) 14 { 15 // Với dữ liệu 3D thật thì gọi WorldToScreen. 16 // Với mock 2D thì có thể vẽ trực tiếp để test overlay. 17 18 if (name) 19 g_overlay->draw_text(100.0f, 100.0f, D2D1::ColorF(0, 255, 0, 255), entity.name.c_str()); 20 21 if (distanceESp) 22 g_overlay->draw_text(100.0f, 120.0f, D2D1::ColorF(255, 255, 255, 255), "[%d]m", static_cast<int>(entity.dist)); 23 } 24}

Trong project thật, bước vẽ dùng:

  1. WorldToScreen(cameraCacheCopy.POV, Entity.origin)
  2. WorldToScreen(cameraCacheCopy.POV, Entity.TopLocation)
  3. Nếu điểm hợp lệ thì vẽ line/name/distance.

14. Code render loop

Render loop là vòng lặp cố định:

1static void render(FOverlay* overlay) 2{ 3 while (true) 4 { 5 std::this_thread::sleep_for(std::chrono::milliseconds(5)); 6 7 overlay->begin_scene(); 8 overlay->clear_scene(); 9 10 frame++; 11 RenderMenu(); 12 RenderCrosshair(); 13 RenderESP(); 14 15 overlay->end_scene(); 16 } 17}

Thứ tự render nên giữ:

  1. Clear scene.
  2. Vẽ menu.
  3. Vẽ crosshair.
  4. Vẽ ESP/entity.
  5. End scene.

Nếu muốn menu luôn nằm trên entity, đổi menu xuống cuối.

15. Code _init

Hàm _init chuẩn bị overlay và tạo thread:

1static void _init(FOverlay* overlay) 2{ 3 if (!overlay->window_init()) 4 return; 5 6 if (!overlay->init_d2d()) 7 return; 8 9 std::thread scanner(ScannerLoop); 10 std::thread renderer(render, overlay); 11 std::thread updater(Update); 12 13 scanner.detach(); 14 renderer.join(); 15 updater.detach(); 16 17 overlay->d2d_shutdown(); 18}

Trong source hiện tại, scanner tương ứng với BaseThread2. Bạn có thể giữ tên đó, nhưng về mặt ý nghĩa thì nó là entity scanner.

16. Code main.cpp

main.cpp chỉ nên làm bootstrap:

  1. Init backend dữ liệu.
  2. Attach target nếu có.
  3. Lấy screen center.
  4. Tạo overlay.
  5. Gọi _init.

Skeleton:

1#include "global.h" 2#include "ESP.h" 3#include <iostream> 4#include <thread> 5 6int main() 7{ 8 if (!DRV::Init()) 9 { 10 std::cout << "[!] Khong the khoi tao data backend" << std::endl; 11 return 0; 12 } 13 14 // Attach target hoặc dùng mock data khi test. 15 16 ScreenCenterX = static_cast<float>(GetSystemMetrics(SM_CXSCREEN)) / 2.0f; 17 ScreenCenterY = static_cast<float>(GetSystemMetrics(SM_CYSCREEN)) / 2.0f; 18 19 g_overlay = new FOverlay(); 20 _init(g_overlay); 21 22 return 0; 23}

Không nên đặt logic scan entity dài trong main.cpp. File này càng ngắn thì project càng dễ debug.

17. Dump SDK bằng Dumper-7 và lấy GWorld, GNames

Khi game hoặc app Unreal Engine cập nhật, các offset trong offset.h có thể sai. Lúc đó cần dump lại SDK bằng Dumper-7 để tra class/field, đồng thời dùng signature scanner như GSpots để lấy nhanh các global quan trọng: GWorld, GNames, GObjects.

Trong ảnh release bạn gửi, file cần tải là GSpots.exe. Tool này tiện cho bước lấy global offset. Dumper-7 thì dùng cho phần SDK đầy đủ: class, struct, enum, property offset.

Quy trình nên đi theo thứ tự:

  1. Mở target Unreal Engine trong môi trường bạn có quyền test.
  2. Đợi game/app load ổn định, tốt nhất ở menu chính hoặc một scene đã vào world.
  3. Chạy GSpots.exe.
  4. Chọn đúng process, ví dụ DeadByDaylight-Win64-Shipping.exe.
  5. Chạy scan signature.
  6. Ghi lại GWorld, GNames, GObjects.
  7. Chạy Dumper-7 để dump SDK.
  8. Dùng SDK dump để đối chiếu các field offset như OwningGameInstance, PersistentLevel, ActorArray, RootComponent.
  9. Cập nhật offset.h.
  10. Build và kiểm tra từng pointer trong console.

Output scanner thường có dạng:

1GWorld = 0xD2C6938 2GNames = 0xCFED7C0 3GObjects = 0xD0F5BE0

Nếu giá trị là RVA, cập nhật trực tiếp vào offset.h:

1namespace offsets 2{ 3 inline uintptr_t GWorld = 0xD2C6938; 4 inline uintptr_t GNames = 0xCFED7C0; 5 inline uintptr_t GObjects = 0xD0F5BE0; 6}

Trong code hiện tại, GWorldGNames được dùng theo kiểu cộng với process_base:

1uWorld = DBD->rpm<uintptr_t>(process_base + offsets::GWorld); 2 3uint64_t GNameTable = process_base + offsets::GNames;

Vì vậy giá trị trong offset.h phải là RVA, không phải absolute address. Nếu tool trả địa chỉ tuyệt đối, phải trừ module base:

1offset = absolute_address - process_base

Ví dụ:

1process_base = 0x7FF600000000 2absolute GWorld = 0x7FF60D2C6938 3GWorld offset = 0x000000000D2C6938

Khi đưa vào offset.h, viết gọn:

1uintptr_t GWorld = 0xD2C6938;

Dùng Dumper-7 để tra field offset

Dumper-7 thường tạo ra thư mục SDK gồm nhiều file header. Sau khi dump xong, tìm các class gốc của Unreal Engine:

Cần tìmClass thường gặp
World hiện tạiUWorld
Game instanceUGameInstance
Level và actor listULevel
Local player/controllerULocalPlayer, APlayerController
Pawn/player stateAPawn, APlayerState
Vị trí actorAActor, USceneComponent
CameraAPlayerCameraManager, FMinimalViewInfo

Ví dụ khi tra SDK:

1Class Engine.World 2 OwningGameInstance 3 PersistentLevel 4 5Class Engine.Level 6 Actors 7 8Class Engine.PlayerController 9 AcknowledgedPawn 10 PlayerCameraManager 11 12Class Engine.Actor 13 RootComponent 14 15Class Engine.SceneComponent 16 RelativeLocation

Sau khi tìm được offset field, cập nhật phần tương ứng trong offset.h:

1uintptr_t OwningGameInstance = 0x250; 2uintptr_t PersistentLevel = 0x50; 3uintptr_t LocalPlayers = 0x58; 4uintptr_t PlayerController = 0x50; 5uintptr_t AcknowledgedPawn = 0x3A0; 6uintptr_t PlayerCameraManager = 0x3B0; 7uintptr_t RootComponent = 0x1E0; 8uintptr_t RelativeLocation = 0x178;

Nhóm offset cần kiểm tra thường xuyên:

NhómOffset
GlobalGWorld, GNames, GObjects
WorldOwningGameInstance, PersistentLevel
PlayerLocalPlayers, PlayerController, AcknowledgedPawn
CameraPlayerCameraManager, CameraCachePrivate, các offset FOV
ActorActorArray, ActorCount, ActorID
TransformRootComponent, RelativeLocation, ComponentToWorld

Cách kiểm tra GWorld

Sau khi update GWorld, build lại và xem console ở đoạn:

1uWorld = DBD->rpm<uintptr_t>(process_base + offsets::GWorld); 2std::cout << "[+] Found Uworld = 0x" << std::hex << uWorld << std::dec << std::endl;

Nếu đúng, uWorld phải là địa chỉ user-mode hợp lệ, không phải 0x0, không quá thấp, không ra giá trị rác.

Sau đó kiểm tra tiếp:

1gameInstance = DBD->rpm<uintptr_t>(uWorld + offsets::OwningGameInstance); 2persistentLevel = DBD->rpm<uintptr_t>(uWorld + offsets::PersistentLevel);

Nếu uWorld đúng nhưng gameInstance hoặc persistentLevel sai, lỗi thường nằm ở OwningGameInstance hoặc PersistentLevel, không phải GWorld.

Cách kiểm tra GNames

GNames được dùng trong GetNameById để đổi ActorID thành tên object:

1uint64_t GNameTable = process_base + offsets::GNames;

Dấu hiệu GNames sai:

  1. GetNameById thường trả "NULL".
  2. Actor scan chạy nhưng không lọc ra survivor/killer/generator.
  3. Tên object bị rỗng, ký tự rác hoặc length bất thường.

Cách debug đơn giản là in thử một vài actorName sau khi đọc ActorID. Nếu toàn "NULL" trong khi ActorArray có count hợp lý, ưu tiên kiểm tra lại GNames, ActorID và format name table.

Checklist sau khi dump

Sau khi sửa offset, kiểm tra từng tầng theo thứ tự:

  1. process_base khác 0.
  2. uWorld hợp lệ.
  3. gameInstancepersistentLevel hợp lệ.
  4. localPlayer, playerController, cameraManager hợp lệ.
  5. actorsArray hợp lệ và actorsCount nằm trong khoảng bình thường.
  6. GetNameById trả được tên object thật.
  7. WorldToScreen vẽ đúng vị trí.

Nếu tầng trước sai thì không debug tầng sau vội. Ví dụ GWorld sai thì mọi offset phía sau đều có thể rác.

18. Build và test theo từng mốc

Không nên code hết rồi mới test. Test theo mốc:

  1. Build được project rỗng với struct.h, global.h.
  2. Tạo overlay window và clear scene không crash.
  3. Vẽ được text menu.
  4. Hotkey bật/tắt menu hoạt động.
  5. Crosshair vẽ đúng tâm màn hình.
  6. Scanner mock tạo entity giả.
  7. Renderer vẽ entity giả.
  8. Thay mock bằng backend dữ liệu thật trong môi trường được phép.
  9. Thêm WorldToScreen.
  10. Tối ưu filter và mutex.

Nếu lỗi, khoanh vùng theo tầng. Overlay lỗi thì không debug scanner. Scanner lỗi thì không sửa WorldToScreen vội.

19. Checklist hoàn chỉnh

Khi project hoàn chỉnh, mỗi tầng phải trả lời được:

  1. driver.h: backend có init/attach/read/write rõ ràng chưa?
  2. overlay.cpp: có release resource Direct2D chưa?
  3. global.h: biến dùng chung có bị duplicate definition không?
  4. util.h: entityList có mutex bảo vệ chưa?
  5. ESP.h: scanner có dùng list tạm trước khi cập nhật global không?
  6. RenderESP: có copy snapshot rồi mới render không?
  7. Update: hotkey có clamp giá trị số không?
  8. main.cpp: có giữ vai trò bootstrap, không chứa logic lớn không?

20. Tóm tắt cách code

Luồng code chuẩn là:

1struct.h 2 -> offset.h 3 -> overlay.h / overlay.cpp 4 -> driver.h 5 -> util.h 6 -> global.h 7 -> ESP.h 8 -> main.cpp

Luồng runtime chuẩn là:

1main 2 -> init backend 3 -> init overlay 4 -> thread Update 5 -> thread ScannerLoop/BaseThread2 6 -> thread render 7 -> RenderMenu + RenderCrosshair + RenderESP

Nếu giữ đúng ranh giới này, project sẽ dễ mở rộng hơn: muốn đổi backend thì sửa driver.h, muốn đổi style vẽ thì sửa overlay.cpp/RenderESP, muốn đổi filter entity thì sửa scanner, còn main.cpp gần như không cần đụng.

Share: