refactor: harden external data layer

This commit is contained in:
kedou 2026-08-08 10:08:08 +08:00
parent 70b391a9e9
commit 390450e8e8
21 changed files with 1550 additions and 1048 deletions

View File

@ -1,4 +1,4 @@
#include "Framework.h" #include "Framework.h"
#include "../Framework/Input/Input.h" #include "../Framework/Input/Input.h"
#include <cmath> #include <cmath>
#include <cstdio> #include <cstdio>
@ -95,25 +95,23 @@ namespace
void CFramework::AimAll() void CFramework::AimAll()
{ {
// メニュー表示中は何もしない (メニュー操作と干渉するため)
if (g.ShowMenu) if (g.ShowMenu)
return; return;
if (!tarkov->IsInRaid() || local.m_address == 0) const auto snapshot = GetDynamicSnapshot();
if (!snapshot || !snapshot->inRaid || !snapshot->cameraValid || !snapshot->local.IsValid())
return; return;
if (!local.Update()) const CPlayer& localPlayer = snapshot->local;
const Vector2 gameSize((float)g.GameRect.right, (float)g.GameRect.bottom);
if (gameSize.x <= 0.f || gameSize.y <= 0.f)
return; return;
const Vector2 GameSize = Vector2((float)g.GameRect.right, (float)g.GameRect.bottom); // Recoil mouse compensation remains render-thread local and does not touch the world snapshot.
if (GameSize.x <= 0.f || GameSize.y <= 0.f)
return;
// ---- 压枪: 射撃中にマウスを下方向へ ----
if (g.g_Recoil && g.g_RecoilStrength > 0.f && IsKeyDown(VK_LBUTTON)) if (g.g_Recoil && g.g_RecoilStrength > 0.f && IsKeyDown(VK_LBUTTON))
{ {
static DWORD s_lastPull = 0; static DWORD s_lastPull = 0;
DWORD now = GetTickCount(); const DWORD now = GetTickCount();
if (now - s_lastPull >= (DWORD)g.g_RecoilInterval) if (now - s_lastPull >= (DWORD)g.g_RecoilInterval)
{ {
SendMouseMove(0, (LONG)g.g_RecoilStrength); SendMouseMove(0, (LONG)g.g_RecoilStrength);
@ -121,7 +119,6 @@ void CFramework::AimAll()
} }
} }
// ---- 自瞄 ----
if (!g.g_Aim) if (!g.g_Aim)
{ {
m_aimLastTarget = 0; m_aimLastTarget = 0;
@ -130,14 +127,10 @@ void CFramework::AimAll()
return; return;
} }
// FOV 円はキーを押していなくても表示 const Vector2 center(gameSize.x * 0.5f, gameSize.y * 0.5f);
Vector2 Center = Vector2(GameSize.x * 0.5f, GameSize.y * 0.5f);
if (g.g_AimDrawFov) if (g.g_AimDrawFov)
ImGui::GetBackgroundDrawList()->AddCircle(ToImVec2(Center), g.g_AimFov, AimFovColor, 64); ImGui::GetBackgroundDrawList()->AddCircle(ToImVec2(center), g.g_AimFov, AimFovColor, 64);
// 発動条件: 强制开启 或 按住触发键 (右键/左键/F9)
// ※ GetAsyncKeyState / 低级钩子 在本环境(管理员+RawInput独占)都检测不到按键,
// g_AimForce 是绕过按键检测的替代方案
if (!g.g_AimForce && !IsKeyDown(g.g_AimKey0) && !IsKeyDown(g.g_AimKey1) && !IsKeyDown(g.g_AimKey2)) if (!g.g_AimForce && !IsKeyDown(g.g_AimKey0) && !IsKeyDown(g.g_AimKey1) && !IsKeyDown(g.g_AimKey2))
{ {
m_aimLastTarget = 0; m_aimLastTarget = 0;
@ -146,85 +139,67 @@ void CFramework::AimAll()
return; return;
} }
// カメラ位置 (ローカル頭部を優先、無ければ origin) const Vector3 localPos = !Vec3_Empty(localPlayer.m_vecHeadOrigin) ? localPlayer.m_vecHeadOrigin : localPlayer.m_vecOrigin;
local.UpdateBone();
Vector3 localPos = !Vec3_Empty(local.m_vecHeadOrigin) ? local.m_vecHeadOrigin : local.m_vecOrigin;
Matrix ViewMatrix = tarkov->GetViewMatrix(); float bestDist = g.g_AimFov;
Vector3 bestPoint{};
Vector2 bestScreen{};
uintptr_t bestAddr = 0;
// 1. ターゲット選択: 画面中心に最も近い敵 (FOV 円内) m_diagTotal = 0;
float bestDist = g.g_AimFov; m_diagHealth = 0;
Vector3 bestPoint{}; m_diagSkipped = 0;
Vector2 bestScreen{};
uintptr_t bestAddr = 0;
m_diagTotal = 0; m_diagHealth = 0; m_diagSkipped = 0; for (const auto& entity : snapshot->players)
for (auto& entity : GetEntityList())
{ {
CPlayer* pEntity = &entity; if (!entity.IsValid() || entity.m_address == localPlayer.m_address || Vec3_Empty(entity.m_vecOrigin))
if (pEntity->m_address == 0 || pEntity->m_address == local.m_address)
continue; continue;
m_diagTotal++; ++m_diagTotal;
if (!pEntity->Update()) if (GetDistance(localPlayer.m_vecOrigin, entity.m_vecOrigin) > g.g_AimMaxDist)
{ {
m_diagSkipped++; ++m_diagSkipped;
continue; continue;
} }
// 距離チェック if (!IsValidAimTarget(entity))
float dist = GetDistance(local.m_vecOrigin, pEntity->m_vecOrigin);
if (dist > g.g_AimMaxDist)
{ {
m_diagSkipped++; ++m_diagSkipped;
continue; continue;
} }
// 種別フィルタ (PMC / Scav / Boss) // Only reject a target on health when the health chain was actually resolved.
if (!IsValidAimTarget(pEntity->m_iSpawnType)) // ObservedPlayerView in the supplied dump does not expose the same HealthController field.
if (!g.g_AimIgnoreHealth && entity.m_bHealthValid && entity.m_fHealth <= 0.f)
{ {
m_diagSkipped++; ++m_diagHealth;
continue; continue;
} }
// 生存チェック (死体はスキップ) const Vector3 aim = GetAimBone(&entity);
// ※ Mono版で HealthController の offset が違うと全員死亡扱いになる。
// g_AimIgnoreHealth で判定をスキップ可能。
if (!g.g_AimIgnoreHealth)
{
pEntity->UpdateHealth();
if (pEntity->m_fHealth <= 0.f)
{
m_diagHealth++;
continue;
}
}
// 狙うボーン
Vector3 aim = GetAimBone(pEntity);
if (Vec3_Empty(aim)) if (Vec3_Empty(aim))
{ {
m_diagSkipped++; ++m_diagSkipped;
continue; continue;
} }
Vector2 s{}; Vector2 screen{};
if (!WorldToScreen(ViewMatrix, GameSize, aim, s)) if (!WorldToScreen(snapshot->viewMatrix, gameSize, aim, screen))
{ {
m_diagSkipped++; ++m_diagSkipped;
continue; continue;
} }
float d = sqrtf((s.x - Center.x) * (s.x - Center.x) + (s.y - Center.y) * (s.y - Center.y)); const float dx = screen.x - center.x;
if (d < bestDist) const float dy = screen.y - center.y;
const float distanceToCenter = sqrtf(dx * dx + dy * dy);
if (distanceToCenter < bestDist)
{ {
bestDist = d; bestDist = distanceToCenter;
bestAddr = pEntity->m_address; bestAddr = entity.m_address;
bestPoint = aim; bestPoint = aim;
bestScreen = s; bestScreen = screen;
} }
} }
@ -246,35 +221,38 @@ void CFramework::AimAll()
m_aimResidualY = 0.f; m_aimResidualY = 0.f;
} }
// 2. 照準適用
if (g.g_AimMethod == 0) if (g.g_AimMethod == 0)
{ {
m_aimResidualX = 0.f; m_aimResidualX = 0.f;
m_aimResidualY = 0.f; m_aimResidualY = 0.f;
AimMemory(bestPoint, localPos); AimMemory(bestPoint, localPos, localPlayer.m_address);
} }
else else
{ {
AimMouse(bestScreen, Center); AimMouse(bestScreen, center);
} }
// 3. ターゲット線
if (g.g_AimDrawTarget) if (g.g_AimDrawTarget)
ImGui::GetBackgroundDrawList()->AddLine(ToImVec2(Center), ToImVec2(bestScreen), AimTargetColor, 1.f); ImGui::GetBackgroundDrawList()->AddLine(ToImVec2(center), ToImVec2(bestScreen), AimTargetColor, 1.f);
} }
bool CFramework::IsValidAimTarget(const int& SpawnType) bool CFramework::IsValidAimTarget(const CPlayer& player)
{ {
switch (SpawnType) if (!player.m_bSpawnTypeValid)
{
if (!player.IsObserved())
return false;
return player.m_iSide == SIDE_SCAV ? g.g_AimTargetScav : g.g_AimTargetPMC;
}
switch (player.m_iSpawnType)
{ {
// PMC (PvE の敵対 PMC)
case PMC_BEAR_PvE: case PMC_BEAR_PvE:
case PMC_USEC_PvE: case PMC_USEC_PvE:
case PMC_BEAR_NEW: case PMC_BEAR_NEW:
case PMC_USEC_NEW: case PMC_USEC_NEW:
return g.g_AimTargetPMC; return g.g_AimTargetPMC;
// Scav / Raider / Cultist / Partisan / Rogue
case SCAV: case SCAV:
case SNIPER_SCAV: case SNIPER_SCAV:
case NORMAL_SCAV: case NORMAL_SCAV:
@ -286,7 +264,6 @@ bool CFramework::IsValidAimTarget(const int& SpawnType)
case BLOODHOUND_SCAV: case BLOODHOUND_SCAV:
return g.g_AimTargetScav; return g.g_AimTargetScav;
// Boss / Follower / Goons
case RESHALA_BOSS: case RESHALA_BOSS:
case RESHALA_FOLLOW: case RESHALA_FOLLOW:
case KILLA_BOSS: case KILLA_BOSS:
@ -314,22 +291,23 @@ bool CFramework::IsValidAimTarget(const int& SpawnType)
case BRIDEYE_SCAV: case BRIDEYE_SCAV:
return g.g_AimTargetBoss; return g.g_AimTargetBoss;
// 乗り物・不明種別は撃たない
case BTR_VEHICLE: case BTR_VEHICLE:
default: default:
return false; return false;
} }
} }
Vector3 CFramework::GetAimBone(CPlayer* pEntity) Vector3 CFramework::GetAimBone(const CPlayer* pEntity)
{ {
// Head / Neck を更新 if (!pEntity)
pEntity->UpdateBone(); return {};
switch (g.g_AimBone) switch (g.g_AimBone)
{ {
case 1: return pEntity->m_vecNeckOrigin; case 1:
case 2: return pEntity->GetBonePosition(BONE_SPINE_3); // Ribcage return pEntity->m_vecNeckOrigin;
case 2:
return pEntity->m_vecSpineOrigin;
case 0: case 0:
default: default:
return pEntity->m_vecHeadOrigin; return pEntity->m_vecHeadOrigin;
@ -391,9 +369,9 @@ void CFramework::AimMouse(const Vector2& target, const Vector2& center)
// Memory モード: MovementContext._rotation (Vector2 x=yaw y=pitch) へ書く // Memory モード: MovementContext._rotation (Vector2 x=yaw y=pitch) へ書く
// オフセットは g.g_AimMcOffset / g.g_AimRotOffset (运行时可变) を使用 // オフセットは g.g_AimMcOffset / g.g_AimRotOffset (运行时可变) を使用
void CFramework::AimMemory(const Vector3& target, const Vector3& localPos) void CFramework::AimMemory(const Vector3& target, const Vector3& localPos, uintptr_t localAddress)
{ {
if (!IsUsableAimAddress(local.m_address) || if (!IsUsableAimAddress(localAddress) ||
g.g_AimMcOffset <= 0 || g.g_AimMcOffset > 0x1000 || g.g_AimMcOffset <= 0 || g.g_AimMcOffset > 0x1000 ||
g.g_AimRotOffset < 0 || g.g_AimRotOffset > 0x1000) g.g_AimRotOffset < 0 || g.g_AimRotOffset > 0x1000)
{ {
@ -401,7 +379,7 @@ void CFramework::AimMemory(const Vector3& target, const Vector3& localPos)
} }
uintptr_t mc = 0; uintptr_t mc = 0;
if (!m.TryRead(local.m_address + static_cast<uintptr_t>(g.g_AimMcOffset), mc) || if (!m.TryRead(localAddress + static_cast<uintptr_t>(g.g_AimMcOffset), mc) ||
!IsUsableAimAddress(mc)) !IsUsableAimAddress(mc))
{ {
return; return;
@ -439,7 +417,7 @@ void CFramework::AimMemory(const Vector3& target, const Vector3& localPos)
const float smooth = Clamp01(g.g_AimSmooth * 0.01f); const float smooth = Clamp01(g.g_AimSmooth * 0.01f);
const float maxAngleStep = 0.10f + (1.f - smooth) * 0.12f; const float maxAngleStep = 0.10f + (1.f - smooth) * 0.12f;
if (angleError > maxAngleStep) if (angleError > maxAngleStep)
t = min(t, maxAngleStep / angleError); t = (std::min)(t, maxAngleStep / angleError);
Vector2 newRot{ Vector2 newRot{
ClampAngle(rot.x + dyaw * t), ClampAngle(rot.x + dyaw * t),
@ -467,7 +445,9 @@ void CFramework::ScanRotationOffset()
static std::vector<Cand> s_cands; static std::vector<Cand> s_cands;
if (!tarkov->IsInRaid() || local.m_address == 0) const auto snapshot = GetDynamicSnapshot();
const uintptr_t localAddress = snapshot ? snapshot->local.m_address : 0;
if (!snapshot || !snapshot->inRaid || !Memory::IsUserAddress(localAddress))
{ {
strcpy_s(m_scanResult, "仅限局内扫描 (先进 raid 再点)"); strcpy_s(m_scanResult, "仅限局内扫描 (先进 raid 再点)");
m_scanPhase = 0; m_scanPhase = 0;
@ -482,7 +462,7 @@ void CFramework::ScanRotationOffset()
// Player 构造 [0x20, 0x700] 内的 8byte アライン済みポインタ // Player 构造 [0x20, 0x700] 内的 8byte アライン済みポインタ
for (uintptr_t mcOff = 0x20; mcOff <= 0x700; mcOff += 0x8) for (uintptr_t mcOff = 0x20; mcOff <= 0x700; mcOff += 0x8)
{ {
uintptr_t ptr = m.Read<uintptr_t>(local.m_address + mcOff); uintptr_t ptr = m.Read<uintptr_t>(localAddress + mcOff);
if (!ptr) if (!ptr)
continue; continue;
@ -524,7 +504,7 @@ void CFramework::ScanRotationOffset()
for (auto& c : s_cands) for (auto& c : s_cands)
{ {
uintptr_t ptr = m.Read<uintptr_t>(local.m_address + c.mcOff); uintptr_t ptr = m.Read<uintptr_t>(localAddress + c.mcOff);
if (!ptr) if (!ptr)
continue; continue;

View File

@ -1,4 +1,4 @@
#include "Framework.h" #include "Framework.h"
#include <cmath> #include <cmath>
// 只写入 dump 明确的对象字段;后座与故障也只使用本轮已确认的链路,弹道追踪暂不写。 // 只写入 dump 明确的对象字段;后座与故障也只使用本轮已确认的链路,弹道追踪暂不写。
@ -196,21 +196,24 @@ namespace
void CFramework::UpdateFeatures() void CFramework::UpdateFeatures()
{ {
if (!tarkov->IsInRaid() || !IsUsableAddress(tarkov->m_pLocalGameWorld)) const auto snapshot = GetDynamicSnapshot();
if (!snapshot || !snapshot->inRaid || !snapshot->local.IsValid())
{ {
local = {};
ResetFeatureOverrides(); ResetFeatureOverrides();
return; return;
} }
// Refresh MainPlayer from the current LocalGameWorld instead of trusting a stale list-thread cache. // The list thread publishes local player state atomically. Keep a render-thread-only
uintptr_t currentLocal = 0; // scratch copy for feature writers so no background thread can zero/change it mid-write.
if (!m.TryRead(tarkov->m_pLocalGameWorld + offset::MainPlayer, currentLocal) || local = snapshot->local;
!IsUsableAddress(currentLocal)) const uintptr_t currentLocal = local.m_address;
if (!IsUsableAddress(currentLocal))
{ {
local = {};
ResetFeatureOverrides(); ResetFeatureOverrides();
return; return;
} }
local.m_address = currentLocal;
if (g.g_InfStamina) if (g.g_InfStamina)

View File

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <algorithm>
#include "../Framework/ImGui/imgui.h" #include "../Framework/ImGui/imgui.h"
#include "../Framework/ImGui/imgui_impl_win32.h" #include "../Framework/ImGui/imgui_impl_win32.h"
#include "../Framework/ImGui/imgui_impl_dx11.h" #include "../Framework/ImGui/imgui_impl_dx11.h"
@ -10,6 +11,51 @@
#include "SDK/CExfil/CExfil.h" #include "SDK/CExfil/CExfil.h"
#include "SDK/CItem/CItem.h" #include "SDK/CItem/CItem.h"
#include <mutex> #include <mutex>
#include <memory>
#include <atomic>
#include <cstdint>
#include <utility>
struct GrenadeSnapshot
{
uintptr_t address{ 0 };
Vector3 position{};
};
struct DynamicWorldSnapshot
{
uint64_t generation{ 0 };
bool initialized{ false };
bool inRaid{ false };
bool cameraValid{ false };
uintptr_t gameWorld{ 0 };
uintptr_t localGameWorld{ 0 };
uintptr_t fpsCamera{ 0 };
Matrix viewMatrix{};
CPlayer local{};
std::vector<CPlayer> players{};
std::vector<GrenadeSnapshot> grenades{};
};
struct WorkerConfigSnapshot
{
bool needDynamic{ false };
bool wantRemotePlayers{ false };
bool wantHealth{ false };
bool wantSkeleton{ false };
bool wantGrenades{ false };
bool wantExfils{ false };
bool wantItems{ false };
float remoteMaxDistance{ 0.f };
};
struct StaticWorldSnapshot
{
uint64_t generation{ 0 };
std::vector<CExfil> exfils{};
std::vector<CItem> items{};
};
class CFramework class CFramework
{ {
@ -24,6 +70,7 @@ public:
void AimAll(); void AimAll();
void UpdateFeatures(); void UpdateFeatures();
void ResetFeatureOverrides(); void ResetFeatureOverrides();
void UpdateWorkerConfigFromUi();
private: private:
struct StaminaOverrideState struct StaminaOverrideState
{ {
@ -94,9 +141,43 @@ private:
void ResetFallDamageOverride(); void ResetFallDamageOverride();
void ResetSwayOverride(); void ResetSwayOverride();
CGame* tarkov = new CGame(); std::unique_ptr<CGame> tarkov{ std::make_unique<CGame>() };
// Render/features keep this as a render-thread-only scratch object. The list thread never mutates it.
CPlayer local; CPlayer local;
std::shared_ptr<const DynamicWorldSnapshot> m_dynamicSnapshot{ std::make_shared<DynamicWorldSnapshot>() };
std::shared_ptr<const StaticWorldSnapshot> m_staticSnapshot{ std::make_shared<StaticWorldSnapshot>() };
std::atomic_uint64_t m_dynamicGeneration{ 0 };
std::atomic_uint64_t m_staticGeneration{ 0 };
mutable std::mutex m_workerConfigMutex{};
WorkerConfigSnapshot m_workerConfig{};
WorkerConfigSnapshot GetWorkerConfig() const
{
std::scoped_lock lock(m_workerConfigMutex);
return m_workerConfig;
}
std::shared_ptr<const DynamicWorldSnapshot> GetDynamicSnapshot() const
{
return std::atomic_load(&m_dynamicSnapshot);
}
std::shared_ptr<const StaticWorldSnapshot> GetStaticSnapshot() const
{
return std::atomic_load(&m_staticSnapshot);
}
void PublishDynamicSnapshot(std::shared_ptr<const DynamicWorldSnapshot> snapshot)
{
std::atomic_store(&m_dynamicSnapshot, std::move(snapshot));
}
void PublishStaticSnapshot(std::shared_ptr<const StaticWorldSnapshot> snapshot)
{
std::atomic_store(&m_staticSnapshot, std::move(snapshot));
}
StaminaOverrideState m_staminaOverride{}; StaminaOverrideState m_staminaOverride{};
FallDamageOverrideState m_fallDamageOverride{}; FallDamageOverrideState m_fallDamageOverride{};
SwayOverrideState m_swayOverride{}; SwayOverrideState m_swayOverride{};
@ -104,26 +185,21 @@ private:
NoMalfunctionOverrideState m_noMalfunctionOverride{}; NoMalfunctionOverrideState m_noMalfunctionOverride{};
std::mutex m_mtxEntityList; // Compatibility accessors for menu/legacy call sites. New render/aim code consumes immutable snapshots directly.
std::mutex m_mtxExfilList; std::vector<CPlayer> GetEntityList() const
std::mutex m_mtxItemList; {
auto snapshot = GetDynamicSnapshot();
std::vector<CPlayer> m_vecEntityList; return snapshot ? snapshot->players : std::vector<CPlayer>{};
std::vector<CExfil> m_vecExfilList;
std::vector<CItem> m_vecItemList;
std::vector<uintptr_t> m_vecGrenadeList;
std::vector<CPlayer> GetEntityList() {
std::lock_guard<std::mutex> lock(m_mtxEntityList);
return m_vecEntityList;
} }
std::vector<CExfil> GetExfilList() { std::vector<CExfil> GetExfilList() const
std::lock_guard<std::mutex> lock(m_mtxExfilList); {
return m_vecExfilList; auto snapshot = GetStaticSnapshot();
return snapshot ? snapshot->exfils : std::vector<CExfil>{};
} }
std::vector<CItem> GetItemList() { std::vector<CItem> GetItemList() const
std::lock_guard<std::mutex> lock(m_mtxItemList); {
return m_vecItemList; auto snapshot = GetStaticSnapshot();
return snapshot ? snapshot->items : std::vector<CItem>{};
} }
// Colors // Colors
@ -153,10 +229,10 @@ private:
void GetESPInfo(const int& SpawnType, std::string& vOutStr, ImColor& vOutColor); void GetESPInfo(const int& SpawnType, std::string& vOutStr, ImColor& vOutColor);
// Aim.cpp // Aim.cpp
bool IsValidAimTarget(const int& SpawnType); bool IsValidAimTarget(const CPlayer& player);
Vector3 GetAimBone(CPlayer* pEntity); Vector3 GetAimBone(const CPlayer* pEntity);
void AimMouse(const Vector2& target, const Vector2& center); void AimMouse(const Vector2& target, const Vector2& center);
void AimMemory(const Vector3& target, const Vector3& localPos); void AimMemory(const Vector3& target, const Vector3& localPos, uintptr_t localAddress);
void ScanRotationOffset(); // 2阶段扫描: Player->MC 与 MC->Rotation 偏移 void ScanRotationOffset(); // 2阶段扫描: Player->MC 与 MC->Rotation 偏移
// Runtime feature backup state is declared above. // Runtime feature backup state is declared above.
@ -192,7 +268,7 @@ private:
return; return;
RectFilled(x, y, x + w, y + h, ImColor(0.f, 0.f, 0.f, 0.725f), 0.f, 0); RectFilled(x, y, x + w, y + h, ImColor(0.f, 0.f, 0.f, 0.725f), 0.f, 0);
RectFilled(x, y, x + w, y + ((h / float(v_max)) * (float)value), ImColor(min(510 * (v_max - value) / 100, 255), min(510 * value / 100, 255), 25, 255), 0.0f, 0); RectFilled(x, y, x + w, y + ((h / float(v_max)) * (float)value), ImColor((std::min)(510 * (v_max - value) / 100, 255), (std::min)(510 * value / 100, 255), 25, 255), 0.0f, 0);
} }
void Circle(ImVec2 pos, float fov_size, ImColor color) void Circle(ImVec2 pos, float fov_size, ImColor color)
{ {
@ -224,4 +300,4 @@ private:
void HealthBar(int x, int y, int w, int h, int value, int v_max, ImColor shadow, float global_alpha); void HealthBar(int x, int y, int w, int h, int value, int v_max, ImColor shadow, float global_alpha);
void String(const Vector2 pos, ImColor color, float alpha, const char* text); void String(const Vector2 pos, ImColor color, float alpha, const char* text);
void StringEx(Vector2 pos, ImColor shadow_color, float global_alpht, float font_size, const char* text); void StringEx(Vector2 pos, ImColor shadow_color, float global_alpht, float font_size, const char* text);
}; };

View File

@ -1,4 +1,4 @@
#include "Framework.h" #include "Framework.h"
#include "../Framework/Input/Input.h" #include "../Framework/Input/Input.h"
// ImGui::Combo/ImGui::List等で使う文字列群 // ImGui::Combo/ImGui::List等で使う文字列群
@ -13,6 +13,10 @@ void CFramework::RenderMenu()
{ {
// Setup // Setup
static int Index = 0; static int Index = 0;
const auto runtimeSnapshot = GetDynamicSnapshot();
const bool runtimeInRaid = runtimeSnapshot && runtimeSnapshot->inRaid;
const uintptr_t runtimeLocalAddress = runtimeSnapshot ? runtimeSnapshot->local.m_address : 0;
const std::size_t runtimeEntityCount = runtimeSnapshot ? runtimeSnapshot->players.size() : 0;
ImGui::SetNextWindowBgAlpha(0.975f); ImGui::SetNextWindowBgAlpha(0.975f);
ImGui::SetNextWindowSize(ImVec2(725.f, 450.f)); ImGui::SetNextWindowSize(ImVec2(725.f, 450.f));
@ -197,9 +201,9 @@ void CFramework::RenderMenu()
// 数据链路状态: 哪一环断了看这里 // 数据链路状态: 哪一环断了看这里
ImGui::Text("自瞄状态"); ImGui::Text("自瞄状态");
ImGui::Text("局内 : %s", tarkov->IsInRaid() ? "是" : "否"); ImGui::Text("局内 : %s", runtimeInRaid ? "是" : "否");
ImGui::Text("本地 : 0x%llX", (unsigned long long)local.m_address); ImGui::Text("本地 : 0x%llX", (unsigned long long)runtimeLocalAddress);
ImGui::Text("实体 : %zu", GetEntityList().size()); ImGui::Text("实体 : %zu", runtimeEntityCount);
ImGui::Text("扫描 : %d 实体 / %d 血量 / %d 跳过", m_diagTotal, m_diagHealth, m_diagSkipped); ImGui::Text("扫描 : %d 实体 / %d 血量 / %d 跳过", m_diagTotal, m_diagHealth, m_diagSkipped);
ImGui::Text("目标 : %s (距 %0.fpx)", m_diagBestAddr ? "锁定" : "无", m_diagBestDist); ImGui::Text("目标 : %s (距 %0.fpx)", m_diagBestAddr ? "锁定" : "无", m_diagBestDist);
{ {
@ -214,9 +218,9 @@ void CFramework::RenderMenu()
ImGui::Separator(); ImGui::Separator();
ImGui::Spacing(); ImGui::Spacing();
if (tarkov->IsInRaid() && local.m_address != 0) if (runtimeInRaid && runtimeLocalAddress != 0)
{ {
uintptr_t mc = m.Read<uintptr_t>(local.m_address + (uintptr_t)g.g_AimMcOffset); uintptr_t mc = m.Read<uintptr_t>(runtimeLocalAddress + (uintptr_t)g.g_AimMcOffset);
if (mc) if (mc)
{ {
Vector2 rot = m.Read<Vector2>(mc + (uintptr_t)g.g_AimRotOffset); Vector2 rot = m.Read<Vector2>(mc + (uintptr_t)g.g_AimRotOffset);
@ -316,8 +320,8 @@ void CFramework::RenderMenu()
ImGui::Text("功能状态"); ImGui::Text("功能状态");
ImGui::Separator(); ImGui::Separator();
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("局内:%s", tarkov->IsInRaid() ? "是" : "否"); ImGui::Text("局内:%s", runtimeInRaid ? "是" : "否");
ImGui::Text("本地:0x%llX", (unsigned long long)local.m_address); ImGui::Text("本地:0x%llX", (unsigned long long)runtimeLocalAddress);
ImGui::TextDisabled("关闭开关或离开局内会恢复原值"); ImGui::TextDisabled("关闭开关或离开局内会恢复原值");
ImGui::TextDisabled("非法/不完整指针链不会写入"); ImGui::TextDisabled("非法/不完整指针链不会写入");

View File

@ -67,7 +67,7 @@ void CFramework::RectFilled(int x0, int y0, int x1, int y1, ImColor color, float
void CFramework::HealthBar(int x, int y, int w, int h, int value, int v_max, ImColor shadow, float global_alpha) void CFramework::HealthBar(int x, int y, int w, int h, int value, int v_max, ImColor shadow, float global_alpha)
{ {
RectFilled(x - 1, y + 1, x + w + 1, y + h - 1, shadow); RectFilled(x - 1, y + 1, x + w + 1, y + h - 1, shadow);
RectFilled(x, y, x + w, y + ((h / float(v_max)) * (float)value), ImColor(min(510 * (v_max - value) / 100, 255), min(510 * value / 100, 255), 25, (int)(255 * global_alpha)), global_alpha); RectFilled(x, y, x + w, y + ((h / float(v_max)) * (float)value), ImColor((std::min)(510 * (v_max - value) / 100, 255), (std::min)(510 * value / 100, 255), 25, (int)(255 * global_alpha)), global_alpha);
} }
void CFramework::String(const Vector2 pos, ImColor color, float alpha, const char* text) void CFramework::String(const Vector2 pos, ImColor color, float alpha, const char* text)

View File

@ -1,45 +1,56 @@
#include "CExfil.h" #include "CExfil.h"
#include <algorithm>
bool CExfil::GetAddress(uintptr_t& address) bool CExfil::GetAddress(uintptr_t& address)
{ {
ptr = address; ptr = address;
return ptr == NULL ? false : true; return Memory::IsUserAddress(ptr);
} }
int CExfil::GetStatus() int CExfil::GetStatus()
{ {
return m.Read<int>(ptr + 0xC8); int status = CLOSE;
m.TryRead(ptr + offset::Exfil::Status, status);
return status;
} }
std::string CExfil::GetName() std::string CExfil::GetName()
{ {
// Name uintptr_t settings = 0;
uintptr_t eSetting = m.Read<uintptr_t>(ptr + 0x78); uintptr_t namePtr = 0;
uintptr_t NamePtr = m.Read<uintptr_t>(eSetting + 0x18); int length = 0;
if (!m.TryRead(ptr + offset::Exfil::Settings, settings) || !Memory::IsUserAddress(settings) ||
!m.TryRead(settings + offset::Exfil::Name, namePtr) || !Memory::IsUserAddress(namePtr) ||
!m.TryRead(namePtr + offset::ManagedString::Length, length) || length <= 0)
{
return {};
}
if (!NamePtr) length = (std::min)(length, 127);
return std::string(); std::string name;
name.reserve(static_cast<std::size_t>(length));
char pName[64]{}; for (int i = 0; i < length; ++i)
int length = m.Read<int>(NamePtr + 0x10); {
wchar_t ch = 0;
for (int i = 0; i < length; i++) if (!m.TryRead(namePtr + offset::ManagedString::Data + static_cast<uintptr_t>(i) * offset::ManagedString::CharStride, ch))
pName[i] = m.Read<char>(NamePtr + 0x14 + (i * 0x2)); break;
if (ch >= 0x20 && ch <= 0x7E)
return pName; name.push_back(static_cast<char>(ch));
else if (ch != 0)
name.push_back('?');
}
return name;
} }
bool CExfil::Update() bool CExfil::Update()
{ {
if (this->GetStatus() == CLOSE) m_pExfilStatus = GetStatus();
return false; if (m_pExfilStatus == CLOSE)
return false;
// Position const uintptr_t transform = m.ReadChain(ptr,
uintptr_t TransformInternal = m.ReadChain(ptr, { 0x10, 0x30, 0x30, 0x8, 0x28, 0x10 }); { offset::UnityComponent::Step0, offset::UnityComponent::Step1, offset::UnityComponent::Step2,
m_pVecLocation = GetTransformPosition(TransformInternal); offset::UnityComponent::Step3, offset::UnityComponent::Step4, offset::UnityComponent::Transform });
m_pVecLocation = GetTransformPosition(transform);
if (Vec3_Empty(m_pVecLocation)) return !Vec3_Empty(m_pVecLocation);
return false; }
return true;
}

View File

@ -1,111 +1,47 @@
#include "CItem.h" #include "CItem.h"
// Example
struct C_EFT_ItemData struct C_EFT_ItemData
{ {
int item_price; int item_price;
std::string item_name; std::string item_name;
std::string item_id; std::string item_id;
}; };
// std::map std::vector<C_EFT_ItemData> C_LootList =
std::vector<C_EFT_ItemData>C_LootList =
{ {
{ { 2500000, "Red Rebel", "5c0126f40db834002a125382" },
2500000, { 20000000, "Labs keycard (Red)", "5c1d0efb86f7744baf2e7b7b" },
"Red Rebel", { 20000000, "Labs keycard (Blue)", "1d0c5f86f7744bb2683cf0" },
"5c0126f40db834002a125382" { 20000000, "Labs keycard (Green)", "1d0dc586f7744baf2e7b79" },
}, { 20000000, "Labs keycard (Violet)", "1e495a86f7743109743dfb" },
{ { 20000000, "Labs keycard (Black)", "1d0f4986f7744bb01837fa" },
20000000, { 20000000, "Labs keycard (Yellow)", "1d0d6d86f7744bb2683e1f" },
"Labs keycard (Red)", { 65000, "0.2 BTC", "faff1d86f7746c51718c9c" }
"5c1d0efb86f7744baf2e7b7b"
},
{
20000000,
" Labs keycard (Blue)",
"1d0c5f86f7744bb2683cf0"
},
{
20000000,
"Labs keycard (Green)",
"1d0dc586f7744baf2e7b79"
},
{
20000000,
"Labs keycard (Vioret)",
"1e495a86f7743109743dfb"
},
{
20000000,
"Labs keycard (Black)",
"1d0f4986f7744bb01837fa"
},
{
20000000,
"Labs keycard (Yellow)",
"1d0d6d86f7744bb2683e1f"
},
{
65000,
"0.2 BTC",
"faff1d86f7746c51718c9c"
}
}; };
bool CItem::GetAddress(uintptr_t& ptr) bool CItem::GetAddress(uintptr_t& ptr)
{ {
address = ptr; address = ptr;
return address == NULL ? false : true; return Memory::IsUserAddress(address);
} }
bool CItem::Update() bool CItem::Update()
{ {
// Position m_bIsCorpse = false;
uintptr_t TransformInternal = m.ReadChain(address, { 0x10, 0x30, 0x30, 0x8, 0x28, 0x10 }); m_vecOrigin = {};
m_vecOrigin = GetTransformPosition(TransformInternal);
if (Vec3_Empty(m_vecOrigin)) const uintptr_t transform = m.ReadChain(address,
return false; { offset::UnityComponent::Step0, offset::UnityComponent::Step1, offset::UnityComponent::Step2,
offset::UnityComponent::Step3, offset::UnityComponent::Step4, offset::UnityComponent::Transform });
// ClassName m_vecOrigin = GetTransformPosition(transform);
uintptr_t nameAddr = m.ReadChain(address, { 0x0, 0x0, 0x48 }); if (Vec3_Empty(m_vecOrigin))
std::string className = m.ReadString(nameAddr, 64); return false;
// コンテナ/死体ではなかったら // Keep the legacy class-name chain from the original project, but validate every link.
if (className.compare("ObservedCorpse") && className.compare("Corpse")) const uintptr_t nameAddr = m.ReadChain(address, { 0x0, 0x0, 0x48 });
{ const std::string className = m.ReadString(nameAddr, 64);
/* if (className == "ObservedCorpse" || className == "Corpse")
// ID m_bIsCorpse = true;
char ItemID[64]{};
uintptr_t BsgId = m.ReadChain(address, { 0x10, 0x28, 0xB0, 0x40, 0x50 });
// 要改善 return true;
const int length = m.Read<int>(BsgId + 0x10); }
for (int j = 0; j < length; j++)
ItemID[j] = m.Read<char>(BsgId + 0x18 + (j * 0x2));
// FUCKING SIMPLE EXAMPLE
std::string temp = ItemID;
if (!temp.empty())
{
for (auto& list_index : C_LootList)
{
if (list_index.item_id.find(temp) != std::string::npos)
{
m_iPrice = list_index.item_price;
m_CName = list_index.item_name;
}
}
}
*/
}
else
{
m_bIsCorpse = true;
}
return true;
}

View File

@ -1,52 +1,78 @@
#include "CPlayer.h" #include "CPlayer.h"
#include <cmath>
std::vector<int> PlayerBoneList{ BONE_PELVIS, BONE_LEFT_THIGH, BONE_LEFT_FOOT, BONE_RIGHT_THIGH, BONE_RIGHT_FOOT, BONE_LEFT_FOREARM, BONE_LEFT_PALM, BONE_RIGHT_FOREARM, BONE_RIGHT_PALM }; std::vector<int> PlayerBoneList = {
BONE_PELVIS, BONE_LEFT_THIGH, BONE_LEFT_FOOT, BONE_RIGHT_THIGH, BONE_RIGHT_FOOT,
BONE_LEFT_FOREARM, BONE_LEFT_PALM, BONE_RIGHT_FOREARM, BONE_RIGHT_PALM
};
namespace namespace
{ {
uintptr_t ReadBifacialOriginal(uintptr_t playerBones, uintptr_t fieldOffset) uintptr_t ReadBifacialOriginal(uintptr_t playerBones, uintptr_t fieldOffset)
{ {
const uintptr_t bifacialTransform = m.Read<uintptr_t>(playerBones + fieldOffset); uintptr_t bifacialTransform = 0;
return bifacialTransform uintptr_t original = 0;
? m.Read<uintptr_t>(bifacialTransform + offset::BifacialTransform::Original) if (!Memory::IsUserAddress(playerBones) ||
: 0; !m.TryRead(playerBones + fieldOffset, bifacialTransform) || !Memory::IsUserAddress(bifacialTransform) ||
!m.TryRead(bifacialTransform + offset::BifacialTransform::Original, original) || !Memory::IsUserAddress(original))
{
return 0;
}
return original;
} }
uintptr_t ReadTransformArrayElement(uintptr_t array, int index) uintptr_t ReadTransformArrayElement(uintptr_t array, int index)
{ {
if (!array || index < 0 || index > 1) if (!Memory::IsUserAddress(array) || index < 0 || index > 1)
return 0; return 0;
return m.Read<uintptr_t>(array + offset::PlayerBones::TransformArrayData + static_cast<uintptr_t>(index) * sizeof(uintptr_t)); uintptr_t transform = 0;
if (!m.TryRead(array + offset::PlayerBones::TransformArrayData + static_cast<uintptr_t>(index) * sizeof(uintptr_t), transform))
return 0;
return Memory::IsUserAddress(transform) ? transform : 0;
} }
Vector3 ReadTransformPositionWithFallback(uintptr_t transform) Vector3 ReadTransformPositionWithFallback(uintptr_t transform)
{ {
if (!transform) if (!Memory::IsUserAddress(transform))
return {}; return {};
Vector3 position = GetTransformPosition(transform); Vector3 position = GetTransformPosition(transform);
if (!Vec3_Empty(position)) if (!Vec3_Empty(position))
return position; return position;
// Keep compatibility with the previous Transform wrapper layout. // Compatibility with older Transform wrappers used by the original project.
uintptr_t wrappedTransform = m.Read<uintptr_t>(transform + 0x10); uintptr_t wrappedTransform = 0;
if (!wrappedTransform || wrappedTransform == transform) if (!m.TryRead(transform + 0x10, wrappedTransform) || !Memory::IsUserAddress(wrappedTransform) || wrappedTransform == transform)
return {}; return {};
return GetTransformPosition(wrappedTransform); return GetTransformPosition(wrappedTransform);
} }
bool LooksLikeEftPlayer(uintptr_t address)
{
uintptr_t profile = 0;
uintptr_t info = 0;
uintptr_t bones = 0;
return m.TryRead(address + offset::Player::pProfile, profile) && Memory::IsUserAddress(profile) &&
m.TryRead(profile + offset::Profile::pInfoClass, info) && Memory::IsUserAddress(info) &&
m.TryRead(address + offset::Player::pPlayerBones, bones) && Memory::IsUserAddress(bones);
}
bool LooksLikeObservedPlayer(uintptr_t address)
{
uintptr_t bones = 0;
uintptr_t body = 0;
uintptr_t transform = 0;
return m.TryRead(address + offset::ObservedPlayer::PlayerBones, bones) && Memory::IsUserAddress(bones) &&
m.TryRead(address + offset::ObservedPlayer::PlayerBody, body) && Memory::IsUserAddress(body) &&
m.TryRead(address + offset::ObservedPlayer::Transform, transform) && Memory::IsUserAddress(transform);
}
} }
bool CPlayer::Update() bool CPlayer::UpdateStatic(CPlayerKind hint)
{
m_vecOrigin = GetBonePosition(BONE_ORIGIN);
return !Vec3_Empty(m_vecOrigin);
}
void CPlayer::UpdateStatic()
{ {
m_kind = CPlayerKind::Unknown;
m_pHealthController = 0; m_pHealthController = 0;
m_pProfile = 0; m_pProfile = 0;
m_pInfo = 0; m_pInfo = 0;
@ -54,102 +80,169 @@ void CPlayer::UpdateStatic()
m_pPlayerBody = 0; m_pPlayerBody = 0;
m_pPlayerBones = 0; m_pPlayerBones = 0;
m_pWeaponAnimation = 0; m_pWeaponAnimation = 0;
m_pObservedTransform = 0;
m_iSpawnType = 0; m_iSpawnType = 0;
m_iSide = 0;
m_bSpawnTypeValid = false;
m_bHealthValid = false;
m_bIsAiming = false;
m_pBoneArray = {}; m_pBoneArray = {};
m_vecBoneList.clear(); m_vecBoneList.clear();
if (!m_address) if (!Memory::IsUserAddress(m_address))
return; return false;
// EFT.Player fields from dump.txt. if (hint == CPlayerKind::EftPlayer || (hint == CPlayerKind::Unknown && LooksLikeEftPlayer(m_address)))
m_pHealthController = m.Read<uintptr_t>(m_address + offset::Player::pHealthController); m_kind = CPlayerKind::EftPlayer;
m_pWeaponAnimation = m.Read<uintptr_t>(m_address + offset::Player::pWeaponAnimation); else if (hint == CPlayerKind::ObservedPlayer || (hint == CPlayerKind::Unknown && LooksLikeObservedPlayer(m_address)))
m_kind = CPlayerKind::ObservedPlayer;
else
return false;
m_pPlayerBody = m.Read<uintptr_t>(m_address + offset::Player::pPlayerBody); if (m_kind == CPlayerKind::EftPlayer)
if (m_pPlayerBody)
m_pPlayerBones = m.Read<uintptr_t>(m_pPlayerBody + offset::PlayerBody::PlayerBones);
m_pProfile = m.Read<uintptr_t>(m_address + offset::Player::pProfile);
if (m_pProfile)
{ {
m_pInfo = m.Read<uintptr_t>(m_pProfile + offset::Profile::pInfoClass); m.TryRead(m_address + offset::Player::pHealthController, m_pHealthController);
if (m_pInfo) m.TryRead(m_address + offset::Player::pWeaponAnimation, m_pWeaponAnimation);
m.TryRead(m_address + offset::Player::pPlayerBody, m_pPlayerBody);
// Prefer the direct Player.PlayerBones field from the current dump; keep PlayerBody as a fallback.
m.TryRead(m_address + offset::Player::pPlayerBones, m_pPlayerBones);
if (!Memory::IsUserAddress(m_pPlayerBones) && Memory::IsUserAddress(m_pPlayerBody))
m.TryRead(m_pPlayerBody + offset::PlayerBody::PlayerBones, m_pPlayerBones);
if (m.TryRead(m_address + offset::Player::pProfile, m_pProfile) && Memory::IsUserAddress(m_pProfile) &&
m.TryRead(m_pProfile + offset::Profile::pInfoClass, m_pInfo) && Memory::IsUserAddress(m_pInfo))
{ {
m_pSetting = m.Read<uintptr_t>(m_pInfo + offset::InfoClass::profileInfoSettingsClass); m.TryRead(m_pInfo + offset::InfoClass::eplayerSide_0, m_iSide);
if (m_pSetting) if (m.TryRead(m_pInfo + offset::InfoClass::profileInfoSettingsClass, m_pSetting) && Memory::IsUserAddress(m_pSetting))
m_iSpawnType = m.Read<int>(m_pSetting + 0x10); // ProfileInfoSettingsClass.Role {
if (m.TryRead(m_pSetting + offset::ProfileInfoSettings::Role, m_iSpawnType))
m_bSpawnTypeValid = true;
}
} }
} }
else
{
m.TryRead(m_address + offset::ObservedPlayer::PlayerBones, m_pPlayerBones);
m.TryRead(m_address + offset::ObservedPlayer::PlayerBody, m_pPlayerBody);
m.TryRead(m_address + offset::ObservedPlayer::Transform, m_pObservedTransform);
m.TryRead(m_address + offset::ObservedPlayer::Side, m_iSide);
// ObservedPlayerView does not expose ProfileInfoSettingsClass.Role in this dump.
// Keep spawn type invalid instead of reinterpreting an unrelated field as Role.
m_bSpawnTypeValid = false;
}
// PlayerBody +0x30 = SkeletonRootJoint; Skeleton +0x30 = values List; // Skeleton.values fallback. This is optional; direct PlayerBones fields are preferred.
// List +0x10 = Transform[] data pointer, as described by dump.txt. if (Memory::IsUserAddress(m_pPlayerBody))
const uintptr_t boneMatrix = m.ReadChain(m_address, { offset::Player::pPlayerBody, offset::PlayerBody::SkeletonRootJoint, 0x30, 0x10 }); {
if (boneMatrix) const uintptr_t boneMatrix = m.ReadChain(m_pPlayerBody, { offset::PlayerBody::SkeletonRootJoint, offset::Skeleton::Values, offset::Skeleton::ValuesArray });
m_pBoneArray = m.Read<CBoneArray>(boneMatrix + 0x20); if (Memory::IsUserAddress(boneMatrix))
m.TryRead(boneMatrix + offset::ManagedArray::Data, m_pBoneArray);
}
return Memory::IsUserAddress(m_pPlayerBones) || Memory::IsUserAddress(m_pObservedTransform);
}
bool CPlayer::Update()
{
if (!IsValid())
return false;
if (m_kind == CPlayerKind::ObservedPlayer && Memory::IsUserAddress(m_pObservedTransform))
m_vecOrigin = GetTransformPosition(m_pObservedTransform);
else
m_vecOrigin = GetBonePosition(BONE_ORIGIN);
if (Vec3_Empty(m_vecOrigin))
m_vecOrigin = GetBonePosition(BONE_ORIGIN);
m_bIsAiming = false;
if (m_kind == CPlayerKind::EftPlayer && Memory::IsUserAddress(m_pWeaponAnimation))
m.TryRead(m_pWeaponAnimation + offset::WeaponAnimations::isAiming, m_bIsAiming);
return !Vec3_Empty(m_vecOrigin);
} }
void CPlayer::UpdateHealth() void CPlayer::UpdateHealth()
{ {
m_fHealth = 0.f; m_fHealth = 0.f;
m_fHealthMax = 0.f; m_fHealthMax = 0.f;
m_bHealthValid = false;
if (!m_address) if (m_kind != CPlayerKind::EftPlayer || !Memory::IsUserAddress(m_address))
return; return;
const uintptr_t healthController = m_pHealthController != 0 uintptr_t healthController = m_pHealthController;
? m_pHealthController if (!Memory::IsUserAddress(healthController) &&
: m.Read<uintptr_t>(m_address + offset::Player::pHealthController); (!m.TryRead(m_address + offset::Player::pHealthController, healthController) || !Memory::IsUserAddress(healthController)))
if (!healthController) {
return; return;
}
// GClass3009<T>.Dictionary_0_1 contains BodyPartState<T> entries. uintptr_t bodyParts = 0;
const uintptr_t bodyParts = m.Read<uintptr_t>(healthController + offset::HealthController::BodyParts); uintptr_t entries = 0;
if (!bodyParts) int entryCount = 0;
return; if (!m.TryRead(healthController + offset::HealthController::BodyParts, bodyParts) || !Memory::IsUserAddress(bodyParts) ||
!m.TryRead(bodyParts + offset::HealthController::DictionaryEntries, entries) || !Memory::IsUserAddress(entries) ||
const uintptr_t entries = m.Read<uintptr_t>(bodyParts + offset::HealthController::DictionaryEntries); !m.TryRead(bodyParts + offset::HealthController::DictionaryCount, entryCount) || entryCount <= 0 || entryCount > 64)
const int entryCount = m.Read<int>(bodyParts + offset::HealthController::DictionaryCount); {
if (!entries || entryCount <= 0 || entryCount > 64)
return; return;
}
int validParts = 0;
for (int i = 0; i < entryCount; ++i) for (int i = 0; i < entryCount; ++i)
{ {
const uintptr_t entry = entries + offset::HealthController::ArrayData const uintptr_t entry = entries + offset::HealthController::ArrayData
+ static_cast<uintptr_t>(i) * offset::HealthController::EntryStride; + static_cast<uintptr_t>(i) * offset::HealthController::EntryStride;
const uintptr_t bodyPartState = m.Read<uintptr_t>(entry + offset::HealthController::EntryValue);
if (!bodyPartState) uintptr_t bodyPartState = 0;
uintptr_t healthValue = 0;
float current = 0.f;
float maximum = 0.f;
if (!m.TryRead(entry + offset::HealthController::EntryValue, bodyPartState) || !Memory::IsUserAddress(bodyPartState) ||
!m.TryRead(bodyPartState + offset::HealthController::BodyPartHealth, healthValue) || !Memory::IsUserAddress(healthValue) ||
!m.TryRead(healthValue + offset::HealthController::HealthValue + offset::HealthController::Current, current) ||
!m.TryRead(healthValue + offset::HealthController::HealthValue + offset::HealthController::Maximum, maximum))
{
continue;
}
if (!std::isfinite(current) || !std::isfinite(maximum) || maximum <= 0.f || maximum > 10000.f)
continue; continue;
const uintptr_t healthValue = m.Read<uintptr_t>(bodyPartState + offset::HealthController::BodyPartHealth); m_fHealth += current;
if (!healthValue) m_fHealthMax += maximum;
continue; ++validParts;
m_fHealth += m.Read<float>(healthValue + offset::HealthController::HealthValue + offset::HealthController::Current);
m_fHealthMax += m.Read<float>(healthValue + offset::HealthController::HealthValue + offset::HealthController::Maximum);
} }
m_bHealthValid = validParts > 0 && m_fHealthMax > 0.f;
} }
void CPlayer::UpdateBone() void CPlayer::UpdateBone(bool buildSkeleton)
{ {
// Refresh the direct PlayerBody -> PlayerBones link before reading the selected aim bone. m_vecHeadOrigin = {};
// The list thread is intentionally slow, while animation/weapon objects can be replaced sooner. m_vecNeckOrigin = {};
if (m_address) m_vecSpineOrigin = {};
{ m_vecBoneList.clear();
uintptr_t playerBody = 0;
uintptr_t playerBones = 0; if (!IsValid())
if (m.TryRead(m_address + offset::Player::pPlayerBody, playerBody) && playerBody && return;
m.TryRead(playerBody + offset::PlayerBody::PlayerBones, playerBones) && playerBones)
{ // Animation objects can be replaced. Refresh only the cheap direct bone pointer here.
m_pPlayerBody = playerBody; uintptr_t refreshedBones = 0;
m_pPlayerBones = playerBones; if (m_kind == CPlayerKind::EftPlayer)
} m.TryRead(m_address + offset::Player::pPlayerBones, refreshedBones);
} else
m.TryRead(m_address + offset::ObservedPlayer::PlayerBones, refreshedBones);
if (Memory::IsUserAddress(refreshedBones))
m_pPlayerBones = refreshedBones;
m_vecHeadOrigin = GetBonePosition(BONE_HEAD); m_vecHeadOrigin = GetBonePosition(BONE_HEAD);
m_vecNeckOrigin = GetBonePosition(BONE_NECK); m_vecNeckOrigin = GetBonePosition(BONE_NECK);
m_vecBoneList.clear(); m_vecSpineOrigin = GetBonePosition(BONE_SPINE_3);
if (!g.g_ESP_Skeleton) if (!buildSkeleton)
return; return;
m_vecBoneList.reserve(PlayerBoneList.size()); m_vecBoneList.reserve(PlayerBoneList.size());
@ -161,32 +254,26 @@ void CPlayer::UpdateBone()
m_vecBoneList.clear(); m_vecBoneList.clear();
return; return;
} }
m_vecBoneList.push_back(bone); m_vecBoneList.push_back(bone);
} }
} }
bool CPlayer::IsAiming() Vector3 CPlayer::GetBonePosition(const int BoneId) const
{
return m_pWeaponAnimation != 0 && m.Read<bool>(m_pWeaponAnimation + offset::WeaponAnimations::isAiming);
}
Vector3 CPlayer::GetBonePosition(const int BoneId)
{ {
constexpr int kBoneCount = static_cast<int>(sizeof(m_pBoneArray.address) / sizeof(m_pBoneArray.address[0])); constexpr int kBoneCount = static_cast<int>(sizeof(m_pBoneArray.address) / sizeof(m_pBoneArray.address[0]));
if (BoneId < 0 || BoneId >= kBoneCount) if (BoneId < 0 || BoneId >= kBoneCount)
return {}; return {};
uintptr_t transform = 0; uintptr_t transform = 0;
if (m_pPlayerBones) if (Memory::IsUserAddress(m_pPlayerBones))
{ {
switch (BoneId) switch (BoneId)
{ {
case BONE_ORIGIN: case BONE_ORIGIN:
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::RootJoint); m.TryRead(m_pPlayerBones + offset::PlayerBones::RootJoint, transform);
break; break;
case BONE_NECK: case BONE_NECK:
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::Neck); m.TryRead(m_pPlayerBones + offset::PlayerBones::Neck, transform);
break; break;
case BONE_HEAD: case BONE_HEAD:
transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::Head); transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::Head);
@ -201,16 +288,24 @@ Vector3 CPlayer::GetBonePosition(const int BoneId)
transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::RightThigh); transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::RightThigh);
break; break;
case BONE_LEFT_FOREARM: case BONE_LEFT_FOREARM:
transform = ReadTransformArrayElement(m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::Forearms), 0); {
uintptr_t array = 0;
m.TryRead(m_pPlayerBones + offset::PlayerBones::Forearms, array);
transform = ReadTransformArrayElement(array, 0);
break; break;
}
case BONE_RIGHT_FOREARM: case BONE_RIGHT_FOREARM:
transform = ReadTransformArrayElement(m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::Forearms), 1); {
uintptr_t array = 0;
m.TryRead(m_pPlayerBones + offset::PlayerBones::Forearms, array);
transform = ReadTransformArrayElement(array, 1);
break; break;
}
case BONE_LEFT_PALM: case BONE_LEFT_PALM:
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::LeftPalm); m.TryRead(m_pPlayerBones + offset::PlayerBones::LeftPalm, transform);
break; break;
case BONE_RIGHT_PALM: case BONE_RIGHT_PALM:
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::RightPalm); m.TryRead(m_pPlayerBones + offset::PlayerBones::RightPalm, transform);
break; break;
case BONE_SPINE_3: case BONE_SPINE_3:
transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::Ribcage); transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::Ribcage);
@ -220,17 +315,15 @@ Vector3 CPlayer::GetBonePosition(const int BoneId)
} }
} }
// Feet and any unmapped legacy IDs fall back to the current Skeleton.values array. if (!Memory::IsUserAddress(transform))
// The array elements are Transform references; do not dereference +0x10 here. transform = static_cast<uintptr_t>(m_pBoneArray.address[BoneId]);
if (!transform)
transform = m_pBoneArray.address[BoneId];
const Vector3 position = ReadTransformPositionWithFallback(transform); const Vector3 position = ReadTransformPositionWithFallback(transform);
if (!Vec3_Empty(position)) if (!Vec3_Empty(position))
return position; return position;
const uintptr_t legacyTransform = m_pBoneArray.address[BoneId]; const uintptr_t legacyTransform = static_cast<uintptr_t>(m_pBoneArray.address[BoneId]);
if (!legacyTransform || legacyTransform == transform) if (!Memory::IsUserAddress(legacyTransform) || legacyTransform == transform)
return {}; return {};
return ReadTransformPositionWithFallback(legacyTransform); return ReadTransformPositionWithFallback(legacyTransform);

View File

@ -1,40 +1,56 @@
#pragma once #pragma once
#include "../GameSDK/GameSDK.h" #include "../GameSDK/GameSDK.h"
struct CBoneArray { struct CBoneArray
uint64_t address[150]{}; {
uint64_t address[150]{};
};
enum class CPlayerKind : unsigned char
{
Unknown = 0,
EftPlayer,
ObservedPlayer
}; };
class CPlayer class CPlayer
{ {
public: public:
uintptr_t m_address{ 0 }; uintptr_t m_address{ 0 };
uintptr_t m_pProfile{ 0 }; CPlayerKind m_kind{ CPlayerKind::Unknown };
uintptr_t m_pInfo{ 0 }; uintptr_t m_pProfile{ 0 };
uintptr_t m_pSetting{ 0 }; uintptr_t m_pInfo{ 0 };
uintptr_t m_pPlayerBody{ 0 }; uintptr_t m_pSetting{ 0 };
uintptr_t m_pPlayerBones{ 0 }; uintptr_t m_pPlayerBody{ 0 };
uintptr_t m_pHealthController{ 0 }; uintptr_t m_pPlayerBones{ 0 };
uintptr_t m_pWeaponAnimation{ 0 }; uintptr_t m_pHealthController{ 0 };
uintptr_t m_pWeaponAnimation{ 0 };
uintptr_t m_pObservedTransform{ 0 };
// データ float m_fHealth{ 0.f };
float m_fHealth{ 0.f }; float m_fHealthMax{ 0.f };
float m_fHealthMax{ 0.f }; int m_iSpawnType{ 0 };
int m_iSpawnType{ 0 }; int m_iSide{ 0 };
Vector3 m_vecOrigin{ 0.f, 0.f, 0.f}; bool m_bSpawnTypeValid{ false };
Vector3 m_vecHeadOrigin{ 0.f, 0.f, 0.f }; bool m_bHealthValid{ false };
Vector3 m_vecNeckOrigin{ 0.f, 0.f, 0.f }; bool m_bIsAiming{ false };
CBoneArray m_pBoneArray{};
std::vector<Vector3> m_vecBoneList{};
// Functions Vector3 m_vecOrigin{};
bool Update(); Vector3 m_vecHeadOrigin{};
void UpdateStatic(); Vector3 m_vecNeckOrigin{};
void UpdateBone(); Vector3 m_vecSpineOrigin{};
void UpdateHealth(); CBoneArray m_pBoneArray{};
std::vector<Vector3> m_vecBoneList{};
bool IsAiming(); bool Update();
bool UpdateStatic(CPlayerKind hint = CPlayerKind::Unknown);
void UpdateBone(bool buildSkeleton = false);
void UpdateHealth();
Vector3 GetBonePosition(const int BoneId); bool IsAiming() const { return m_bIsAiming; }
}; bool IsObserved() const { return m_kind == CPlayerKind::ObservedPlayer; }
bool IsValid() const { return m_address != 0 && m_kind != CPlayerKind::Unknown; }
Vector3 GetBonePosition(const int BoneId) const;
};

View File

@ -1,8 +1,10 @@
#include "GameSDK.h" #include "GameSDK.h"
#include <cmath>
#include <cstring>
bool Vec2_Empty(const Vector2& value) bool Vec2_Empty(const Vector2& value)
{ {
return value == Vector2(); return value == Vector2();
} }
bool Vec3_Empty(const Vector3& value) bool Vec3_Empty(const Vector3& value)
@ -10,232 +12,325 @@ bool Vec3_Empty(const Vector3& value)
return value == Vector3(); return value == Vector3();
} }
float GetDistance(Vector3 value1, Vector3 value2) float GetDistance(const Vector3& value1, const Vector3& value2)
{ {
float num = value1.x - value2.x; const float x = value1.x - value2.x;
float num2 = value1.y - value2.y; const float y = value1.y - value2.y;
float num3 = value1.z - value2.z; const float z = value1.z - value2.z;
return std::sqrt(x * x + y * y + z * z);
return sqrt(num * num + num2 * num2 + num3 * num3);
} }
Vector3 GetTransformPosition(uintptr_t& transform) Vector3 GetTransformPosition(uintptr_t transform)
{ {
if (!transform) if (!Memory::IsUserAddress(transform))
return {}; return {};
__m128 result{}; __m128 result{};
const __m128 mulVec0 = { -2.000f, 2.000f, -2.000f, 0.000f };
const __m128 mulVec1 = { 2.000f, -2.000f, -2.000f, 0.000f };
const __m128 mulVec2 = { -2.000f, -2.000f, 2.000f, 0.000f };
const __m128 mulVec0 = { -2.000, 2.000, -2.000, 0.000 }; TransformAccessReadOnly access{};
const __m128 mulVec1 = { 2.000, -2.000, -2.000, 0.000 }; unsigned int index = 0;
const __m128 mulVec2 = { -2.000, -2.000, 2.000, 0.000 }; if (!m.TryRead(transform + offset::UnityTransform::Access, access) ||
!m.TryRead(transform + offset::UnityTransform::Index, index) ||
!Memory::IsUserAddress(access.pTransformData))
{
return {};
}
TransformAccessReadOnly pTransformAccessReadOnly = m.Read<TransformAccessReadOnly>(transform + 0x38); TransformData data{};
unsigned int Index = m.Read<unsigned int>(transform + 0x40); if (!m.TryRead(access.pTransformData + offset::UnityTransform::TransformData, data) ||
if (!pTransformAccessReadOnly.pTransformData) !Memory::IsUserAddress(data.pTransformArray) ||
return {}; !Memory::IsUserAddress(data.pTransformIndices))
{
return {};
}
TransformData transform_data = m.Read<TransformData>(pTransformAccessReadOnly.pTransformData + 0x18); if (!m.TryRead(data.pTransformArray + offset::UnityTransform::MatrixStride * static_cast<uintptr_t>(index), result))
return {};
if (transform_data.pTransformArray && transform_data.pTransformIndices) int transformIndex = -1;
{ if (!m.TryRead(data.pTransformIndices + offset::UnityTransform::IndexStride * static_cast<uintptr_t>(index), transformIndex))
result = m.Read<__m128>(transform_data.pTransformArray + 0x30 * Index); return {};
int transform_index = m.Read<int>(transform_data.pTransformIndices + 0x4 * Index);
int safe = 0;
while (transform_index >= 0 && safe++ < 200)
{
Matrix34 matrix = m.Read<Matrix34>(transform_data.pTransformArray + 0x30 * transform_index);
__m128 xxxx = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0x00)); // xxxx int safe = 0;
__m128 yyyy = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0x55)); // yyyy while (transformIndex >= 0 && safe++ < 200)
__m128 zwxy = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0x8E)); // zwxy {
__m128 wzyw = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0xDB)); // wzyw Matrix34 matrix{};
__m128 zzzz = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0xAA)); // zzzz if (!m.TryRead(data.pTransformArray + offset::UnityTransform::MatrixStride * static_cast<uintptr_t>(transformIndex), matrix))
__m128 yxwy = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0x71)); // yxwy return {};
__m128 tmp7 = _mm_mul_ps(*(__m128*)(&matrix.vec2), result);
result = _mm_add_ps(_mm_add_ps( __m128 xxxx = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0x00));
_mm_add_ps( __m128 yyyy = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0x55));
_mm_mul_ps( __m128 zwxy = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0x8E));
_mm_sub_ps( __m128 wzyw = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0xDB));
_mm_mul_ps(_mm_mul_ps(xxxx, mulVec1), zwxy), __m128 zzzz = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0xAA));
_mm_mul_ps(_mm_mul_ps(yyyy, mulVec2), wzyw)), __m128 yxwy = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0x71));
_mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(tmp7), 0xAA))), __m128 tmp7 = _mm_mul_ps(*reinterpret_cast<const __m128*>(&matrix.vec2), result);
_mm_mul_ps(
_mm_sub_ps(
_mm_mul_ps(_mm_mul_ps(zzzz, mulVec2), wzyw),
_mm_mul_ps(_mm_mul_ps(xxxx, mulVec0), yxwy)),
_mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(tmp7), 0x55)))),
_mm_add_ps(
_mm_mul_ps(
_mm_sub_ps(
_mm_mul_ps(_mm_mul_ps(yyyy, mulVec0), yxwy),
_mm_mul_ps(_mm_mul_ps(zzzz, mulVec1), zwxy)),
_mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(tmp7), 0x00))),
tmp7)), *(__m128*)(&matrix.vec0));
transform_index = m.Read<int>(transform_data.pTransformIndices + 0x4 * transform_index); result = _mm_add_ps(_mm_add_ps(
} _mm_add_ps(
} _mm_mul_ps(
_mm_sub_ps(
_mm_mul_ps(_mm_mul_ps(xxxx, mulVec1), zwxy),
_mm_mul_ps(_mm_mul_ps(yyyy, mulVec2), wzyw)),
_mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(tmp7), 0xAA))),
_mm_mul_ps(
_mm_sub_ps(
_mm_mul_ps(_mm_mul_ps(zzzz, mulVec2), wzyw),
_mm_mul_ps(_mm_mul_ps(xxxx, mulVec0), yxwy)),
_mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(tmp7), 0x55)))),
_mm_add_ps(
_mm_mul_ps(
_mm_sub_ps(
_mm_mul_ps(_mm_mul_ps(yyyy, mulVec0), yxwy),
_mm_mul_ps(_mm_mul_ps(zzzz, mulVec1), zwxy)),
_mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(tmp7), 0x00))),
tmp7)), *reinterpret_cast<const __m128*>(&matrix.vec0));
return Vector3(result.m128_f32[0], result.m128_f32[1], result.m128_f32[2]); if (!m.TryRead(data.pTransformIndices + offset::UnityTransform::IndexStride * static_cast<uintptr_t>(transformIndex), transformIndex))
return {};
}
alignas(16) float components[4]{};
_mm_store_ps(components, result);
return Vector3(components[0], components[1], components[2]);
} }
bool WorldToScreen(Matrix ViewMatrix, const Vector2& GameSize, Vector3& vIn, Vector2& vOut) bool WorldToScreen(const Matrix& ViewMatrix, const Vector2& GameSize, const Vector3& vIn, Vector2& vOut)
{ {
Matrix m = ViewMatrix.Transpose(); const Matrix transposed = ViewMatrix.Transpose();
Vector3 translationVector = Vector3(m._41, m._42, m._43); const Vector3 translationVector(transposed._41, transposed._42, transposed._43);
Vector3 up = Vector3(m._21, m._22, m._23); const Vector3 up(transposed._21, transposed._22, transposed._23);
Vector3 right = Vector3(m._11, m._12, m._13); const Vector3 right(transposed._11, transposed._12, transposed._13);
float w = translationVector.Dot(vIn) + m._44; const float w = translationVector.Dot(vIn) + transposed._44;
if (!std::isfinite(w) || w < 0.098f)
if (w < 0.098f)
return false; return false;
float y = up.Dot(vIn) + m._24; const float y = up.Dot(vIn) + transposed._24;
float x = right.Dot(vIn) + m._14; const float x = right.Dot(vIn) + transposed._14;
vOut = Vector2((GameSize.x / 2) * (1.f + x / w), (GameSize.y / 2) * (1.f - y / w)); if (!std::isfinite(x) || !std::isfinite(y))
return false;
return true; vOut = Vector2((GameSize.x / 2.f) * (1.f + x / w), (GameSize.y / 2.f) * (1.f - y / w));
return std::isfinite(vOut.x) && std::isfinite(vOut.y);
} }
uintptr_t GetObjectFromList(uintptr_t listPtr, uintptr_t lastObjectPtr, const char* objectName) uintptr_t GetObjectFromList(uintptr_t listPtr, uintptr_t lastObjectPtr, const char* objectName)
{ {
if (!listPtr || !objectName) if (!Memory::IsUserAddress(listPtr) || !Memory::IsUserAddress(lastObjectPtr) || !objectName)
return 0; return 0;
char ObjectName[128]{}; BaseObject activeObject{};
uintptr_t classNamePtr = 0; BaseObject lastObject{};
BaseObject activeObject = m.Read<BaseObject>(listPtr); if (!m.TryRead(listPtr, activeObject) || !m.TryRead(lastObjectPtr, lastObject))
BaseObject lastObject = m.Read<BaseObject>(lastObjectPtr); return 0;
if (activeObject.object != NULL) char objectNameBuffer[128]{};
{ int safe = 0;
while (activeObject.object != 0 && activeObject.object != lastObject.object) while (activeObject.object != 0 && activeObject.object != lastObject.object && safe++ < 4096)
{ {
classNamePtr = m.Read<uint64_t>(activeObject.object + 0x60); uintptr_t classNamePtr = 0;
m.ReadString(classNamePtr, ObjectName, sizeof(ObjectName)); if (m.TryRead(activeObject.object + offset::UnityCamera::ObjectName, classNamePtr) &&
Memory::IsUserAddress(classNamePtr) &&
m.ReadString(classNamePtr, objectNameBuffer, sizeof(objectNameBuffer)) &&
std::strcmp(objectNameBuffer, objectName) == 0)
{
return activeObject.object;
}
if (strcmp(ObjectName, objectName) == 0) if (!Memory::IsUserAddress(activeObject.nextObjectLink) || !m.TryRead(activeObject.nextObjectLink, activeObject))
return activeObject.object; break;
}
if (!activeObject.nextObjectLink) if (lastObject.object != 0)
break; {
uintptr_t classNamePtr = 0;
if (m.TryRead(lastObject.object + offset::UnityCamera::ObjectName, classNamePtr) &&
Memory::IsUserAddress(classNamePtr) &&
m.ReadString(classNamePtr, objectNameBuffer, sizeof(objectNameBuffer)) &&
std::strcmp(objectNameBuffer, objectName) == 0)
{
return lastObject.object;
}
}
activeObject = m.Read<BaseObject>(activeObject.nextObjectLink); return 0;
} }
}
if (lastObject.object != NULL)
{
classNamePtr = m.Read<uint64_t>(lastObject.object + 0x60);
m.ReadString(classNamePtr, ObjectName, sizeof(ObjectName));
if (strcmp(ObjectName, objectName) == 0) bool CGame::InitAddressUnlocked()
return lastObject.object; {
} uintptr_t pGom = 0;
if (!m.TryRead(m.m_gBaseAddress + offset::dwGameObjectManager, pGom) || !Memory::IsUserAddress(pGom) ||
!m.TryRead(pGom, m_objGameObjectManager))
{
m_objGameObjectManager = {};
m_initialized = false;
return false;
}
ZeroMemory(ObjectName, sizeof(ObjectName)); m_initialized = m_objGameObjectManager.lastActiveObject != 0 && m_objGameObjectManager.ActiveNodes != 0;
return m_initialized;
}
return 0; bool CGame::UpdateWorldUnlocked()
{
m_pGameWorld = 0;
m_pLocalGameWorld = 0;
if (!m_initialized && !InitAddressUnlocked())
return false;
uintptr_t activeNodes = 0;
uintptr_t lastActiveNode = 0;
if (!m.TryRead(m_objGameObjectManager.ActiveNodes, activeNodes) ||
!m.TryRead(m_objGameObjectManager.LastActiveNode, lastActiveNode))
{
return false;
}
m_pGameWorld = GetObjectFromList(activeNodes, lastActiveNode, "GameWorld");
if (!Memory::IsUserAddress(m_pGameWorld))
return false;
m_pLocalGameWorld = m.ReadChain(m_pGameWorld,
{ offset::UnityGameWorld::Component0, offset::UnityGameWorld::Component1, offset::UnityGameWorld::LocalGameWorld });
return Memory::IsUserAddress(m_pLocalGameWorld);
}
bool CGame::DiscoverCameraUnlocked()
{
m_pFpsCamera = 0;
m_cameraValid = false;
uintptr_t allCameraPtr = 0;
if (!m.TryRead(m.m_gBaseAddress + offset::dwAllCamera, allCameraPtr) || !Memory::IsUserAddress(allCameraPtr))
return false;
Camera allCameras{};
if (!m.TryRead(allCameraPtr, allCameras) || !Memory::IsUserAddress(allCameras.list) || allCameras.count == 0 || allCameras.count > 512)
return false;
for (uint64_t i = 0; i < allCameras.count; ++i)
{
uintptr_t camera = 0;
uintptr_t cameraObject = 0;
uintptr_t cameraNamePtr = 0;
if (!m.TryRead(allCameras.list + static_cast<uintptr_t>(i * sizeof(uintptr_t)), camera) || !Memory::IsUserAddress(camera) ||
!m.TryRead(camera + offset::UnityCamera::NativeObject, cameraObject) || !Memory::IsUserAddress(cameraObject) ||
!m.TryRead(cameraObject + offset::UnityCamera::ObjectName, cameraNamePtr) || !Memory::IsUserAddress(cameraNamePtr))
{
continue;
}
char name[64]{};
if (!m.ReadString(cameraNamePtr, name, sizeof(name)))
continue;
if (std::strcmp(name, "FPS Camera") == 0)
{
m_pFpsCamera = cameraObject;
return true;
}
}
return false;
}
bool CGame::UpdateViewMatrixUnlocked()
{
m_cameraValid = false;
m_mxViewMatrix = {};
if (!Memory::IsUserAddress(m_pFpsCamera))
return false;
uintptr_t viewObject = 0;
uintptr_t matrixHolder = 0;
Matrix matrix{};
if (!m.TryRead(m_pFpsCamera + offset::UnityCamera::ViewObject, viewObject) || !Memory::IsUserAddress(viewObject) ||
!m.TryRead(viewObject + offset::UnityCamera::ViewMatrixHolder, matrixHolder) || !Memory::IsUserAddress(matrixHolder) ||
!m.TryRead(matrixHolder + offset::UnityCamera::ViewMatrix, matrix))
{
return false;
}
m_mxViewMatrix = matrix;
m_cameraValid = true;
return true;
} }
bool CGame::InitAddress() bool CGame::InitAddress()
{ {
// GameObjectManager std::lock_guard<std::mutex> lock(m_stateMutex);
uintptr_t pGOM = m.Read<uintptr_t>(m.m_gBaseAddress + offset::dwGameObjectManager); return InitAddressUnlocked();
if (!pGOM)
{
m_objGameObjectManager = {};
return false;
}
m_objGameObjectManager = m.Read<GameObjectManager>(pGOM);
return m_objGameObjectManager.lastActiveObject != 0;
} }
bool CGame::Update() bool CGame::Update()
{ {
m_pGameWorld = 0; std::lock_guard<std::mutex> lock(m_stateMutex);
m_pLocalGameWorld = 0; return UpdateWorldUnlocked();
// GameWorld
uintptr_t activeNodes = m.Read<uintptr_t>(m_objGameObjectManager.ActiveNodes);
uintptr_t lastActiveNode = m.Read<uintptr_t>(m_objGameObjectManager.LastActiveNode);
m_pGameWorld = GetObjectFromList(activeNodes, lastActiveNode, "GameWorld");
if (!m_pGameWorld)
return false;
// LocalGameWorld
m_pLocalGameWorld = m.ReadChain(m_pGameWorld, { 0x30, 0x18, 0x28 });
if (!m_pLocalGameWorld)
return false;
return true;
} }
bool CGame::UpdateCamera() bool CGame::UpdateCamera()
{ {
m_pFpsCamera = 0; std::lock_guard<std::mutex> lock(m_stateMutex);
if (!DiscoverCameraUnlocked())
uintptr_t all_camera_ptr = m.Read<uintptr_t>(m.m_gBaseAddress + offset::dwAllCamera); return false;
if (!all_camera_ptr) return UpdateViewMatrixUnlocked();
return false;
Camera all_cameras = m.Read<Camera>(all_camera_ptr);
if (all_cameras.list == 0 || all_cameras.count == 0)
return false;
for (int i = 0; i < all_cameras.count; i++)
{
uintptr_t camera = m.Read<uintptr_t>(all_cameras.list + (i * 0x8));
if (camera != 0)
{
uintptr_t camera_obj = m.Read<uintptr_t>(camera + 0x30);
if (!camera_obj)
continue;
uintptr_t camera_name_ptr = m.Read<uintptr_t>(camera_obj + 0x60);
if (!camera_name_ptr)
continue;
char name[64]{};
m.ReadString(camera_name_ptr, name, sizeof(name));
if (strcmp(name, "FPS Camera") == 0) {
m_pFpsCamera = camera_obj;
return true;
}
}
}
return false;
} }
bool CGame::IsInRaid() bool CGame::Refresh()
{ {
return m_pGameWorld != NULL && m_pLocalGameWorld != NULL; std::lock_guard<std::mutex> lock(m_stateMutex);
if (!m_initialized && !InitAddressUnlocked())
return false;
if (!UpdateWorldUnlocked())
{
// GOM nodes can be replaced between raids. Re-read GOM once before giving up.
if (!InitAddressUnlocked() || !UpdateWorldUnlocked())
{
m_pGameWorld = 0;
m_pLocalGameWorld = 0;
return false;
}
}
if (!UpdateViewMatrixUnlocked())
{
if (!DiscoverCameraUnlocked() || !UpdateViewMatrixUnlocked())
m_cameraValid = false;
}
return true;
} }
Matrix CGame::GetViewMatrix() bool CGame::IsInRaid() const
{ {
if (!m_pFpsCamera) std::lock_guard<std::mutex> lock(m_stateMutex);
return {}; return Memory::IsUserAddress(m_pGameWorld) && Memory::IsUserAddress(m_pLocalGameWorld);
}
// ここ省略できるかチェック Matrix CGame::GetViewMatrix() const
uintptr_t dw = m.Read<uintptr_t>(m_pFpsCamera + 0x30); {
if (!dw) std::lock_guard<std::mutex> lock(m_stateMutex);
return {}; return m_mxViewMatrix;
}
uintptr_t viewmatrix_ptr = m.Read<uintptr_t>(dw + 0x18); GameStateSnapshot CGame::GetState() const
if (!viewmatrix_ptr) {
return {}; std::lock_guard<std::mutex> lock(m_stateMutex);
GameStateSnapshot state{};
return m.Read<Matrix>(viewmatrix_ptr + 0x100); state.initialized = m_initialized;
} state.inRaid = Memory::IsUserAddress(m_pGameWorld) && Memory::IsUserAddress(m_pLocalGameWorld);
state.cameraValid = m_cameraValid;
state.gameWorld = m_pGameWorld;
state.localGameWorld = m_pLocalGameWorld;
state.fpsCamera = m_pFpsCamera;
state.viewMatrix = m_mxViewMatrix;
return state;
}

View File

@ -3,30 +3,54 @@
#include "Struct.h" #include "Struct.h"
#include "../../../Framework/Memory/Memory.h" #include "../../../Framework/Memory/Memory.h"
#include "../../../Framework/Config/Config.h" #include "../../../Framework/Config/Config.h"
#include <mutex>
struct GameStateSnapshot
{
bool initialized{ false };
bool inRaid{ false };
bool cameraValid{ false };
uintptr_t gameWorld{ 0 };
uintptr_t localGameWorld{ 0 };
uintptr_t fpsCamera{ 0 };
Matrix viewMatrix{};
};
class CGame class CGame
{ {
private: private:
GameObjectManager m_objGameObjectManager{}; mutable std::mutex m_stateMutex;
Camera m_pAllCamera{ 0 }; GameObjectManager m_objGameObjectManager{};
Camera m_pAllCamera{};
uintptr_t m_pGameWorld{ 0 };
uintptr_t m_pLocalGameWorld{ 0 };
uintptr_t m_pFpsCamera{ 0 };
Matrix m_mxViewMatrix{};
bool m_initialized{ false };
bool m_cameraValid{ false };
bool InitAddressUnlocked();
bool UpdateWorldUnlocked();
bool DiscoverCameraUnlocked();
bool UpdateViewMatrixUnlocked();
public: public:
uintptr_t m_pGameWorld{ 0 }; bool InitAddress();
uintptr_t m_pLocalGameWorld{ 0 }; bool Update();
uintptr_t m_pFpsCamera{ 0 }; bool UpdateCamera();
Matrix m_mxViewMatrix{};
bool InitAddress(); // Single-writer refresh used by the dynamic snapshot thread. It updates world state and
// the view matrix under one lock so consumers never observe half-refreshed CGame fields.
bool Refresh();
bool IsInRaid(); bool IsInRaid() const;
Matrix GetViewMatrix() const;
bool Update(); GameStateSnapshot GetState() const;
bool UpdateCamera();
Matrix GetViewMatrix();
}; };
extern bool Vec2_Empty(const Vector2& value); extern bool Vec2_Empty(const Vector2& value);
extern bool Vec3_Empty(const Vector3& value); extern bool Vec3_Empty(const Vector3& value);
extern float GetDistance(Vector3 value1, Vector3 value2); extern float GetDistance(const Vector3& value1, const Vector3& value2);
extern Vector3 GetTransformPosition(uintptr_t& transform); extern Vector3 GetTransformPosition(uintptr_t transform);
extern bool WorldToScreen(Matrix ViewMatrix, const Vector2& GameSize, Vector3& vIn, Vector2& vOut); extern bool WorldToScreen(const Matrix& ViewMatrix, const Vector2& GameSize, const Vector3& vIn, Vector2& vOut);
extern uintptr_t GetObjectFromList(uintptr_t listPtr, uintptr_t lastObjectPtr, const char* objectName); extern uintptr_t GetObjectFromList(uintptr_t listPtr, uintptr_t lastObjectPtr, const char* objectName);

View File

@ -1,31 +1,128 @@
#pragma once #pragma once
// by Unispect and UC // Offsets confirmed from the supplied Unispect dump unless explicitly marked native/Unity.
namespace offset namespace offset
{ {
// Base (not present in dump.txt; keep the native UnityPlayer values used by this build) // Native UnityPlayer values for the supplied build (not class-field offsets from dump.txt).
constexpr auto dwGameObjectManager = 0x1CF93E0; constexpr auto dwGameObjectManager = 0x1CF93E0;
constexpr auto dwAllCamera = 0x1BF8BC0; constexpr auto dwAllCamera = 0x1BF8BC0;
// EFT.GameWorld / EFT.ClientLocalGameWorld namespace ManagedArray
constexpr auto ExfilController = 0x30;
constexpr auto LootList = 0x118;
constexpr auto RegisteredPlayers = 0x140;
constexpr auto MainPlayer = 0x1A8;
constexpr auto GrenadeList = 0x200;
// EFT.Player (dump.txt: EFT.Player)
namespace Player
{ {
constexpr auto pPlayerBody = 0xC0; // _playerBody constexpr auto Length = 0x18;
constexpr auto pMovementContext = 0x58; // <MovementContext>k__BackingField constexpr auto Data = 0x20;
constexpr auto pWeaponAnimation = 0x1E0; // <ProceduralWeaponAnimation>k__BackingField }
constexpr auto pProfile = 0x618; // <Profile>k__BackingField
constexpr auto pPhysics = 0x630; // Physical
constexpr auto pHealthController = 0x660; // _healthController namespace ManagedString
{
constexpr auto Length = 0x10;
constexpr auto Data = 0x14;
constexpr auto CharStride = 0x2;
}
namespace UnityComponent
{
// Legacy component -> Transform chain used by item/exfil/grenade objects in this project.
constexpr auto Step0 = 0x10;
constexpr auto Step1 = 0x30;
constexpr auto Step2 = 0x30;
constexpr auto Step3 = 0x8;
constexpr auto Step4 = 0x28;
constexpr auto Transform = 0x10;
}
namespace UnityTransform
{
constexpr auto Access = 0x38;
constexpr auto Index = 0x40;
constexpr auto TransformData = 0x18;
constexpr auto MatrixStride = 0x30;
constexpr auto IndexStride = 0x4;
}
namespace UnityCamera
{
constexpr auto NativeObject = 0x30;
constexpr auto ObjectName = 0x60;
constexpr auto ViewObject = 0x30;
constexpr auto ViewMatrixHolder = 0x18;
constexpr auto ViewMatrix = 0x100;
}
namespace UnityGameWorld
{
// Native Unity GameObject -> component chain used by this build.
constexpr auto Component0 = 0x30;
constexpr auto Component1 = 0x18;
constexpr auto LocalGameWorld = 0x28;
}
namespace Skeleton
{
constexpr auto Values = 0x30;
constexpr auto ValuesArray = 0x10;
}
namespace Grenade
{
constexpr auto ListObject = 0x18;
}
namespace Exfil
{
constexpr auto ControllerArray = 0x28;
constexpr auto Settings = 0x78;
constexpr auto Name = 0x18;
constexpr auto Status = 0xC8;
}
// EFT.GameWorld / EFT.ClientLocalGameWorld
namespace GameWorld
{
constexpr auto ExfilController = 0x30;
constexpr auto LootList = 0x118;
constexpr auto RegisteredPlayers = 0x140;
constexpr auto AllAlivePlayersList = 0x168;
constexpr auto ObservedPlayersById = 0x188;
constexpr auto MainPlayer = 0x1A8;
constexpr auto GrenadeList = 0x200;
}
// Backward-compatible aliases for older code.
constexpr auto ExfilController = GameWorld::ExfilController;
constexpr auto LootList = GameWorld::LootList;
constexpr auto RegisteredPlayers = GameWorld::RegisteredPlayers;
constexpr auto MainPlayer = GameWorld::MainPlayer;
constexpr auto GrenadeList = GameWorld::GrenadeList;
namespace Player
{
constexpr auto pMovementContext = 0x58;
constexpr auto pPlayerBody = 0xC0;
constexpr auto pWeaponAnimation = 0x1E0;
constexpr auto pProfile = 0x618;
constexpr auto pPhysics = 0x630;
constexpr auto pHealthController = 0x660;
constexpr auto pInventoryController = 0x678;
constexpr auto pHandsController = 0x680;
constexpr auto pPlayerBones = 0x6E8;
constexpr auto PlayerId = 0x9D4;
constexpr auto IsYourPlayer = 0xA36;
}
// EFT.NextObservedPlayer.ObservedPlayerView
namespace ObservedPlayer
{
constexpr auto PlayerBones = 0x60;
constexpr auto PlayerBody = 0x68;
constexpr auto Controller = 0x88;
constexpr auto Transform = 0xB8;
constexpr auto Physical = 0xE8;
constexpr auto Side = 0x100;
} }
// EFT.PlayerBody / EFT.PlayerBones (dump.txt)
namespace PlayerBody namespace PlayerBody
{ {
constexpr auto PlayerBones = 0x28; constexpr auto PlayerBones = 0x28;
@ -46,6 +143,7 @@ namespace offset
constexpr auto LeftThigh = 0x158; constexpr auto LeftThigh = 0x158;
constexpr auto RightThigh = 0x160; constexpr auto RightThigh = 0x160;
constexpr auto Spine3 = 0x168; constexpr auto Spine3 = 0x168;
constexpr auto Fireport = 0x178;
constexpr auto TransformArrayData = 0x20; constexpr auto TransformArrayData = 0x20;
} }
@ -54,22 +152,21 @@ namespace offset
constexpr auto Original = 0x10; constexpr auto Original = 0x10;
} }
// EFT.MovementContext (dump.txt: EFT.MovementContext)
namespace MovementContext namespace MovementContext
{ {
// Vector2 (x = yaw, y = pitch), in radians. constexpr auto Rotation = 0x27C;
constexpr auto Rotation = 0x27C; // Rotation_1
} }
// EFT.Profile
namespace Profile namespace Profile
{ {
constexpr auto Id = 0x10; constexpr auto Id = 0x10;
constexpr auto AccountId = 0x18; constexpr auto AccountId = 0x18;
constexpr auto pInfoClass = 0x40; // Info constexpr auto pInfoClass = 0x40;
constexpr auto Health = 0x58;
constexpr auto Inventory = 0x60;
constexpr auto Skills = 0x70;
} }
// EFT.Profile -> InfoClass
namespace InfoClass namespace InfoClass
{ {
constexpr auto Nickname = 0x10; constexpr auto Nickname = 0x10;
@ -77,59 +174,64 @@ namespace offset
constexpr auto MainProfileNickname = 0x20; constexpr auto MainProfileNickname = 0x20;
constexpr auto MGroupId = 0x28; constexpr auto MGroupId = 0x28;
constexpr auto TeamId = 0x30; constexpr auto TeamId = 0x30;
constexpr auto profileInfoSettingsClass = 0x48; // ProfileInfoSettingsClass constexpr auto profileInfoSettingsClass = 0x48;
constexpr auto PrestigeLevel = 0x70;
constexpr auto eplayerSide_0 = 0x88; constexpr auto eplayerSide_0 = 0x88;
constexpr auto RegistrationDate = 0x8C; constexpr auto RegistrationDate = 0x8C;
constexpr auto Type = 0x90; constexpr auto Type = 0x90;
constexpr auto MemberCategory = 0x98;
}
namespace ProfileInfoSettings
{
constexpr auto Role = 0x10;
constexpr auto BotDifficulty = 0x14;
} }
// EFT.HealthSystem.ActiveHealthController / GClass3009<T>
namespace HealthController namespace HealthController
{ {
constexpr auto BodyParts = 0x68; // Dictionary<int, BodyPartState<T>> constexpr auto BodyParts = 0x68;
constexpr auto DictionaryEntries = 0x18; constexpr auto DictionaryEntries = 0x18;
constexpr auto DictionaryCount = 0x40; constexpr auto DictionaryCount = 0x40;
constexpr auto ArrayData = 0x20; constexpr auto ArrayData = 0x20;
constexpr auto EntryStride = 0x18; constexpr auto EntryStride = 0x18;
constexpr auto EntryValue = 0x10; constexpr auto EntryValue = 0x10;
constexpr auto BodyPartHealth = 0x10; // BodyPartState.Health constexpr auto BodyPartHealth = 0x10;
constexpr auto HealthValue = 0x10; // HealthValue.Value constexpr auto HealthValue = 0x10;
constexpr auto Current = 0x00; constexpr auto Current = 0x00;
constexpr auto Maximum = 0x04; constexpr auto Maximum = 0x04;
} }
// EFT.Animations.ProceduralWeaponAnimation
namespace WeaponAnimations namespace WeaponAnimations
{ {
constexpr auto Breath = 0x30; constexpr auto Breath = 0x30;
constexpr auto MotionReact = 0x40; // MotionEffector constexpr auto MotionReact = 0x40;
constexpr auto ShotEffector = 0x50; // ShotEffector / Shootingg constexpr auto ShotEffector = 0x50;
constexpr auto FirearmController = 0xB0;
constexpr auto isAiming = 0x1C5; constexpr auto isAiming = 0x1C5;
constexpr auto ShotDirection = 0x22C;
constexpr auto Pitch = 0x398;
} }
// -.MotionEffector
namespace MotionEffector namespace MotionEffector
{ {
constexpr auto SwayFactors = 0x94; // UnityEngine.Vector3 constexpr auto SwayFactors = 0x94;
} }
// -.ShotEffector
namespace ShotEffector namespace ShotEffector
{ {
constexpr auto MainWeaponInHands = 0x38; // EFT.InventoryLogic.Weapon constexpr auto MainWeaponInHands = 0x38;
} }
// EFT.InventoryLogic.Weapon
namespace Weapon namespace Weapon
{ {
constexpr auto Template = 0x40; // CompoundItem.Template constexpr auto Template = 0x40;
constexpr auto MalfState = 0xF0; // WeaponMalfunctionStateClass constexpr auto MalfState = 0xF0;
} }
// EFT.InventoryLogic.WeaponTemplate
namespace WeaponTemplate namespace WeaponTemplate
{ {
constexpr auto WeaponRecoilSettings = 0x180; // WeaponRecoil constexpr auto WeaponRecoilSettings = 0x180;
constexpr auto AllowJam = 0x2F0; constexpr auto AllowJam = 0x2F0;
constexpr auto AllowFeed = 0x2F1; constexpr auto AllowFeed = 0x2F1;
constexpr auto AllowMisfire = 0x2F2; constexpr auto AllowMisfire = 0x2F2;
@ -137,7 +239,6 @@ namespace offset
constexpr auto BaseMalfunctionChance = 0x2F4; constexpr auto BaseMalfunctionChance = 0x2F4;
} }
// -.WeaponRecoil / -.Weapon.WeaponMalfunctionStateClass
namespace WeaponRecoil namespace WeaponRecoil
{ {
constexpr auto Enable = 0x18; constexpr auto Enable = 0x18;
@ -148,13 +249,11 @@ namespace offset
constexpr auto State = 0x6C; constexpr auto State = 0x6C;
} }
// -.GClass774 (stamina/oxygen state)
namespace PhysicalState namespace PhysicalState
{ {
constexpr auto Current = 0x48; // Single constexpr auto Current = 0x48;
} }
// .BasePhysicalClass
namespace Physics namespace Physics
{ {
constexpr auto Stamina = 0x38; constexpr auto Stamina = 0x38;
@ -166,4 +265,3 @@ namespace offset
constexpr auto OxygenRestoreRate = 0xD4; constexpr auto OxygenRestoreRate = 0xD4;
} }
} }

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <iostream> #include <iostream>
#include <cstdint>
#include <SimpleMath.h> #include <SimpleMath.h>
using namespace DirectX::SimpleMath; using namespace DirectX::SimpleMath;
@ -140,7 +141,7 @@ enum WildSpanwType : int
SANITOR_BOSS = 17, SANITOR_BOSS = 17,
// Global // Global
NORMAL_SCAV = 19, // 少し強化されたScav (????? NORMAL_SCAV = 19, // ꂽScav (?????
CULTIST_01_SCAV = 20, CULTIST_01_SCAV = 20,
CULTIST_02_SCAV = 21, CULTIST_02_SCAV = 21,

View File

@ -1,158 +1,231 @@
#include "Framework.h" #include "Framework.h"
#include <algorithm>
#include <chrono>
#include <thread>
// いくらかのリストを1RPMで読み取る為の構造体 namespace
struct CGameObjectList { {
uint64_t address[64]{}; constexpr int kMaxPlayers = 512;
}; constexpr int kMaxItems = 8192;
constexpr int kMaxGrenades = 256;
constexpr std::size_t kMaxExfils = 32;
// アイテムを1RPMで読み取るための構造体 bool ReadManagedPointerList(uintptr_t listObject, std::vector<uintptr_t>& out, int hardLimit)
struct CGameItemList { {
uint64_t address[2048]{}; out.clear();
}; if (!Memory::IsUserAddress(listObject))
return false;
UnityList list{};
if (!m.TryRead(listObject, list) || !Memory::IsUserAddress(list.list_address) || list.count < 0 || list.count > hardLimit)
return false;
if (list.count == 0)
return true;
return m.TryReadVector<uintptr_t>(list.list_address + offset::ManagedArray::Data,
static_cast<std::size_t>(list.count), out, static_cast<std::size_t>(hardLimit));
}
}
void CFramework::UpdateWorkerConfigFromUi()
{
WorkerConfigSnapshot next{};
next.needDynamic = g.ShowMenu || g.g_ESP || g.g_Aim || g.g_InfStamina || g.g_NoFallDamage || g.g_NoSway ||
g.g_NoRecoil || g.g_NoMalfunction || g.g_Recoil || g.g_Crosshair;
next.wantRemotePlayers = g.g_ESP || g.g_Aim;
next.wantHealth = g.g_ESP_HealthBar || (g.g_Aim && !g.g_AimIgnoreHealth);
next.wantSkeleton = g.g_ESP && g.g_ESP_Skeleton;
next.wantGrenades = g.g_ESP && g.g_ESP_Grenade;
next.wantExfils = g.g_ESP && g.g_ESP_Exfil;
next.wantItems = g.g_ESP && g.g_ESP_Item;
if (g.g_ESP && g.g_Aim)
next.remoteMaxDistance = (std::max)(g.g_ESP_MaxDistance, g.g_AimMaxDist);
else if (g.g_ESP)
next.remoteMaxDistance = g.g_ESP_MaxDistance;
else if (g.g_Aim)
next.remoteMaxDistance = g.g_AimMaxDist;
std::scoped_lock lock(m_workerConfigMutex);
m_workerConfig = next;
}
void CFramework::UpdateList() void CFramework::UpdateList()
{ {
// Get GameObjectManager // CGame is owned/refreshed by this thread only. Consumers receive immutable snapshots.
while (!tarkov->InitAddress() || !tarkov->UpdateCamera()) while (g.process_active && !tarkov->Refresh())
std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::this_thread::sleep_for(std::chrono::milliseconds(250));
// Loop
while (g.process_active) while (g.process_active)
{ {
// Sleep... const auto tickStart = std::chrono::steady_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(1)); const WorkerConfigSnapshot config = GetWorkerConfig();
auto snapshot = std::make_shared<DynamicWorldSnapshot>();
// 仮のリスト用変数 snapshot->generation = ++m_dynamicGeneration;
std::vector<CPlayer> list_player{};
std::vector<uintptr_t> list_grenade{};
if (g.g_ESP || g.g_Aim || g.g_InfStamina || g.g_NoFallDamage || g.g_NoSway) const bool refreshed = tarkov->Refresh();
const GameStateSnapshot game = tarkov->GetState();
snapshot->initialized = game.initialized;
snapshot->inRaid = refreshed && game.inRaid;
snapshot->cameraValid = game.cameraValid;
snapshot->gameWorld = game.gameWorld;
snapshot->localGameWorld = game.localGameWorld;
snapshot->fpsCamera = game.fpsCamera;
snapshot->viewMatrix = game.viewMatrix;
if (snapshot->inRaid && config.needDynamic)
{ {
uintptr_t localAddress = 0;
// Failed refreshes must not leave a stale player address available to memory features. if (m.TryRead(snapshot->localGameWorld + offset::GameWorld::MainPlayer, localAddress) &&
local.m_address = 0; Memory::IsUserAddress(localAddress))
// not In raid
if (!tarkov->Update())
continue;
// PlayerList
const auto GameWorld = tarkov->m_pLocalGameWorld;
const auto registered_player = m.Read<uintptr_t>(GameWorld + offset::RegisteredPlayers);
const auto entity_array = m.Read<UnityList>(registered_player);
if (entity_array.count <= 0)
continue;
else if (entity_array.list_address == NULL)
continue;
local.m_address = m.Read<uintptr_t>(GameWorld + offset::MainPlayer);
if (local.m_address != NULL)
{ {
local.UpdateStatic(); snapshot->local.m_address = localAddress;
local.Update(); if (snapshot->local.UpdateStatic(CPlayerKind::EftPlayer) && snapshot->local.Update())
auto player_list = m.Read<CGameObjectList>(entity_array.list_address + 0x20);
// ESP用EntityListを構築
for (auto i = 0; i < entity_array.count; i++)
{ {
CPlayer p = CPlayer(); if (config.wantHealth)
p.m_address = player_list.address[i]; snapshot->local.UpdateHealth();
}
else
{
snapshot->local = {};
}
}
if (p.m_address != NULL && p.m_address != local.m_address) uintptr_t registeredPlayers = 0;
std::vector<uintptr_t> playerAddresses;
if (config.wantRemotePlayers && snapshot->local.IsValid() &&
m.TryRead(snapshot->localGameWorld + offset::GameWorld::RegisteredPlayers, registeredPlayers) &&
ReadManagedPointerList(registeredPlayers, playerAddresses, kMaxPlayers))
{
snapshot->players.reserve(playerAddresses.size());
for (const uintptr_t address : playerAddresses)
{
if (!Memory::IsUserAddress(address) || address == snapshot->local.m_address)
continue;
CPlayer player{};
player.m_address = address;
if (!player.UpdateStatic() || !player.Update())
continue;
const float distance = GetDistance(snapshot->local.m_vecOrigin, player.m_vecOrigin);
if (config.remoteMaxDistance > 0.f && distance > config.remoteMaxDistance)
continue;
player.UpdateBone(config.wantSkeleton);
if (config.wantHealth)
player.UpdateHealth();
snapshot->players.push_back(std::move(player));
}
}
if (config.wantGrenades && snapshot->local.IsValid())
{
uintptr_t grenadeClass = 0;
uintptr_t grenadeListObject = 0;
std::vector<uintptr_t> grenadeAddresses;
if (m.TryRead(snapshot->localGameWorld + offset::GameWorld::GrenadeList, grenadeClass) &&
Memory::IsUserAddress(grenadeClass) &&
m.TryRead(grenadeClass + offset::Grenade::ListObject, grenadeListObject) &&
ReadManagedPointerList(grenadeListObject, grenadeAddresses, kMaxGrenades))
{
snapshot->grenades.reserve(grenadeAddresses.size());
for (const uintptr_t grenadeAddress : grenadeAddresses)
{ {
p.UpdateStatic(); if (!Memory::IsUserAddress(grenadeAddress))
continue;
// Distance check const uintptr_t transform = m.ReadChain(grenadeAddress, { offset::UnityComponent::Step0, offset::UnityComponent::Step1, offset::UnityComponent::Step2, offset::UnityComponent::Step3, offset::UnityComponent::Step4, offset::UnityComponent::Transform });
if (GetDistance(local.m_vecOrigin, p.GetBonePosition(BONE_ORIGIN)) < g.g_ESP_MaxDistance) const Vector3 position = GetTransformPosition(transform);
{ if (Vec3_Empty(position))
list_player.push_back(p); continue;
}
snapshot->grenades.push_back({ grenadeAddress, position });
} }
} }
} }
}
else {
std::this_thread::sleep_for(std::chrono::seconds(3));
continue;
} }
std::lock_guard<std::mutex> lock0(m_mtxEntityList); PublishDynamicSnapshot(snapshot);
m_vecEntityList = list_player;
// 30 Hz data snapshots are smooth enough for overlay/aim selection and keep all RPM off the render thread.
//GrenadeList = list_grenade; const auto elapsed = std::chrono::steady_clock::now() - tickStart;
constexpr auto target = std::chrono::milliseconds(33);
if (elapsed < target)
std::this_thread::sleep_for(target - elapsed);
} }
} }
// 要改善 void CFramework::UpdateStaticList()
void CFramework::UpdateStaticList() // C6262 :(
{ {
while (g.process_active) while (g.process_active)
{ {
std::this_thread::sleep_for(std::chrono::seconds(5)); // Interruptible wait so shutdown can join this worker promptly.
for (int i = 0; i < 20 && g.process_active; ++i)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (!g.process_active)
break;
std::vector<CItem> list_item{}; const WorkerConfigSnapshot config = GetWorkerConfig();
std::vector<CExfil> list_exfil{}; auto staticSnapshot = std::make_shared<StaticWorldSnapshot>();
tarkov->UpdateCamera(); staticSnapshot->generation = ++m_staticGeneration;
// ItemList const auto dynamicSnapshot = GetDynamicSnapshot();
if (tarkov->Update()) if (!dynamicSnapshot || !dynamicSnapshot->inRaid || !Memory::IsUserAddress(dynamicSnapshot->localGameWorld))
{ {
const auto GameWorld = tarkov->m_pLocalGameWorld; PublishStaticSnapshot(staticSnapshot);
// Exfil
if (g.g_ESP_Exfil)
{
const auto exfil_controller = m.Read<uintptr_t>(GameWorld + offset::ExfilController);
const auto exfil_array = m.Read<uintptr_t>(exfil_controller + 0x28);
auto exfil_list = m.Read<CGameObjectList>(exfil_array + 0x20);
for (auto j = 0; j < 16; j++)
{
CExfil e;
if (e.GetAddress(exfil_list.address[j]))
{
if (!e.Update())
continue;
e.m_pExfilName = e.GetName();
list_exfil.push_back(e);
}
}
}
if (g.g_ESP_Item)
{
const auto LootList = m.Read<uintptr_t>(tarkov->m_pLocalGameWorld + offset::LootList);
const auto ItemArray = m.Read<UnityList>(LootList);
if (ItemArray.count != 0)
{
auto item_list = m.Read<CGameItemList>(ItemArray.list_address + 0x20);
for (int k = 0; k < ItemArray.count; k++)
{
CItem item;
if (item.GetAddress(item_list.address[k])) {
item.Update();
list_item.push_back(item);
}
}
}
}
}
else {
std::this_thread::sleep_for(std::chrono::seconds(3));
continue; continue;
} }
std::lock_guard<std::mutex> lock1(m_mtxExfilList); const uintptr_t gameWorld = dynamicSnapshot->localGameWorld;
std::lock_guard<std::mutex> lock2(m_mtxItemList);
m_vecExfilList = list_exfil; if (config.wantExfils)
m_vecItemList = list_item; {
uintptr_t exfilController = 0;
uintptr_t exfilArray = 0;
if (m.TryRead(gameWorld + offset::GameWorld::ExfilController, exfilController) && Memory::IsUserAddress(exfilController) &&
m.TryRead(exfilController + offset::Exfil::ControllerArray, exfilArray) && Memory::IsUserAddress(exfilArray))
{
uint64_t exfilCount = 0;
std::vector<uintptr_t> exfilAddresses;
if (m.TryRead(exfilArray + offset::ManagedArray::Length, exfilCount) && exfilCount > 0 && exfilCount <= kMaxExfils &&
m.TryReadVector<uintptr_t>(exfilArray + offset::ManagedArray::Data, static_cast<std::size_t>(exfilCount), exfilAddresses, kMaxExfils))
{
staticSnapshot->exfils.reserve(exfilAddresses.size());
for (uintptr_t address : exfilAddresses)
{
CExfil exfil{};
if (!Memory::IsUserAddress(address) || !exfil.GetAddress(address) || !exfil.Update())
continue;
exfil.m_pExfilName = exfil.GetName();
staticSnapshot->exfils.push_back(std::move(exfil));
}
}
}
}
if (config.wantItems)
{
uintptr_t lootList = 0;
std::vector<uintptr_t> itemAddresses;
if (m.TryRead(gameWorld + offset::GameWorld::LootList, lootList) &&
ReadManagedPointerList(lootList, itemAddresses, kMaxItems))
{
staticSnapshot->items.reserve(itemAddresses.size());
for (uintptr_t address : itemAddresses)
{
CItem item{};
if (!Memory::IsUserAddress(address) || !item.GetAddress(address) || !item.Update())
continue;
staticSnapshot->items.push_back(std::move(item));
}
}
}
PublishStaticSnapshot(staticSnapshot);
} }
} }

View File

@ -4,35 +4,33 @@
bool InScreen(const Vector2& vIn) bool InScreen(const Vector2& vIn)
{ {
if (vIn.x < g.GameRect.left || vIn.x > g.GameRect.right || vIn.y < g.GameRect.top || vIn.y > g.GameRect.bottom) return !(vIn.x < g.GameRect.left || vIn.x > g.GameRect.right ||
return false; vIn.y < g.GameRect.top || vIn.y > g.GameRect.bottom);
return true;
} }
void CFramework::RenderInfo() void CFramework::RenderInfo()
{ {
// ウォーターマーク const auto snapshot = GetDynamicSnapshot();
std::string InfoText{ "Tarkov PvE | " + std::to_string((int)ImGui::GetIO().Framerate) + "FPS" };
StringEx(ImVec2(8.f, 8.f), ImColor(1.f, 1.f, 1.f, 1.f), ImGui::GetFontSize(), InfoText.c_str());
// --- 数据链路诊断: ESP/自瞄不工作时看这一行 --- std::string infoText{ "Tarkov PvE | " + std::to_string((int)ImGui::GetIO().Framerate) + "FPS" };
// Init = GOM 初始化(GameObjectManager offset 是否有效) StringEx(ImVec2(8.f, 8.f), ImColor(1.f, 1.f, 1.f, 1.f), ImGui::GetFontSize(), infoText.c_str());
// Raid = 是否在局内(GameWorld 是否找到)
// Local = 本地玩家地址(0 说明 MainPlayer offset 失效) const bool initialized = snapshot && snapshot->initialized;
// Ent = 实体列表数量(0 说明 RegisteredPlayers/PlayerBody offset 失效) const bool inRaid = snapshot && snapshot->inRaid;
char diag[192]{}; const uintptr_t localAddress = snapshot ? snapshot->local.m_address : 0;
const std::size_t entityCount = snapshot ? snapshot->players.size() : 0;
char diag[224]{};
snprintf(diag, sizeof(diag), snprintf(diag, sizeof(diag),
"Init:%s Raid:%s Local:0x%llX Ent:%d", "Init:%s Raid:%s Cam:%s Local:0x%llX Ent:%zu Gen:%llu",
tarkov->InitAddress() ? "OK" : "FAIL", initialized ? "OK" : "FAIL",
tarkov->IsInRaid() ? "Y" : "N", inRaid ? "Y" : "N",
(unsigned long long)local.m_address, (snapshot && snapshot->cameraValid) ? "OK" : "FAIL",
(int)GetEntityList().size()); (unsigned long long)localAddress,
entityCount,
(unsigned long long)(snapshot ? snapshot->generation : 0));
StringEx(ImVec2(8.f, 24.f), ImColor(1.f, 1.f, 0.f, 1.f), ImGui::GetFontSize(), diag); StringEx(ImVec2(8.f, 24.f), ImColor(1.f, 1.f, 0.f, 1.f), ImGui::GetFontSize(), diag);
// --- 自瞄实时状态 (关菜单也能看) ---
// Aim = 自瞄开关; M = 方式(MEM内存/MOU鼠标); Key = 触发键是否按住(含F9)
// Tgt = 是否锁定目标; d = 目标离屏幕中心距离px; Hook/Btn = 鼠标钩子状态
char aim[192]{}; char aim[192]{};
snprintf(aim, sizeof(aim), snprintf(aim, sizeof(aim),
"Aim:%s M:%s F:%s Tgt:%s d=%.0fpx", "Aim:%s M:%s F:%s Tgt:%s d=%.0fpx",
@ -43,20 +41,21 @@ void CFramework::RenderInfo()
m_diagBestDist); m_diagBestDist);
StringEx(ImVec2(8.f, 42.f), ImColor(1.f, 0.f, 0.f, 1.f), ImGui::GetFontSize(), aim); StringEx(ImVec2(8.f, 42.f), ImColor(1.f, 0.f, 0.f, 1.f), ImGui::GetFontSize(), aim);
// Crosshair if (g.g_Crosshair && snapshot && snapshot->local.IsValid() && !snapshot->local.IsAiming())
if (g.g_Crosshair && !local.IsAiming())
{ {
switch (g.g_CrosshairType) switch (g.g_CrosshairType)
{ {
case 0: { case 0:
ImVec2 Center = ImVec2(g.GameRect.right / 2, g.GameRect.bottom / 2); {
ImGui::GetBackgroundDrawList()->AddLine(ImVec2(Center.x - g.g_CrosshairSize, Center.y), ImVec2((Center.x + g.g_CrosshairSize) + 1, Center.y), CrosshairColor, 1); ImVec2 center = ImVec2(g.GameRect.right / 2.f, g.GameRect.bottom / 2.f);
ImGui::GetBackgroundDrawList()->AddLine(ImVec2(Center.x, Center.y - g.g_CrosshairSize), ImVec2(Center.x, (Center.y + g.g_CrosshairSize) + 1), CrosshairColor, 1); ImGui::GetBackgroundDrawList()->AddLine(ImVec2(center.x - g.g_CrosshairSize, center.y), ImVec2(center.x + g.g_CrosshairSize + 1.f, center.y), CrosshairColor, 1.f);
} break; ImGui::GetBackgroundDrawList()->AddLine(ImVec2(center.x, center.y - g.g_CrosshairSize), ImVec2(center.x, center.y + g.g_CrosshairSize + 1.f), CrosshairColor, 1.f);
break;
}
case 1: case 1:
ImGui::GetBackgroundDrawList()->AddCircle(ImVec2((float)g.GameRect.right / 2.f, (float)g.GameRect.bottom / 2.f), g.g_CrosshairSize, CrosshairColor, NULL); ImGui::GetBackgroundDrawList()->AddCircle(ImVec2((float)g.GameRect.right / 2.f, (float)g.GameRect.bottom / 2.f), g.g_CrosshairSize, CrosshairColor, 0);
//ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2((float)g.GameRect.right / 2.f, (float)g.GameRect.bottom / 2.f), g.g_CrosshairSize + 1, ImColor(0.f, 0.f, 0.f, 1.f), NULL); break;
//ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2((float)g.GameRect.right / 2.f, (float)g.GameRect.bottom / 2.f), g.g_CrosshairSize, CrosshairColor, NULL); default:
break; break;
} }
} }
@ -64,252 +63,200 @@ void CFramework::RenderInfo()
void CFramework::RenderESP() void CFramework::RenderESP()
{ {
// Localの更新 const auto snapshot = GetDynamicSnapshot();
CPlayer* pLocal = &local; if (!snapshot || !snapshot->inRaid || !snapshot->cameraValid || !snapshot->local.IsValid())
if (!pLocal->Update())
return; return;
// ViewMatrix const CPlayer& localPlayer = snapshot->local;
Matrix ViewMatrix = tarkov->GetViewMatrix(); // WorldToScreenの度に呼び出す必要はない const Matrix& viewMatrix = snapshot->viewMatrix;
const Vector2 gameSize((float)g.GameRect.right, (float)g.GameRect.bottom);
// るーぷするよ // Rendering consumes an immutable snapshot: no player/camera RPM is performed in this loop.
for (auto& entity : GetEntityList()) for (const auto& entity : snapshot->players)
{ {
CPlayer* pEntity = &entity; const CPlayer& player = entity;
if (!player.IsValid() || Vec3_Empty(player.m_vecOrigin))
if (!pEntity->Update())
continue;
// 距離を算出
float flDistance = GetDistance(pLocal->m_vecOrigin, pEntity->m_vecOrigin);
// WorldToScreen
Vector2 pScreen{}, pHead{}, pNeck{};;
if (!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), pEntity->m_vecOrigin, pScreen))
continue; continue;
if (!InScreen(pScreen)) const float distance = GetDistance(localPlayer.m_vecOrigin, player.m_vecOrigin);
Vector2 screen{}, head{}, neck{};
if (!WorldToScreen(viewMatrix, gameSize, player.m_vecOrigin, screen) || !InScreen(screen))
continue;
if (!WorldToScreen(viewMatrix, gameSize, player.m_vecHeadOrigin, head) ||
!WorldToScreen(viewMatrix, gameSize, player.m_vecNeckOrigin, neck))
continue; continue;
// WorldToScreen const float headToNeck = neck.y - head.y;
pEntity->UpdateBone(); const float height = screen.y - head.y + (headToNeck * 2.5f);
if (!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), pEntity->m_vecHeadOrigin, pHead) || const float width = height / 3.5f;
!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), pEntity->m_vecNeckOrigin, pNeck)) if (height <= 0.f || width <= 0.f)
continue; continue;
// 各種サイズの算出等 std::string name = "InValid";
const float HeadToNeck = pNeck.y - pHead.y; ImColor color = ImColor(1.f, 1.f, 1.f, 1.f);
const float pHeight = pScreen.y - pHead.y + (HeadToNeck * 2.5f); if (player.m_bSpawnTypeValid)
const float pWidth = pHeight / 3.5f; {
GetESPInfo(player.m_iSpawnType, name, color);
std::string Name = "InValid"; }
ImColor pColor = ImColor(1.f, 1.f, 1.f, 1.f); else if (player.IsObserved())
GetESPInfo(pEntity->m_iSpawnType, Name, pColor); {
if (player.m_iSide == SIDE_SCAV)
// BTR-82 {
if (pEntity->m_iSpawnType == BTR_VEHICLE) { name = "Observed SCAV";
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(pScreen.x, pScreen.y - 2.f), 2.f, ImColor(1.f, 1.f, 1.f, 1.f), 0.f); color = Col_ESP_Scav;
}
std::string BTR_Text = "BTR-82 [" + std::to_string((int)flDistance) + "m]"; else
String(ImVec2(pScreen.x - (ImGui::CalcTextSize(BTR_Text.c_str()).x / 2.f), pScreen.y), ImColor(1.f, 1.f, 1.f, 1.f), BTR_Text.c_str()); {
name = "Observed PMC";
color = Col_ESP_PMC;
}
}
if (player.m_bSpawnTypeValid && player.m_iSpawnType == BTR_VEHICLE)
{
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y - 2.f), 2.f, ImColor(1.f, 1.f, 1.f, 1.f), 0);
const std::string text = "BTR-82 [" + std::to_string((int)distance) + "m]";
String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y), ImColor(1.f, 1.f, 1.f, 1.f), text.c_str());
continue; continue;
} }
// Line
if (g.g_ESP_Line) if (g.g_ESP_Line)
DrawLine(ImVec2(g.GameRect.right / 2.f, g.GameRect.bottom), ImVec2(pScreen.x, pScreen.y), pColor, 1.f); DrawLine(ImVec2(g.GameRect.right / 2.f, g.GameRect.bottom), ImVec2(screen.x, screen.y), color, 1.f);
// 2D Box
if (g.g_ESP_Box) if (g.g_ESP_Box)
{ {
static float bScale = 1.75f; static float boxScale = 1.75f;
// Filled
if (g.g_ESP_BoxFilled) if (g.g_ESP_BoxFilled)
ImGui::GetBackgroundDrawList()->AddRectFilled(ImVec2(pScreen.x - pWidth, pScreen.y - pHeight), ImVec2(pScreen.x + pWidth, pScreen.y), Col_ESP_Filled, 1.f); ImGui::GetBackgroundDrawList()->AddRectFilled(ImVec2(screen.x - width, screen.y - height), ImVec2(screen.x + width, screen.y), Col_ESP_Filled, 1.f);
// Box
switch (g.g_ESP_BoxStyle) switch (g.g_ESP_BoxStyle)
{ {
case 0: case 0:
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y - pHeight), ImVec2(pScreen.x + pWidth, pScreen.y - pHeight), pColor, 1.f); DrawLine(ImVec2(screen.x - width, screen.y - height), ImVec2(screen.x + width, screen.y - height), color, 1.f);
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y), ImVec2(pScreen.x - pWidth, pScreen.y - pHeight), pColor, 1.f); DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x - width, screen.y - height), color, 1.f);
DrawLine(ImVec2(pScreen.x + pWidth, pScreen.y), ImVec2(pScreen.x + pWidth, pScreen.y - pHeight), pColor, 1.f); DrawLine(ImVec2(screen.x + width, screen.y), ImVec2(screen.x + width, screen.y - height), color, 1.f);
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y), ImVec2(pScreen.x + pWidth, pScreen.y), pColor, 1.f); DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x + width, screen.y), color, 1.f);
break; break;
case 1: case 1:
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y - pHeight), ImVec2(pScreen.x - pWidth + (pWidth / bScale), pScreen.y - pHeight), pColor, 1.f); DrawLine(ImVec2(screen.x - width, screen.y - height), ImVec2(screen.x - width + (width / boxScale), screen.y - height), color, 1.f);
DrawLine(ImVec2(pScreen.x + pWidth, pScreen.y - pHeight), ImVec2(pScreen.x + pWidth - (pWidth / bScale), pScreen.y - pHeight), pColor, 1.f); DrawLine(ImVec2(screen.x + width, screen.y - height), ImVec2(screen.x + width - (width / boxScale), screen.y - height), color, 1.f);
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y - pHeight), ImVec2(pScreen.x - pWidth, pScreen.y - pHeight + (pWidth / bScale)), pColor, 1.f); DrawLine(ImVec2(screen.x - width, screen.y - height), ImVec2(screen.x - width, screen.y - height + (width / boxScale)), color, 1.f);
DrawLine(ImVec2(pScreen.x + pWidth, pScreen.y - pHeight), ImVec2(pScreen.x + pWidth, pScreen.y - pHeight + (pWidth / bScale)), pColor, 1.f); DrawLine(ImVec2(screen.x + width, screen.y - height), ImVec2(screen.x + width, screen.y - height + (width / boxScale)), color, 1.f);
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y), ImVec2(pScreen.x - pWidth, pScreen.y - (pWidth / bScale)), pColor, 1.f); DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x - width, screen.y - (width / boxScale)), color, 1.f);
DrawLine(ImVec2(pScreen.x + pWidth, pScreen.y), ImVec2(pScreen.x + pWidth, pScreen.y - (pWidth / bScale)), pColor, 1.f); DrawLine(ImVec2(screen.x + width, screen.y), ImVec2(screen.x + width, screen.y - (width / boxScale)), color, 1.f);
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y), ImVec2(pScreen.x - pWidth + (pWidth / bScale), pScreen.y), pColor, 1.f); DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x - width + (width / boxScale), screen.y), color, 1.f);
DrawLine(ImVec2(pScreen.x + pWidth, pScreen.y), ImVec2(pScreen.x + pWidth - (pWidth / bScale), pScreen.y), pColor, 1.f); DrawLine(ImVec2(screen.x + width, screen.y), ImVec2(screen.x + width - (width / boxScale), screen.y), color, 1.f);
break; break;
default: default:
break; break;
} }
} }
// パフォーマンスの都合上250m以上先のプレイヤーはスキップ if (g.g_ESP_Skeleton && distance < 250.f && player.m_vecBoneList.size() >= 9)
if (g.g_ESP_Skeleton && 250.f > flDistance)
{ {
// Head Circle ImGui::GetBackgroundDrawList()->AddCircle(ImVec2(head.x, head.y - (headToNeck / 2.f)), headToNeck * 1.55f, color, 0);
ImGui::GetBackgroundDrawList()->AddCircle(ImVec2(pHead.x, pHead.y - (HeadToNeck / 2.f)), HeadToNeck * 1.55f, pColor, 0.f);
if (pEntity->m_vecBoneList.size() != 0) const Vector3 bones[][2]{
{ player.m_vecNeckOrigin, player.m_vecBoneList[CPelvis] },
{ player.m_vecNeckOrigin, player.m_vecBoneList[CLeftForearm] },
{ player.m_vecBoneList[CLeftForearm], player.m_vecBoneList[CLeftPalm] },
{ player.m_vecNeckOrigin, player.m_vecBoneList[CRightForearm] },
{ player.m_vecBoneList[CRightForearm], player.m_vecBoneList[CRightPalm] },
{ player.m_vecBoneList[CPelvis], player.m_vecBoneList[CLeftThigh] },
{ player.m_vecBoneList[CLeftThigh], player.m_vecBoneList[CLeftFoot] },
{ player.m_vecBoneList[CPelvis], player.m_vecBoneList[CRightThigh] },
{ player.m_vecBoneList[CRightThigh], player.m_vecBoneList[CRightFoot] }
};
for (const auto& pair : bones)
{ {
// BoneList Vector2 a{}, b{};
Vector3 list[][2]{ if (!WorldToScreen(viewMatrix, gameSize, pair[0], a) || !WorldToScreen(viewMatrix, gameSize, pair[1], b))
{ pEntity->m_vecNeckOrigin, pEntity->m_vecBoneList[CPelvis] }, continue;
{ pEntity->m_vecNeckOrigin, pEntity->m_vecBoneList[CLeftForearm] }, DrawLine(ImVec2(a.x, a.y), ImVec2(b.x, b.y), color, 1.f);
{ pEntity->m_vecBoneList[CLeftForearm], pEntity->m_vecBoneList[CLeftPalm] },
{ pEntity->m_vecNeckOrigin, pEntity->m_vecBoneList[CRightForearm] },
{ pEntity->m_vecBoneList[CRightForearm], pEntity->m_vecBoneList[CRightPalm] },
{ pEntity->m_vecBoneList[CPelvis], pEntity->m_vecBoneList[CLeftThigh] },
{ pEntity->m_vecBoneList[CLeftThigh], pEntity->m_vecBoneList[CLeftFoot] },
{ pEntity->m_vecBoneList[CPelvis], pEntity->m_vecBoneList[CRightThigh] },
{ pEntity->m_vecBoneList[CRightThigh], pEntity->m_vecBoneList[CRightFoot] }
};
// Body
for (int j = 0; j < 9; j++) {
Vector2 bOut1{}, bOut2{};
if (!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), list[j][0], bOut1) || !WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), list[j][1], bOut2))
break;
DrawLine(ImVec2(bOut1.x, bOut1.y), ImVec2(bOut2.x, bOut2.y), pColor, 1.f);
}
} }
} }
// Distance if (g.g_ESP_Distance)
if (g.g_ESP_Distance) {
std::string DistanceStr = std::to_string((int)flDistance) + "m";
String(ImVec2(pScreen.x - (ImGui::CalcTextSize(DistanceStr.c_str()).x / 2.f), pScreen.y), ImColor(1.f, 1.f, 1.f, 1.f), DistanceStr.c_str());
}
// Healthbar
if (g.g_ESP_HealthBar) {
pEntity->UpdateHealth();
HealthBar(pScreen.x - pWidth - 4.f, pScreen.y, 2.f, -pHeight, pEntity->m_fHealth, pEntity->m_fHealthMax);
}
// Name
if (g.g_ESP_Name && pEntity->m_iSpawnType != SIDE_SCAV && pEntity->m_iSpawnType != SNIPER_SCAV && pEntity->m_iSpawnType != NORMAL_SCAV)
{ {
// 不明なSpawnTypeだったら値を表示 const std::string distanceText = std::to_string((int)distance) + "m";
if (!Name.compare("InValid")) String(ImVec2(screen.x - (ImGui::CalcTextSize(distanceText.c_str()).x / 2.f), screen.y), ImColor(1.f, 1.f, 1.f, 1.f), distanceText.c_str());
Name += "[" + std::to_string(pEntity->m_iSpawnType) + "]"; }
String(ImVec2(pScreen.x - (ImGui::CalcTextSize(Name.c_str()).x / 2.f), pScreen.y - pHeight - 14.f), ImColor(1.f, 1.f, 1.f, 1.f), Name.c_str()); if (g.g_ESP_HealthBar && player.m_bHealthValid)
HealthBar(screen.x - width - 4.f, screen.y, 2.f, -height, (int)player.m_fHealth, (int)player.m_fHealthMax);
if (g.g_ESP_Name && (!player.m_bSpawnTypeValid ||
(player.m_iSpawnType != SIDE_SCAV && player.m_iSpawnType != SNIPER_SCAV && player.m_iSpawnType != NORMAL_SCAV)))
{
if (name == "InValid" && player.m_bSpawnTypeValid)
name += "[" + std::to_string(player.m_iSpawnType) + "]";
String(ImVec2(screen.x - (ImGui::CalcTextSize(name.c_str()).x / 2.f), screen.y - height - 14.f), ImColor(1.f, 1.f, 1.f, 1.f), name.c_str());
} }
} }
// Grenade
if (g.g_ESP_Grenade) if (g.g_ESP_Grenade)
{ {
// GrenadeList for (const auto& grenade : snapshot->grenades)
if (g.g_ESP_Grenade)
{ {
const auto grenade_class = m.Read<uintptr_t>(tarkov->m_pLocalGameWorld + offset::GrenadeList); const float distance = GetDistance(localPlayer.m_vecOrigin, grenade.position);
const auto grenade_array_ptr = m.Read<uintptr_t>(grenade_class + 0x18); if (distance > 100.f)
const auto grenade_array = m.Read<UnityList>(grenade_array_ptr); continue;
if (distance < 10.f)
String(ImVec2(g.GameRect.right / 2.f - (ImGui::CalcTextSize("[ WARNING ] Grenade!!").x / 2.f), g.GameRect.bottom / 2.f), ImColor(1.f, 0.f, 0.f, 1.f), "[ WARNING ] Grenade!!");
if (grenade_array.count > 0) Vector2 screen{};
{ if (!WorldToScreen(viewMatrix, gameSize, grenade.position, screen))
for (auto k = 0; k < grenade_array.count; k++) continue;
{
auto grenade = m.Read<uintptr_t>(grenade_array.list_address + 0x20 + (0x8 * k));
if (grenade != NULL)
{
uintptr_t TransformInternal = m.ReadChain(grenade, { 0x10, 0x30, 0x30, 0x8, 0x28, 0x10 });
Vector3 m_pVecLocation = GetTransformPosition(TransformInternal);
if (Vec3_Empty(m_pVecLocation)) const std::string text = "Grenade [" + std::to_string((int)distance) + "m]";
continue; ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y), 2.f, ImColor(1.f, 0.f, 0.f, 1.f), 0);
String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y - 13.f), ImColor(1.f, 0.f, 0.f, 1.f), text.c_str());
float gDistance = GetDistance(pLocal->m_vecOrigin, m_pVecLocation);
if (gDistance > 100.f)
continue;
else if (gDistance < 10.f)
String(ImVec2(g.GameRect.right / 2.f - (ImGui::CalcTextSize("[ WARNING ] Grenade!!").x / 2.f), g.GameRect.bottom / 2.f), ImColor(1.f, 0.f, 0.f, 1.f), "[ WARNING ] Grenade!!");
Vector2 pGrenadeRoot{};
if (!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), m_pVecLocation, pGrenadeRoot))
continue;
std::string gre_tx = "Grenade [" + std::to_string(int(gDistance)) + "m]";
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(pGrenadeRoot.x, pGrenadeRoot.y), 2.f, ImColor(1.f, 0.f, 0.f, 1.f), 0.f);
String(ImVec2(pGrenadeRoot.x - (ImGui::CalcTextSize(gre_tx.c_str()).x / 2.f), pGrenadeRoot.y - 13.f), ImColor(1.f, 0.f, 0.f, 1.f), gre_tx.c_str());
}
}
}
} }
} }
// Item const auto staticSnapshot = GetStaticSnapshot();
if (!staticSnapshot)
return;
if (g.g_ESP_Item) if (g.g_ESP_Item)
{ {
for (auto& item : GetItemList()) for (const auto& item : staticSnapshot->items)
{ {
CItem* pItem = &item; if (!item.m_bIsCorpse && item.m_CName.empty())
if (!pItem->m_bIsCorpse && pItem->m_CName.empty())
continue; continue;
else if (!g.g_ESP_Corpse && pItem->m_bIsCorpse) if (!g.g_ESP_Corpse && item.m_bIsCorpse)
continue; continue;
float ItemDistance = GetDistance(pLocal->m_vecOrigin, pItem->m_vecOrigin); const float distance = GetDistance(localPlayer.m_vecOrigin, item.m_vecOrigin);
if (distance > g.g_ESP_MaxItemDistance)
// Distance Check
if (ItemDistance > g.g_ESP_MaxItemDistance)
continue; continue;
Vector2 pItemScreen{}; Vector2 screen{};
if (!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), pItem->m_vecOrigin, pItemScreen)) if (!WorldToScreen(viewMatrix, gameSize, item.m_vecOrigin, screen) || !InScreen(screen))
continue; continue;
if (!InScreen(pItemScreen)) std::string text = item.m_bIsCorpse ? "Corpse" : item.m_CName;
continue; text += "[" + std::to_string((int)distance) + "m]";
const ImColor itemColor = item.m_bIsCorpse ? Col_ESP_Corpse : Col_ESP_RareItem;
std::string vItemTx = pItem->m_bIsCorpse ? "Corpse" : pItem->m_CName; ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y - 2.f), 2.f, itemColor, 0);
vItemTx +="[" + std::to_string((int)ItemDistance) + "m]"; String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y), itemColor, text.c_str());
ImColor itemCol = pItem->m_bIsCorpse ? Col_ESP_Corpse : Col_ESP_RareItem;
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(pItemScreen.x, pItemScreen.y - 2.f), 2.f, itemCol, 0.f);
String(ImVec2(pItemScreen.x - (ImGui::CalcTextSize(vItemTx.c_str()).x / 2.f), pItemScreen.y), itemCol, vItemTx.c_str());
} }
} }
// Exfil
if (g.g_ESP_Exfil) if (g.g_ESP_Exfil)
{ {
for (auto& exfil : GetExfilList()) for (const auto& exfil : staticSnapshot->exfils)
{ {
CExfil* pExfil = &exfil; Vector2 screen{};
if (!WorldToScreen(viewMatrix, gameSize, exfil.m_pVecLocation, screen) || !InScreen(screen))
Vector2 pExfilScreen{};
if (!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), pExfil->m_pVecLocation, pExfilScreen))
continue; continue;
if (!InScreen(pExfilScreen)) const ImColor exfilColor = exfil.m_pExfilStatus == NOTREADY ? Col_ESP_ExfilClose : Col_ESP_ExfilOpen;
continue; const std::string text = exfil.m_pExfilName + " [" + std::to_string((int)GetDistance(localPlayer.m_vecOrigin, exfil.m_pVecLocation)) + "m]";
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y - 2.f), 2.f, exfilColor, 0);
ImColor ExfilColor = pExfil->m_pExfilStatus == NOTREADY ? Col_ESP_ExfilClose : Col_ESP_ExfilOpen; String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y), exfilColor, text.c_str());
std::string exfilText = pExfil->m_pExfilName + " [" + std::to_string((int)GetDistance(pLocal->m_vecOrigin, pExfil->m_pVecLocation)) + "m]";
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(pExfilScreen.x, pExfilScreen.y - 2.f), 2.f, ExfilColor, 0.f);
String(ImVec2(pExfilScreen.x - (ImGui::CalcTextSize(exfilText.c_str()).x / 2.f), pExfilScreen.y), ExfilColor, exfilText.c_str());
} }
} }
} }

