- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 546
- Reaction score
- 7
Anyone currently digging into DMA hardware knows that the MemProcFS API can be a bit of a wall for beginners. Found this approach that simplifies things by treating the memory space like a standard file system—effective if you want a chill radar without the risk of a rage-induced manual ban.
The Architecture
Instead of hooking into the game process via standard handles (which EAC loves to flag), this implementation leverages a DMA card to map the system memory to a virtual drive (M:). The code scans for the r5apex process folder and reads the memory.vmem file directly using CreateFileA and ReadFile. It is a bit of a "lazy" way to handle DMA, but it works for an external radar setup.
Core Offsets
The following offsets are currently used in the logic for entity iteration and local player data:
Technical Notes & Troubleshooting
This method relies on MemProcFS being active and correctly mapping the memory. If the M: drive is missing, the tool will obviously fail to initialize. Also, reading from .vmem is strictly slower than using the native Vmm.dll functions, but for a 2D radar, the latency is negligible.
Anti-Cheat Context
Since this is 100% DMA, you are physically bypassing the OS kernel for memory access. EAC has a hard time with this unless your firmware is pasted or your TLP (Transaction Layer Packet) signatures are flagged. Keep your firmware private, and don't run a visible overlay on the same machine—use a second PC or a fuser if you want to stay truly safe.
have fun reversing
The Architecture
Instead of hooking into the game process via standard handles (which EAC loves to flag), this implementation leverages a DMA card to map the system memory to a virtual drive (M:). The code scans for the r5apex process folder and reads the memory.vmem file directly using CreateFileA and ReadFile. It is a bit of a "lazy" way to handle DMA, but it works for an external radar setup.
Core Offsets
The following offsets are currently used in the logic for entity iteration and local player data:
- Entity List: 0x612c5e8
- Local Player: 0x2563888
- Health: 0x324
- Shield: 0x1a0
- Position: 0x17c
- Life State: 0x690
Code:
//
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <thread>
#include <chrono>
#include <cmath>
#include <Windows.h>
using namespace std;
// OFFSETS - April 14, 2026
#define OFF_ENTITY_LIST 0x612c5e8
#define OFF_LOCAL_PLAYER 0x2563888
#define OFF_HEALTH 0x324
#define OFF_MAX_HEALTH 0x468
#define OFF_SHIELD 0x1a0
#define OFF_MAX_SHIELD 0x1a4
#define OFF_POSITION 0x17c
#define OFF_TEAM 0x33c
#define OFF_LIFE_STATE 0x690
string g_ProcFolder;
string g_MemPath;
HANDLE g_MemFile = INVALID_HANDLE_VALUE;
uint64_t g_Base = 0;
// ============================================================================
// READ FROM MEMORY FILE
// ============================================================================
template<typename T>
T Read(uint64_t addr) {
T val = {};
if (g_MemFile == INVALID_HANDLE_VALUE || addr < 0x10000) return val;
LARGE_INTEGER pos;
pos.QuadPart = (LONGLONG)addr;
if (SetFilePointerEx(g_MemFile, pos, NULL, FILE_BEGIN)) {
DWORD bytesRead = 0;
ReadFile(g_MemFile, &val, sizeof(T), &bytesRead, NULL);
}
return val;
}
bool IsValid(uint64_t p) { return p > 0x10000 && p < 0x7FFFFFFFFFFF; }
// ============================================================================
// READ TEXT FILE
// ============================================================================
string ReadTextFile(const string& path) {
ifstream f(path);
if (!f.is_open()) return "";
string content;
getline(f, content);
f.close();
return content;
}
// ============================================================================
// FIND BASE ADDRESS
// ============================================================================
uint64_t FindBaseAddress(const string& procFolder) {
cout << "\n[*] Searching for base address..." << endl;
// Method 1: Check modules folder
string modulesPath = procFolder + "\\modules";
cout << "[*] Checking: " << modulesPath << endl;
WIN32_FIND_DATAA fd;
HANDLE hFind = FindFirstFileA((modulesPath + "\\*").c_str(), &fd);
if (hFind != INVALID_HANDLE_VALUE) {
cout << "[*] Modules found:" << endl;
do {
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
string modName = fd.cFileName;
if (modName != "." && modName != "..") {
cout << " - " << modName << endl;
// Check if it's r5apex
if (modName.find("r5apex") != string::npos) {
string basePath = modulesPath + "\\" + modName + "\\base.txt";
string baseStr = ReadTextFile(basePath);
if (baseStr.empty()) {
// Try without .txt
basePath = modulesPath + "\\" + modName + "\\base";
baseStr = ReadTextFile(basePath);
}
if (!baseStr.empty()) {
uint64_t base = strtoull(baseStr.c_str(), nullptr, 16);
if (base > 0) {
cout << "[+] Found base in " << modName << ": 0x" << hex << base << dec << endl;
return base;
}
}
}
}
}
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
}
// Method 2: Read from map file (memmap)
cout << "[*] Trying memmap..." << endl;
string memmapPath = procFolder + "\\memmap\\memmap.txt";
ifstream mapFile(memmapPath);
if (mapFile.is_open()) {
string line;
while (getline(mapFile, line)) {
if (line.find("r5apex") != string::npos && line.find(".exe") != string::npos) {
// Parse: "00007FF600000000-00007FF600001000 ... r5apex_dx12.exe"
size_t dash = line.find('-');
if (dash != string::npos) {
string addrStr = line.substr(0, dash);
uint64_t base = strtoull(addrStr.c_str(), nullptr, 16);
if (base > 0x10000) {
cout << "[+] Found base in memmap: 0x" << hex << base << dec << endl;
mapFile.close();
return base;
}
}
}
}
mapFile.close();
}
// Method 3: Read from vads
cout << "[*] Trying vads..." << endl;
WIN32_FIND_DATAA vfd;
HANDLE vFind = FindFirstFileA((procFolder + "\\files\\vads\\*.txt").c_str(), &vfd);
if (vFind != INVALID_HANDLE_VALUE) {
do {
string vadName = vfd.cFileName;
if (vadName.find("r5apex") != string::npos) {
// Filename format: 00007FF600000000-r5apex_dx12.exe.txt
size_t dash = vadName.find('-');
if (dash != string::npos) {
string addrStr = vadName.substr(0, dash);
uint64_t base = strtoull(addrStr.c_str(), nullptr, 16);
if (base > 0x10000) {
cout << "[+] Found base in vads: 0x" << hex << base << dec << endl;
FindClose(vFind);
return base;
}
}
}
} while (FindNextFileA(vFind, &vfd));
FindClose(vFind);
}
return 0;
}
// ============================================================================
// STRUCTURES
// ============================================================================
struct Vec3 {
float x, y, z;
float Dist(const Vec3& o) const {
float dx = x - o.x, dy = y - o.y, dz = z - o.z;
return sqrtf(dx*dx + dy*dy + dz*dz) / 39.62f;
}
};
// ============================================================================
// MAIN
// ============================================================================
int main() {
cout << "=== APEX RADAR v2 ===" << endl << endl;
// Find Apex process folder
cout << "[*] Searching M:\\name\\ ..." << endl;
WIN32_FIND_DATAA fd;
HANDLE hFind = FindFirstFileA("M:\\name\\r5apex*", &fd);
if (hFind == INVALID_HANDLE_VALUE) {
cout << "[!] Apex not found in M:\\name\\" << endl;
cout << "[!] Error code: " << GetLastError() << endl;
// Check if M: exists
if (GetFileAttributesA("M:\\") == INVALID_FILE_ATTRIBUTES) {
cout << "[!] M: drive does not exist!" << endl;
cout << "[!] Make sure MemProcFS is running" << endl;
}
system("pause");
return 1;
}
do {
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
g_ProcFolder = string("M:\\name\\") + fd.cFileName;
cout << "[+] Found: " << fd.cFileName << endl;
break;
}
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
// Find base address
g_Base = FindBaseAddress(g_ProcFolder);
if (g_Base == 0) {
cout << "\n[!] Could not auto-detect base address" << endl;
cout << "[*] Enter base address manually (from MemProcFS):" << endl;
cout << "[*] Look in M:\\name\\r5apex...\\modules\\ for the base" << endl;
cout << "\nEnter base (hex, e.g. 7FF600000000): ";
string input;
cin >> input;
g_Base = strtoull(input.c_str(), nullptr, 16);
cin.ignore();
}
if (g_Base == 0) {
cout << "[!] Invalid base address" << endl;
system("pause");
return 1;
}
cout << "\n[+] Using base: 0x" << hex << g_Base << dec << endl;
// Open memory file
g_MemPath = g_ProcFolder + "\\memory.vmem";
cout << "[*] Opening: " << g_MemPath << endl;
g_MemFile = CreateFileA(
g_MemPath.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (g_MemFile == INVALID_HANDLE_VALUE) {
cout << "[!] Cannot open memory.vmem" << endl;
cout << "[!] Error: " << GetLastError() << endl;
system("pause");
return 1;
}
cout << "[+] Memory file opened!" << endl;
// Test offsets
cout << "\n=== TESTING ===" << endl;
uint64_t localPlayer = Read<uint64_t>(g_Base + OFF_LOCAL_PLAYER);
cout << "LocalPlayer ptr: 0x" << hex << localPlayer << dec << endl;
if (IsValid(localPlayer)) {
Vec3 pos = Read<Vec3>(localPlayer + OFF_POSITION);
int hp = Read<int>(localPlayer + OFF_HEALTH);
int shield = Read<int>(localPlayer + OFF_SHIELD);
cout << "Position: " << pos.x << ", " << pos.y << ", " << pos.z << endl;
cout << "Health: " << hp << " | Shield: " << shield << endl;
if (hp > 0 && hp <= 150) {
cout << "\n[+] SUCCESS! Offsets are working!" << endl;
} else {
cout << "\n[?] Health value unusual - may need to update offsets" << endl;
}
} else {
cout << "[!] LocalPlayer is NULL - make sure you're in a match!" << endl;
}
cout << "\nPress Enter to start radar (or Ctrl+C to exit)...";
cin.get();
// Radar loop
while (true) {
system("cls");
uint64_t lp = Read<uint64_t>(g_Base + OFF_LOCAL_PLAYER);
if (!IsValid(lp)) {
cout << "Waiting for match..." << endl;
Sleep(1000);
continue;
}
Vec3 myPos = Read<Vec3>(lp + OFF_POSITION);
int myTeam = Read<int>(lp + OFF_TEAM);
int myHp = Read<int>(lp + OFF_HEALTH);
int myShield = Read<int>(lp + OFF_SHIELD);
cout << "=== APEX RADAR ===" << endl;
cout << "HP: " << myHp << " | Shield: " << myShield << " | Team: " << myTeam << endl;
cout << "Pos: " << (int)myPos.x << ", " << (int)myPos.y << endl;
cout << "==================" << endl;
int enemies = 0;
for (int i = 0; i < 70; i++) {
uint64_t ent = Read<uint64_t>(g_Base + OFF_ENTITY_LIST + ((uint64_t)(i & 0xFFFF) << 5));
if (!IsValid(ent) || ent == lp) continue;
int maxHp = Read<int>(ent + OFF_MAX_HEALTH);
if (maxHp < 100 || maxHp > 150) continue;
int hp = Read<int>(ent + OFF_HEALTH);
int team = Read<int>(ent + OFF_TEAM);
int life = Read<int>(ent + OFF_LIFE_STATE);
if (life != 0 || hp <= 0) continue; // Dead
if (team == myTeam) continue; // Teammate
Vec3 pos = Read<Vec3>(ent + OFF_POSITION);
int shield = Read<int>(ent + OFF_SHIELD);
float dist = myPos.Dist(pos);
printf("[ENEMY] HP: %d+%d | Dist: %.0fm\n", hp, shield, dist);
enemies++;
}
if (enemies == 0) {
cout << "No enemies found" << endl;
}
cout << "\nTotal enemies: " << enemies << endl;
Sleep(150);
}
CloseHandle(g_MemFile);
return 0;
}
Technical Notes & Troubleshooting
This method relies on MemProcFS being active and correctly mapping the memory. If the M: drive is missing, the tool will obviously fail to initialize. Also, reading from .vmem is strictly slower than using the native Vmm.dll functions, but for a 2D radar, the latency is negligible.
Anti-Cheat Context
Since this is 100% DMA, you are physically bypassing the OS kernel for memory access. EAC has a hard time with this unless your firmware is pasted or your TLP (Transaction Layer Packet) signatures are flagged. Keep your firmware private, and don't run a visible overlay on the same machine—use a second PC or a fuser if you want to stay truly safe.
have fun reversing
Last edited by a moderator: