#include "game.hh"
#include "offs.hh" // ВСЕ актуальные оффсеты хранятся здесь
#include <TlHelp32.h>
#include <thread>
#include <string>
#include <sstream>
/* ───────── helpers ───────── */
static DWORD getPid(const wchar_t* exe)
{
PROCESSENTRY32W pe{ sizeof pe };
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE) return 0;
for (BOOL ok = Process32FirstW(snap, &pe); ok; ok = Process32NextW(snap, &pe))
if (!_wcsicmp(pe.szExeFile, exe)) { CloseHandle(snap); return pe.th32ProcessID; }
CloseHandle(snap);
return 0;
}
static uintptr_t getModule(DWORD pid, const wchar_t* mod)
{
MODULEENTRY32W me{ sizeof me };
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
if (snap == INVALID_HANDLE_VALUE) return 0;
for (BOOL ok = Module32FirstW(snap, &me); ok; ok = Module32NextW(snap, &me))
if (!_wcsicmp(me.szModule, mod)) { CloseHandle(snap); return (uintptr_t)me.modBaseAddr; }
CloseHandle(snap);
return 0;
}
template<class T>
static bool rpm(HANDLE h, uintptr_t addr, T& out)
{
return ReadProcessMemory(h, reinterpret_cast<LPCVOID>(addr),
&out, sizeof(T), nullptr);
}
/* ───────── глобалы, объявлены в game.hh ───────── */
namespace game {
std::vector<Enemy> enemies;
float view[16]{};
std::atomic_bool running{ false };
std::mutex mtx;
}
/* временный контейнер для первого прохода */
struct Enemy {
Vec3 pos;
int hp;
bool visible;
};
struct EnemyTmp {
Vec3 pos;
int hp;
uintptr_t spottedState;
uint64_t lastSeenTick; // время, когда m_bSpotted = true
};
template<typename T>
std::string ToHexString(T value) {
std::stringstream ss;
ss << "0x" << std::hex << value;
return ss.str();
}
/* ------------------------------------------------------------------
game::start / game::stop
------------------------------------------------------------------ */
void game::start()
{
if (running) return;
running = true;
std::thread([] {
/* ---------- attach к процессу ---------- */
const DWORD pid = getPid(L"cs2.exe");
if (!pid) { running = false; return; }
HANDLE h = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,
FALSE, pid);
if (!h) { running = false; return; }
const uintptr_t client = getModule(pid, L"client.dll");
if (!client) { CloseHandle(h); running = false; return; }
/* ---------- основной цикл ---------- */
while (running)
{
/* локальный pawn -------------------------------------------------- */
uintptr_t localPawn = 0;
if (!rpm(h, client + offs::dwLocalPlayerPawn, localPawn) || !localPawn)
{
Sleep(10); continue;
}
uint8_t localTeam = 0;
rpm(h, localPawn + offs::m_iTeamNum, localTeam);
/* view-matrix ----------------------------------------------------- */
rpm(h, client + offs::dwViewMatrix, game::view);
/* индекс локального controller-a --------------------------------- */
uintptr_t localCtrl = 0;
rpm(h, client + offs::dwLocalPlayerController, localCtrl);
if (!localCtrl) { Sleep(10); continue; }
uint32_t hPawn = 0; // CHandle
rpm(h, localCtrl + offs::m_hPlayerPawn, hPawn);
int localBit = (hPawn & 0x7FF);
if (localBit == 0x7FF) continue;
const int dword_index = (localBit / 32) ^ 1; // 0 или 1 (для CS2 порядок обратный)
const int bit_mask = 1 << (localBit % 32);
Vec3 eyeOrigin{}; rpm(h, localPawn + offs::m_vecAbsOrigin, eyeOrigin);
Vec3 viewOffset{}; rpm(h, localPawn + offs::m_vecViewOffset, viewOffset);
Vec3 eye{ eyeOrigin.x + viewOffset.x,
eyeOrigin.y + viewOffset.y,
eyeOrigin.z + viewOffset.z };
int localCtrlIdx = -1;
std::vector<EnemyTmp> tmpRaw;
/* проход #1 — собираем pos/hp/spottedState ----------------------- */
uintptr_t entList = 0;
if (!rpm(h, client + offs::dwEntityList, entList)) { Sleep(10); continue; }
for (int idx = 1; idx < 2048; ++idx)
{
uintptr_t seg = 0;
rpm(h, entList + 0x10 + 0x8 * ((idx >> 9) & 0x7F), seg);
if (!seg) continue;
uintptr_t ent = 0;
rpm(h, seg + 0x78 * (idx & 0x1FF), ent);
if (!ent) continue;
/* 1 если это наш controller — запоминаем индекс и пропускаем */
if (ent == localCtrl) { localCtrlIdx = idx; continue; }
/* --- фильтры pawn-ов --- */
int hp = 0; rpm(h, ent + offs::m_iHealth, hp);
if (hp <= 0 || hp > 100) continue;
uint8_t life = 0; rpm(h, ent + offs::m_lifeState, life);
if (life) continue;
uint8_t team = 0; rpm(h, ent + offs::m_iTeamNum, team);
if (team == localTeam || team == 0) continue;
/* позиция */
uintptr_t node = 0; rpm(h, ent + offs::m_pGameSceneNode, node);
if (!node) continue;
Vec3 pos{}; rpm(h, node + offs::m_vecAbsOrigin, pos);
/* spottedState */
uintptr_t spotted = 0; rpm(h, ent + offs::m_entitySpottedState, spotted);
tmpRaw.push_back({ pos, hp, spotted, 0 });
}
if (localCtrlIdx == -1) { Sleep(10); continue; } // safety
const uint64_t now = GetTickCount64();
std::vector<Enemy> out;
out.reserve(tmpRaw.size());
// В функции game::start (обработка видимости врагов):
for (auto& r : tmpRaw)
{
bool vis = false;
if (r.spottedState)
{
uint32_t spotted_mask[2]{};
bool readSuccess = rpm(h, r.spottedState + offs::m_bSpottedByMask, spotted_mask);
// Проверка общего флага видимости m_bSpotted
bool base_spotted = false;
rpm(h, r.spottedState + offs::m_bSpotted, base_spotted);
// Корректный расчет индекса
const int dword_index = (localBit / 32) % 2; // 0 или 1
const int bit_mask = 1 << (localBit % 32);
vis = base_spotted && (spotted_mask[dword_index] & bit_mask) != 0;
}
out.push_back({ r.pos, r.hp, vis });
}
/* ---------- публикуем ---------- */
{
std::lock_guard<std::mutex> lk(game::mtx);
game::enemies.swap(out);
}
Sleep(10); // ~100 Гц
}
CloseHandle(h);
}).detach();
}
void game::stop()
{
running = false;
}