View File

@ -1,10 +1,11 @@
#pragma once #pragma once
#include <Windows.h> #include <Windows.h>
#include <atomic>
struct Globals struct Globals
{ {
// System // System
bool process_active = true; std::atomic_bool process_active{ true };
bool ShowMenu = false; bool ShowMenu = false;
// GameWindow Data // GameWindow Data

View File

@ -49,7 +49,11 @@ bool Memory::AttachProcess(const char* targetName, int InitMode)
void Memory::DetachProcess() void Memory::DetachProcess()
{ {
CloseHandle(pHandle); if (pHandle)
CloseHandle(pHandle);
pHandle = nullptr;
PID = 0;
m_gBaseAddress = 0;
} }
uintptr_t Memory::GetModuleBase(const std::string moduleName) uintptr_t Memory::GetModuleBase(const std::string moduleName)

View File

@ -5,116 +5,166 @@
#include <psapi.h> #include <psapi.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include <limits>
#include <algorithm>
/* /*
[+] メモリやオーバーレイの初期化モードを設定します Memory/overlay initialization mode.
*/
WINDOW_TITLE : ウィンドウのタイトルを使用します
WINDOW_CLASS : ウィンドウのクラス名を使用します
PROCESS : 実行ファイル名を使用します */
enum InitMode : int enum InitMode : int
{ {
WINDOW_TITLE, WINDOW_TITLE,
WINDOW_CLASS, WINDOW_CLASS,
PROCESS PROCESS
}; };
class Memory class Memory
{ {
private: private:
DWORD PID; DWORD PID{};
HANDLE pHandle; HANDLE pHandle{};
uintptr_t GetModuleBase(const std::string moduleName); uintptr_t GetModuleBase(const std::string moduleName);
PROCESSENTRY32 GetProcess(const std::string processName); PROCESSENTRY32 GetProcess(const std::string processName);
public: public:
uintptr_t m_gBaseAddress; uintptr_t m_gBaseAddress{};
bool AttachProcess(const char* targetName, int mode); bool AttachProcess(const char* targetName, int mode);
void DetachProcess(); void DetachProcess();
// R/WPM Templates static bool IsUserAddress(const uintptr_t address) noexcept
template <typename T> {
constexpr const T Read(const uintptr_t& address) const noexcept return address > 0x10000ULL && address < 0x0000800000000000ULL;
{ }
T value{};
ReadProcessMemory(pHandle, reinterpret_cast<const void*>(address), &value, sizeof(T), NULL);
return value;
}
template <typename T>
constexpr void Write(const uintptr_t& address, const T& value) const noexcept
{
WriteProcessMemory(pHandle, reinterpret_cast<void*>(address), &value, sizeof(T), NULL);
}
template <typename T> template <typename T>
bool TryRead(const uintptr_t& address, T& value) const noexcept bool TryRead(const uintptr_t& address, T& value) const noexcept
{ {
value = T{}; value = T{};
if (!pHandle || !address) if (!pHandle || !IsUserAddress(address))
return false; return false;
SIZE_T sizeRead = 0; SIZE_T sizeRead = 0;
return !!ReadProcessMemory(pHandle, reinterpret_cast<const void*>(address), &value, sizeof(T), &sizeRead) return !!ReadProcessMemory(pHandle, reinterpret_cast<const void*>(address), &value, sizeof(T), &sizeRead)
&& sizeRead == sizeof(T); && sizeRead == sizeof(T);
} }
template <typename T> template <typename T>
bool TryWrite(const uintptr_t& address, const T& value) const noexcept bool TryWrite(const uintptr_t& address, const T& value) const noexcept
{ {
if (!pHandle || !address) if (!pHandle || !IsUserAddress(address))
return false; return false;
SIZE_T sizeWritten = 0; SIZE_T sizeWritten = 0;
return !!WriteProcessMemory(pHandle, reinterpret_cast<void*>(address), &value, sizeof(T), &sizeWritten) return !!WriteProcessMemory(pHandle, reinterpret_cast<void*>(address), &value, sizeof(T), &sizeWritten)
&& sizeWritten == sizeof(T); && sizeWritten == sizeof(T);
} }
uintptr_t ReadChain(uintptr_t address, const std::vector<uintptr_t>& offsets) bool TryReadBuffer(const uintptr_t address, void* buffer, const SIZE_T size) const noexcept
{ {
if (!address || offsets.empty()) if (!pHandle || !IsUserAddress(address) || !buffer || size == 0)
return 0; return false;
SIZE_T sizeRead = 0;
return !!ReadProcessMemory(pHandle, reinterpret_cast<const void*>(address), buffer, size, &sizeRead)
&& sizeRead == size;
}
uintptr_t result = Read<uintptr_t>(address + offsets.front()); template <typename T>
for (std::size_t i = 1; i < offsets.size(); ++i) bool TryReadVector(const uintptr_t address, const std::size_t count, std::vector<T>& out,
{ const std::size_t hardLimit = 16384) const noexcept
if (!result) {
return 0; out.clear();
if (count == 0)
return true;
if (!IsUserAddress(address) || count > hardLimit || count > (std::numeric_limits<SIZE_T>::max)() / sizeof(T))
return false;
result = Read<uintptr_t>(result + offsets[i]); out.resize(count);
} if (!TryReadBuffer(address, out.data(), static_cast<SIZE_T>(count * sizeof(T))))
{
out.clear();
return false;
}
return true;
}
return result; // Compatibility helpers. Failed reads return zero-initialized values, while SDK code
} // should prefer TryRead when failure needs to be distinguished from a legitimate zero.
bool ReadString(uintptr_t address, LPVOID buffer, SIZE_T size) const template <typename T>
{ T Read(const uintptr_t& address) const noexcept
SIZE_T size_read; {
T value{};
TryRead(address, value);
return value;
}
return !!::ReadProcessMemory(pHandle, LPCVOID(address), buffer, size, &size_read) && size_read > 0; template <typename T>
} void Write(const uintptr_t& address, const T& value) const noexcept
std::string ReadString(uintptr_t address, int length) {
{ TryWrite(address, value);
if (!address || length <= 0) }
return {};
char Buffer[256]{}; uintptr_t ReadChain(uintptr_t address, const std::vector<uintptr_t>& offsets) const
if (length >= static_cast<int>(sizeof(Buffer))) {
length = static_cast<int>(sizeof(Buffer)) - 1; if (!IsUserAddress(address) || offsets.empty())
return 0;
if (!ReadProcessMemory(pHandle, reinterpret_cast<void*>(address), Buffer, length, nullptr)) uintptr_t result = 0;
return {}; if (!TryRead(address + offsets.front(), result) || !IsUserAddress(result))
return 0;
Buffer[length] = '\0'; for (std::size_t i = 1; i < offsets.size(); ++i)
return std::string(Buffer); {
}; uintptr_t next = 0;
wchar_t* ReadWString(uintptr_t address, int length) if (!TryRead(result + offsets[i], next) || !IsUserAddress(next))
{ return 0;
wchar_t Buffer[256]{}; result = next;
ReadProcessMemory(pHandle, (void*)(address), Buffer, (length * 2), nullptr); }
return result;
}
return Buffer; bool ReadString(uintptr_t address, LPVOID buffer, SIZE_T size) const
}; {
if (!pHandle || !IsUserAddress(address) || !buffer || size == 0)
return false;
SIZE_T sizeRead = 0;
return !!ReadProcessMemory(pHandle, reinterpret_cast<const void*>(address), buffer, size, &sizeRead) && sizeRead > 0;
}
std::string ReadString(uintptr_t address, int length) const
{
if (!IsUserAddress(address) || length <= 0)
return {};
if (length > 4096)
length = 4096;
std::vector<char> buffer(static_cast<std::size_t>(length) + 1, '\0');
SIZE_T sizeRead = 0;
if (!pHandle || !ReadProcessMemory(pHandle, reinterpret_cast<const void*>(address), buffer.data(), length, &sizeRead) || sizeRead == 0)
return {};
buffer[(std::min)(static_cast<std::size_t>(length), static_cast<std::size_t>(sizeRead))] = '\0';
return std::string(buffer.data());
}
std::wstring ReadWString(uintptr_t address, int length) const
{
if (!IsUserAddress(address) || length <= 0)
return {};
if (length > 4096)
length = 4096;
std::vector<wchar_t> buffer(static_cast<std::size_t>(length) + 1, L'\0');
const SIZE_T bytes = static_cast<SIZE_T>(length) * sizeof(wchar_t);
SIZE_T sizeRead = 0;
if (!pHandle || !ReadProcessMemory(pHandle, reinterpret_cast<const void*>(address), buffer.data(), bytes, &sizeRead) || sizeRead == 0)
return {};
buffer[(std::min)(static_cast<std::size_t>(length), static_cast<std::size_t>(sizeRead / sizeof(wchar_t)))] = L'\0';
return std::wstring(buffer.data());
}
}; };
extern Memory m; extern Memory m;

View File

@ -18,6 +18,8 @@ auto cheat = std::make_unique<CFramework>();
void Overlay::OverlayUserFunction() void Overlay::OverlayUserFunction()
{ {
// Publish UI/config state once per frame; worker threads never read mutable ImGui-bound globals directly.
cheat->UpdateWorkerConfigFromUi();
cheat->RenderInfo(); cheat->RenderInfo();
if (g.g_ESP) if (g.g_ESP)
@ -89,12 +91,18 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
} }
io.Fonts->Build(); io.Fonts->Build();
// スレッドを作成 // Worker threads are joinable. This prevents RPM/WPM from racing with DetachProcess on shutdown.
std::thread([&]() { cheat->UpdateList(); }).detach(); std::thread dynamicSnapshotThread([&]() { cheat->UpdateList(); });
std::thread([&]() { cheat->UpdateStaticList(); }).detach(); std::thread staticSnapshotThread([&]() { cheat->UpdateStaticList(); });
overlay->OverlayLoop(); overlay->OverlayLoop();
g.process_active = false; g.process_active = false;
if (dynamicSnapshotThread.joinable())
dynamicSnapshotThread.join();
if (staticSnapshotThread.joinable())
staticSnapshotThread.join();
cheat->ResetFeatureOverrides(); cheat->ResetFeatureOverrides();
overlay->DestroyOverlay(); overlay->DestroyOverlay();
m.DetachProcess(); m.DetachProcess();

View File

@ -24,7 +24,6 @@
| 尸体 ESP | 尸体标记 | ✅ | | 尸体 ESP | 尸体标记 | ✅ |
| 撤离点 ESP | 开启 / 未开启撤离点分色(绿 / 橙) | ✅ | | 撤离点 ESP | 开启 / 未开启撤离点分色(绿 / 橙) | ✅ |
| 手雷 ESP | 投掷物轨迹标记 + 屏幕顶部 `[ WARNING ] Grenade!!` 警告 | ✅ | | 手雷 ESP | 投掷物轨迹标记 + 屏幕顶部 `[ WARNING ] Grenade!!` 警告 | ✅ |
| 子弹追踪(只读) | 读取弹道快照(初速 / 速度 / 方向 / 落点),绘制弹道轨迹,**只读不改写** | ✅ |
| 最大距离 | `g_ESP_MaxDistance`(默认 500m) | ✅ | | 最大距离 | `g_ESP_MaxDistance`(默认 500m) | ✅ |
### 自瞄(AimBot) ### 自瞄(AimBot)
@ -72,12 +71,23 @@
运行前提:先启动 SPT-AKI 3.11.3 客户端(进程名为 `EscapeFromTarkov`),再以管理员身份运行 `CFramework.exe`。 运行前提:先启动 SPT-AKI 3.11.3 客户端(进程名为 `EscapeFromTarkov`),再以管理员身份运行 `CFramework.exe`。
程序通过窗口标题附加进程并创建覆盖层;若附加失败返回码 `1`,Overlay 初始化失败返回码 `2`。 程序通过窗口标题附加进程并创建覆盖层;若附加失败返回码 `1`,Overlay 初始化失败返回码 `2`。
## 当前数据架构(2026-08 重构)
- `CGame` 的 GameWorld / Camera 状态由动态数据线程统一刷新,并在内部用互斥锁保护。
- 动态玩家、相机矩阵和手雷数据发布为不可变 `DynamicWorldSnapshot`;撤离点和物资发布为 `StaticWorldSnapshot`。
- ESP 与 Aim 目标选择只消费 Snapshot,不在渲染阶段直接 RPM。真正的 Memory Aim / 功能 override 仍通过独立写入路径执行。
- `RegisteredPlayers` 按 `IPlayer` 处理,并区分完整 `EFT.Player` 与 `ObservedPlayerView` 两种布局。ObservedPlayer 无法从当前 dump 可靠确认的 Profile/Health/Role 字段不会强行套用。
- 后台线程不直接读取 ImGui 绑定的普通 `g_*` 变量;渲染线程通过 `WorkerConfigSnapshot` 发布所需配置。
- 玩家、物资、手雷列表使用运行时长度 + 上限校验的批量读取,不再使用固定 `address[64]` / `address[2048]` 缓冲。
- 工作线程可 join;退出时先停止并等待后台线程,再恢复功能写入和关闭进程句柄。
详细变更与验证结果见 [`REFACTOR_NOTES.md`](REFACTOR_NOTES.md)。
## 已知问题 ## 已知问题
- **CPU 占用偏高**:原版遗留问题,二次开发中已部分缓解但未根治。 - 当前包是在 Linux 容器中做源码级重构,环境没有 Visual Studio 2022 / Windows SDK,因此没有执行真正的 MSVC Release x64 链接构建。核心重构源文件已用 Clang + Windows API 测试桩做 `-fsyntax-only` 验证;最终仍建议在 VS2022 中执行一次 `Release x64` 全量构建。
- 已改善:实体 / 撤离点 / 物资列表刷新线程已降频(500ms ~ 5s 间隔轮询,见 `UpdateList.cpp`);主渲染循环开启了 VSync(`Present(1, 0)`),不会无限空转。 - `ObservedPlayerView` 的昵称、Role、实时 Health 等字段在当前 dump 中仍是混淆字段;本次实现采取保守降级显示,不猜测未知字段语义。
- 仍存在:主循环没有独立帧率上限,VSync 下跟着显示器刷新率跑(144Hz 屏即 144 FPS);每一帧都全量执行 `UpdateFeatures()`(耐力 / 跌落 / 摇摆 / 后座 / 故障五组 override 的内存读链 + 写入)、子弹追踪快照和 ESP 遍历渲染,没有脏标记或事件驱动缓存。高刷新率屏幕上占用依然明显。 - 主 Overlay 仍跟随 VSync/显示器刷新率;`UpdateFeatures()` 是渲染线程写功能路径,后续如果需要进一步降低 CPU/RPM,可以单独给写功能增加节流/脏标记。
- 需要进一步优化时,可从给主循环加帧率上限(如 60 FPS + `sleep_for`)或给 `UpdateFeatures` 加每 N 帧执行一次入手。
## 项目结构 ## 项目结构
@ -88,7 +98,7 @@ CFramework/
│ ├── main.cpp # 入口:附加进程、初始化 Overlay、字体、后台线程 │ ├── main.cpp # 入口:附加进程、初始化 Overlay、字体、后台线程
│ ├── Cheat/ # 功能实现 │ ├── Cheat/ # 功能实现
│ │ ├── Aim.cpp # 自瞄 + 压枪(Mouse / Memory) │ │ ├── Aim.cpp # 自瞄 + 压枪(Mouse / Memory)
│ │ ├── Visual.cpp # ESP 渲染(玩家/物资/撤离点/手雷/子弹轨迹) │ │ ├── Visual.cpp # ESP 渲染(玩家/物资/撤离点/手雷)
│ │ ├── Features.cpp # 耐力/跌落/摇摆/后座/故障 override │ │ ├── Features.cpp # 耐力/跌落/摇摆/后座/故障 override
│ │ ├── Menu.cpp # ImGui 菜单(中文) │ │ ├── Menu.cpp # ImGui 菜单(中文)
│ │ ├── Render.cpp / UpdateList.cpp │ │ ├── Render.cpp / UpdateList.cpp

72
REFACTOR_NOTES.md Normal file
View File

@ -0,0 +1,72 @@
# 2026-08 External Data-Layer Refactor
本次修改以当前 `CFramework` 外部框架为基础,没有重新迁移 Overlay / ImGui / Camera / Transform。目标是修复共享状态竞争、固定数组越界和新版本 `IPlayer` 布局差异,并让渲染/目标选择与目标进程读取解耦。
## 已完成
1. **CGame 状态同步**
- GameWorld / LocalGameWorld / FPS Camera / ViewMatrix 统一由 `CGame::Refresh()` 刷新。
- 内部状态使用 mutex,消费者通过 `GameStateSnapshot` 读取一致副本。
- 移除渲染线程每帧调用 `InitAddress()` 的行为。
2. **不可变世界快照**
- 新增 `DynamicWorldSnapshot`:本地玩家、远端玩家、Camera/ViewMatrix、Grenade。
- 新增 `StaticWorldSnapshot`:Exfil、Item。
- 使用 `shared_ptr<const ...>` + `atomic_load/atomic_store` 发布,消费者不会看到构建到一半的容器。
3. **ESP / Aim 数据读取解耦**
- `RenderESP()` 不再直接 RPM,也不再调用 Player Update/Health/Bone。
- Aim 目标筛选和骨骼选择只读取动态快照。
- Memory Aim 与 Misc override 继续保留为明确的写入路径。
4. **玩家类型适配**
- 新增 `CPlayerKind::{EftPlayer, ObservedPlayer}`。
- 完整玩家使用当前 dump 中的 `Player + 0x618 Profile`、`+0x660 HealthController`、`+0x6E8 PlayerBones` 等链。
- ObservedPlayer 使用当前 dump 可确认的 `+0x60 PlayerBones`、`+0x68 PlayerBody`、`+0xB8 Transform`、`+0x100 Side`。
- 对 dump 未确认的 Observed Profile/Health/Role 不做猜测。
5. **列表越界修复**
- 移除固定 `CGameObjectList::address[64]` 和 Item `address[2048]` 读取方式。
- Managed List/Array 使用实际 count/length + hard limit + `TryReadVector`。
- 当前 hard limit:Player 512、Item 8192、Grenade 256、Exfil 32。
6. **Memory API 加固**
- 增加 `IsUserAddress`、`TryRead`、`TryWrite`、`TryReadBuffer`、`TryReadVector`。
- `ReadChain` 增加地址校验。
- 修复旧 `ReadWString` 返回临时缓冲地址的问题。
- `DetachProcess()` 会关闭并清空句柄/PID/Base。
7. **Worker 配置同步**
- `g_*` 仍保留为 ImGui 绑定配置。
- 渲染线程通过 `UpdateWorkerConfigFromUi()` 发布 `WorkerConfigSnapshot`。
- 后台列表线程仅读取该受锁保护的配置副本,不与 UI 裸共享普通 bool/float。
8. **退出生命周期**
- 动态/静态数据线程不再 detach。
- Overlay 退出后设置原子 `process_active=false`,join 两个线程,再 Reset overrides / Destroy overlay / Detach process。
9. **其他边界修复**
- Exfil 名称读取增加地址、长度和缓冲区限制,并修复 status 未正确缓存的问题。
- Unity Transform、Camera、Managed Array/List 等常用裸偏移集中到 `Offset.h`。
- `min` 调用改为显式 `std::min`,降低 Windows 宏依赖。
- README 移除源码中不存在的“子弹追踪(只读)已完成”声明。
## 验证
在当前容器里对以下核心文件执行了 Clang C++17 `-fsyntax-only` 静态语法验证(Windows API 使用最小测试桩;内置 UTF-16 字体头仅在临时测试副本中转换编码):
- `Cheat/UpdateList.cpp`
- `Cheat/Visual.cpp`
- `Cheat/Aim.cpp`
- `Cheat/Features.cpp`
- `Cheat/Menu.cpp`
- `Cheat/SDK/GameSDK/GameSDK.cpp`
- `Cheat/SDK/CPlayer/CPlayer.cpp`
- `Cheat/SDK/CExfil/CExfil.cpp`
- `Cheat/SDK/CItem/CItem.cpp`
- `Cheat/Render.cpp`
- `Framework/Memory/Memory.cpp`(仅有测试桩下的 `NULL -> DWORD` 警告)
以上文件均通过语法检查。
当前环境没有 Visual Studio 2022、MSVC v143、Windows SDK 和 DX11/DWM 链接环境,因此**没有声称已经完成真实 Windows Release 链接/运行验证**。建议拿到包后第一步在 VS2022 中执行 `Release | x64` 全量 Rebuild,并根据实际 SPT/EFT build 对照最新 dump 校验根指针和版本敏感 offset。