- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 280
- Реакции
- 6
Привет всем обитателям. Решил поделиться небольшим наброском кода для работы с памятью в The Division 2. Если кто-то ковыряет игру и хочет зафиксировать значение патронов на 999, то этот пример поможет понять структуру цепочки указателей (pointer chain).
Техническая часть:
В основе лежит обычный RPM/WPM. Для поиска нужного адреса используем цепочку оффсетов, которую можно вытянуть через Cheat Engine. Код написан на C++, используем ReadProcessMemory для навигации по указателям и WriteProcessMemory для принудительного изменения значения в цикле.
Важные моменты:
Кто пробовал реализовывать другие механики через подобные цепочки? Есть ли смысл переходить на полноценный Kernel-драйвер для записи или EAC/BattlEye сейчас слишком агрессивно мониторит именно эту область памяти? Делитесь результатами тестов.
Техническая часть:
В основе лежит обычный RPM/WPM. Для поиска нужного адреса используем цепочку оффсетов, которую можно вытянуть через Cheat Engine. Код написан на C++, используем ReadProcessMemory для навигации по указателям и WriteProcessMemory для принудительного изменения значения в цикле.
Код:
Reading
#include <windows.h>
#include <iostream>
#include <vector>
#include <TlHelp32.h>
#include <Psapi.h>
// Get process ID by name
DWORD GetProcessIdByName(const wchar_t* processName) {
DWORD processId = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry)) {
do {
if (wcscmp(entry.szExeFile, processName) == 0) {
processId = entry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
}
return processId;
}
// Follow a pointer chain with multiple offsets
uintptr_t ResolvePointerChain(HANDLE hProcess, uintptr_t baseAddr, std::vector<uintptr_t> offsets) {
uintptr_t addr = baseAddr;
for (size_t i = 0; i < offsets.size(); ++i) {
if (!ReadProcessMemory(hProcess, (LPCVOID)addr, &addr, sizeof(addr), nullptr)) {
std::cerr << "Failed to read memory.\n";
return 0;
}
addr += offsets[i];
}
return addr;
}
int main() {
const wchar_t* processName = L"TheDivision2.exe";
DWORD pid = GetProcessIdByName(processName);
if (pid == 0) {
std::cerr << "Process not found.\n";
return 1;
}
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!hProcess) {
std::cerr << "Failed to open process.\n";
return 1;
}
// Base address from Cheat Engine pointer (you may need to get actual module base)
uintptr_t baseAddress = 0x06DE73D0;
// Get actual module base of the process
HMODULE hMods[1024];
DWORD cbNeeded;
uintptr_t moduleBase = 0;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
moduleBase = (uintptr_t)hMods[0]; // Assuming first module is the main EXE
}
uintptr_t pointerBase = moduleBase + baseAddress;
std::vector<uintptr_t> offsets = { 0x18, 0x48, 0x8, 0x10, 0x240, 0x5C8, 0x148 };
uintptr_t finalAddr = ResolvePointerChain(hProcess, pointerBase, offsets);
int value = 0;
if (ReadProcessMemory(hProcess, (LPCVOID)finalAddr, &value, sizeof(value), nullptr)) {
std::cout << "Final value: " << value << std::endl;
} else {
std::cerr << "Failed to read final value.\n";
}
CloseHandle(hProcess);
return 0;
}
Write 999
#include <windows.h>
#include <iostream>
#include <vector>
#include <TlHelp32.h>
#include <Psapi.h>
DWORD GetProcessIdByName(const wchar_t* processName) {
DWORD processId = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry)) {
do {
if (wcscmp(entry.szExeFile, processName) == 0) {
processId = entry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
}
return processId;
}
uintptr_t ResolvePointerChain(HANDLE hProcess, uintptr_t baseAddr, std::vector<uintptr_t> offsets) {
uintptr_t addr = baseAddr;
for (size_t i = 0; i < offsets.size(); ++i) {
if (!ReadProcessMemory(hProcess, (LPCVOID)addr, &addr, sizeof(addr), nullptr)) {
std::cerr << "Failed to read memory.\n";
return 0;
}
addr += offsets[i];
}
return addr;
}
int main() {
const wchar_t* processName = L"TheDivision2.exe";
DWORD pid = GetProcessIdByName(processName);
if (pid == 0) {
std::cerr << "Process not found.\n";
return 1;
}
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!hProcess) {
std::cerr << "Failed to open process.\n";
return 1;
}
uintptr_t baseAddress = 0x06DE73D0;
HMODULE hMods[1024];
DWORD cbNeeded;
uintptr_t moduleBase = 0;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
moduleBase = (uintptr_t)hMods[0]; // First module is usually the .exe
}
uintptr_t pointerBase = moduleBase + baseAddress;
std::vector<uintptr_t> offsets = { 0x18, 0x48, 0x8, 0x10, 0x240, 0x5C8, 0x148 };
uintptr_t finalAddr = ResolvePointerChain(hProcess, pointerBase, offsets);
int value = 999;
// Optionally: infinite loop to freeze the value
while (true) {
if (WriteProcessMemory(hProcess, (LPVOID)finalAddr, &value, sizeof(value), nullptr)) {
std::cout << "Value 999 written successfully." << std::endl;
} else {
std::cerr << "Failed to write value." << std::endl;
}
Sleep(100); // Sleep 100ms to avoid CPU overload
}
CloseHandle(hProcess);
return 0;
}
/* cut here */
//Write 999
#include <windows.h>
#include <iostream>
#include <vector>
#include <TlHelp32.h>
DWORD GetProcessIdByName(const wchar_t* processName) {
DWORD processId = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry)) {
do {
if (wcscmp(entry.szExeFile, processName) == 0) {
processId = entry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
}
return processId;
}
uintptr_t ResolvePointerChain(HANDLE hProcess, uintptr_t baseAddr, std::vector<uintptr_t> offsets) {
uintptr_t addr = baseAddr;
for (size_t i = 0; i < offsets.size(); ++i) {
if (!ReadProcessMemory(hProcess, (LPCVOID)addr, &addr, sizeof(addr), nullptr)) {
std::cerr << "Failed to read memory.\n";
return 0;
}
addr += offsets[i];
}
return addr;
}
int main() {
const wchar_t* processName = L"TheDivision2.exe";
DWORD pid = GetProcessIdByName(processName);
if (pid == 0) {
std::cerr << "Process not found.\n";
return 1;
}
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!hProcess) {
std::cerr << "Failed to open process.\n";
return 1;
}
uintptr_t baseAddress = 0x06DE73D0;
HMODULE hMods[1024];
DWORD cbNeeded;
uintptr_t moduleBase = 0;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
moduleBase = (uintptr_t)hMods[0]; // First module is usually the .exe
}
uintptr_t pointerBase = moduleBase + baseAddress;
std::vector<uintptr_t> offsets = { 0x18, 0x48, 0x8, 0x10, 0x240, 0x5C8, 0x148 };
uintptr_t finalAddr = ResolvePointerChain(hProcess, pointerBase, offsets);
int value = 999;
// Optionally: infinite loop to freeze the value
while (true) {
if (WriteProcessMemory(hProcess, (LPVOID)finalAddr, &value, sizeof(value), nullptr)) {
std::cout << "Value 999 written successfully." << std::endl;
} else {
std::cerr << "Failed to write value." << std::endl;
}
Sleep(100); // Sleep 100ms to avoid CPU overload
}
CloseHandle(hProcess);
return 0;
}
Важные моменты:
- Этот код работает через стандартный WinAPI, что небезопасно для лайв-серверов.
- Оффсеты могут меняться после каждого обновления игры, так что актуализируйте их через дампы.
- Использование WPM в цикле с большой частотой может привести к багам визуализации или крашу, рекомендую добавить `Sleep(100)`.
Кто пробовал реализовывать другие механики через подобные цепочки? Есть ли смысл переходить на полноценный Kernel-драйвер для записи или EAC/BattlEye сейчас слишком агрессивно мониторит именно эту область памяти? Делитесь результатами тестов.