refactor: harden external data layer
This commit is contained in:
parent
70b391a9e9
commit
390450e8e8
|
|
@ -1,4 +1,4 @@
|
|||
#include "Framework.h"
|
||||
#include "Framework.h"
|
||||
#include "../Framework/Input/Input.h"
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
|
@ -95,25 +95,23 @@ namespace
|
|||
|
||||
void CFramework::AimAll()
|
||||
{
|
||||
// メニュー表示中は何もしない (メニュー操作と干渉するため)
|
||||
if (g.ShowMenu)
|
||||
return;
|
||||
|
||||
if (!tarkov->IsInRaid() || local.m_address == 0)
|
||||
const auto snapshot = GetDynamicSnapshot();
|
||||
if (!snapshot || !snapshot->inRaid || !snapshot->cameraValid || !snapshot->local.IsValid())
|
||||
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;
|
||||
|
||||
const Vector2 GameSize = Vector2((float)g.GameRect.right, (float)g.GameRect.bottom);
|
||||
if (GameSize.x <= 0.f || GameSize.y <= 0.f)
|
||||
return;
|
||||
|
||||
// ---- 压枪: 射撃中にマウスを下方向へ ----
|
||||
// Recoil mouse compensation remains render-thread local and does not touch the world snapshot.
|
||||
if (g.g_Recoil && g.g_RecoilStrength > 0.f && IsKeyDown(VK_LBUTTON))
|
||||
{
|
||||
static DWORD s_lastPull = 0;
|
||||
DWORD now = GetTickCount();
|
||||
const DWORD now = GetTickCount();
|
||||
if (now - s_lastPull >= (DWORD)g.g_RecoilInterval)
|
||||
{
|
||||
SendMouseMove(0, (LONG)g.g_RecoilStrength);
|
||||
|
|
@ -121,7 +119,6 @@ void CFramework::AimAll()
|
|||
}
|
||||
}
|
||||
|
||||
// ---- 自瞄 ----
|
||||
if (!g.g_Aim)
|
||||
{
|
||||
m_aimLastTarget = 0;
|
||||
|
|
@ -130,14 +127,10 @@ void CFramework::AimAll()
|
|||
return;
|
||||
}
|
||||
|
||||
// FOV 円はキーを押していなくても表示
|
||||
Vector2 Center = Vector2(GameSize.x * 0.5f, GameSize.y * 0.5f);
|
||||
const Vector2 center(gameSize.x * 0.5f, gameSize.y * 0.5f);
|
||||
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))
|
||||
{
|
||||
m_aimLastTarget = 0;
|
||||
|
|
@ -146,85 +139,67 @@ void CFramework::AimAll()
|
|||
return;
|
||||
}
|
||||
|
||||
// カメラ位置 (ローカル頭部を優先、無ければ origin)
|
||||
local.UpdateBone();
|
||||
Vector3 localPos = !Vec3_Empty(local.m_vecHeadOrigin) ? local.m_vecHeadOrigin : local.m_vecOrigin;
|
||||
const Vector3 localPos = !Vec3_Empty(localPlayer.m_vecHeadOrigin) ? localPlayer.m_vecHeadOrigin : localPlayer.m_vecOrigin;
|
||||
|
||||
Matrix ViewMatrix = tarkov->GetViewMatrix();
|
||||
float bestDist = g.g_AimFov;
|
||||
Vector3 bestPoint{};
|
||||
Vector2 bestScreen{};
|
||||
uintptr_t bestAddr = 0;
|
||||
|
||||
// 1. ターゲット選択: 画面中心に最も近い敵 (FOV 円内)
|
||||
float bestDist = g.g_AimFov;
|
||||
Vector3 bestPoint{};
|
||||
Vector2 bestScreen{};
|
||||
uintptr_t bestAddr = 0;
|
||||
m_diagTotal = 0;
|
||||
m_diagHealth = 0;
|
||||
m_diagSkipped = 0;
|
||||
|
||||
m_diagTotal = 0; m_diagHealth = 0; m_diagSkipped = 0;
|
||||
|
||||
for (auto& entity : GetEntityList())
|
||||
for (const auto& entity : snapshot->players)
|
||||
{
|
||||
CPlayer* pEntity = &entity;
|
||||
|
||||
if (pEntity->m_address == 0 || pEntity->m_address == local.m_address)
|
||||
if (!entity.IsValid() || entity.m_address == localPlayer.m_address || Vec3_Empty(entity.m_vecOrigin))
|
||||
continue;
|
||||
|
||||
m_diagTotal++;
|
||||
++m_diagTotal;
|
||||
|
||||
if (!pEntity->Update())
|
||||
if (GetDistance(localPlayer.m_vecOrigin, entity.m_vecOrigin) > g.g_AimMaxDist)
|
||||
{
|
||||
m_diagSkipped++;
|
||||
++m_diagSkipped;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 距離チェック
|
||||
float dist = GetDistance(local.m_vecOrigin, pEntity->m_vecOrigin);
|
||||
if (dist > g.g_AimMaxDist)
|
||||
if (!IsValidAimTarget(entity))
|
||||
{
|
||||
m_diagSkipped++;
|
||||
++m_diagSkipped;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 種別フィルタ (PMC / Scav / Boss)
|
||||
if (!IsValidAimTarget(pEntity->m_iSpawnType))
|
||||
// Only reject a target on health when the health chain was actually resolved.
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 生存チェック (死体はスキップ)
|
||||
// ※ Mono版で HealthController の offset が違うと全員死亡扱いになる。
|
||||
// g_AimIgnoreHealth で判定をスキップ可能。
|
||||
if (!g.g_AimIgnoreHealth)
|
||||
{
|
||||
pEntity->UpdateHealth();
|
||||
if (pEntity->m_fHealth <= 0.f)
|
||||
{
|
||||
m_diagHealth++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 狙うボーン
|
||||
Vector3 aim = GetAimBone(pEntity);
|
||||
const Vector3 aim = GetAimBone(&entity);
|
||||
if (Vec3_Empty(aim))
|
||||
{
|
||||
m_diagSkipped++;
|
||||
++m_diagSkipped;
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 s{};
|
||||
if (!WorldToScreen(ViewMatrix, GameSize, aim, s))
|
||||
Vector2 screen{};
|
||||
if (!WorldToScreen(snapshot->viewMatrix, gameSize, aim, screen))
|
||||
{
|
||||
m_diagSkipped++;
|
||||
++m_diagSkipped;
|
||||
continue;
|
||||
}
|
||||
|
||||
float d = sqrtf((s.x - Center.x) * (s.x - Center.x) + (s.y - Center.y) * (s.y - Center.y));
|
||||
if (d < bestDist)
|
||||
const float dx = screen.x - center.x;
|
||||
const float dy = screen.y - center.y;
|
||||
const float distanceToCenter = sqrtf(dx * dx + dy * dy);
|
||||
if (distanceToCenter < bestDist)
|
||||
{
|
||||
bestDist = d;
|
||||
bestAddr = pEntity->m_address;
|
||||
bestDist = distanceToCenter;
|
||||
bestAddr = entity.m_address;
|
||||
bestPoint = aim;
|
||||
bestScreen = s;
|
||||
bestScreen = screen;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,35 +221,38 @@ void CFramework::AimAll()
|
|||
m_aimResidualY = 0.f;
|
||||
}
|
||||
|
||||
// 2. 照準適用
|
||||
if (g.g_AimMethod == 0)
|
||||
{
|
||||
m_aimResidualX = 0.f;
|
||||
m_aimResidualY = 0.f;
|
||||
AimMemory(bestPoint, localPos);
|
||||
AimMemory(bestPoint, localPos, localPlayer.m_address);
|
||||
}
|
||||
else
|
||||
{
|
||||
AimMouse(bestScreen, Center);
|
||||
AimMouse(bestScreen, center);
|
||||
}
|
||||
|
||||
// 3. ターゲット線
|
||||
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_USEC_PvE:
|
||||
case PMC_BEAR_NEW:
|
||||
case PMC_USEC_NEW:
|
||||
return g.g_AimTargetPMC;
|
||||
|
||||
// Scav / Raider / Cultist / Partisan / Rogue
|
||||
case SCAV:
|
||||
case SNIPER_SCAV:
|
||||
case NORMAL_SCAV:
|
||||
|
|
@ -286,7 +264,6 @@ bool CFramework::IsValidAimTarget(const int& SpawnType)
|
|||
case BLOODHOUND_SCAV:
|
||||
return g.g_AimTargetScav;
|
||||
|
||||
// Boss / Follower / Goons
|
||||
case RESHALA_BOSS:
|
||||
case RESHALA_FOLLOW:
|
||||
case KILLA_BOSS:
|
||||
|
|
@ -314,22 +291,23 @@ bool CFramework::IsValidAimTarget(const int& SpawnType)
|
|||
case BRIDEYE_SCAV:
|
||||
return g.g_AimTargetBoss;
|
||||
|
||||
// 乗り物・不明種別は撃たない
|
||||
case BTR_VEHICLE:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 CFramework::GetAimBone(CPlayer* pEntity)
|
||||
Vector3 CFramework::GetAimBone(const CPlayer* pEntity)
|
||||
{
|
||||
// Head / Neck を更新
|
||||
pEntity->UpdateBone();
|
||||
if (!pEntity)
|
||||
return {};
|
||||
|
||||
switch (g.g_AimBone)
|
||||
{
|
||||
case 1: return pEntity->m_vecNeckOrigin;
|
||||
case 2: return pEntity->GetBonePosition(BONE_SPINE_3); // Ribcage
|
||||
case 1:
|
||||
return pEntity->m_vecNeckOrigin;
|
||||
case 2:
|
||||
return pEntity->m_vecSpineOrigin;
|
||||
case 0:
|
||||
default:
|
||||
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) へ書く
|
||||
// オフセットは 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_AimRotOffset < 0 || g.g_AimRotOffset > 0x1000)
|
||||
{
|
||||
|
|
@ -401,7 +379,7 @@ void CFramework::AimMemory(const Vector3& target, const Vector3& localPos)
|
|||
}
|
||||
|
||||
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))
|
||||
{
|
||||
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 maxAngleStep = 0.10f + (1.f - smooth) * 0.12f;
|
||||
if (angleError > maxAngleStep)
|
||||
t = min(t, maxAngleStep / angleError);
|
||||
t = (std::min)(t, maxAngleStep / angleError);
|
||||
|
||||
Vector2 newRot{
|
||||
ClampAngle(rot.x + dyaw * t),
|
||||
|
|
@ -467,7 +445,9 @@ void CFramework::ScanRotationOffset()
|
|||
|
||||
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 再点)");
|
||||
m_scanPhase = 0;
|
||||
|
|
@ -482,7 +462,7 @@ void CFramework::ScanRotationOffset()
|
|||
// Player 构造 [0x20, 0x700] 内的 8byte アライン済みポインタ
|
||||
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)
|
||||
continue;
|
||||
|
||||
|
|
@ -524,7 +504,7 @@ void CFramework::ScanRotationOffset()
|
|||
|
||||
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)
|
||||
continue;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include "Framework.h"
|
||||
#include "Framework.h"
|
||||
#include <cmath>
|
||||
|
||||
// 只写入 dump 明确的对象字段;后座与故障也只使用本轮已确认的链路,弹道追踪暂不写。
|
||||
|
|
@ -196,21 +196,24 @@ namespace
|
|||
|
||||
void CFramework::UpdateFeatures()
|
||||
{
|
||||
if (!tarkov->IsInRaid() || !IsUsableAddress(tarkov->m_pLocalGameWorld))
|
||||
const auto snapshot = GetDynamicSnapshot();
|
||||
if (!snapshot || !snapshot->inRaid || !snapshot->local.IsValid())
|
||||
{
|
||||
local = {};
|
||||
ResetFeatureOverrides();
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh MainPlayer from the current LocalGameWorld instead of trusting a stale list-thread cache.
|
||||
uintptr_t currentLocal = 0;
|
||||
if (!m.TryRead(tarkov->m_pLocalGameWorld + offset::MainPlayer, currentLocal) ||
|
||||
!IsUsableAddress(currentLocal))
|
||||
// The list thread publishes local player state atomically. Keep a render-thread-only
|
||||
// scratch copy for feature writers so no background thread can zero/change it mid-write.
|
||||
local = snapshot->local;
|
||||
const uintptr_t currentLocal = local.m_address;
|
||||
if (!IsUsableAddress(currentLocal))
|
||||
{
|
||||
local = {};
|
||||
ResetFeatureOverrides();
|
||||
return;
|
||||
}
|
||||
local.m_address = currentLocal;
|
||||
|
||||
if (g.g_InfStamina)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include <algorithm>
|
||||
#include "../Framework/ImGui/imgui.h"
|
||||
#include "../Framework/ImGui/imgui_impl_win32.h"
|
||||
#include "../Framework/ImGui/imgui_impl_dx11.h"
|
||||
|
|
@ -10,6 +11,51 @@
|
|||
#include "SDK/CExfil/CExfil.h"
|
||||
#include "SDK/CItem/CItem.h"
|
||||
#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
|
||||
{
|
||||
|
|
@ -24,6 +70,7 @@ public:
|
|||
void AimAll();
|
||||
void UpdateFeatures();
|
||||
void ResetFeatureOverrides();
|
||||
void UpdateWorkerConfigFromUi();
|
||||
private:
|
||||
struct StaminaOverrideState
|
||||
{
|
||||
|
|
@ -94,9 +141,43 @@ private:
|
|||
void ResetFallDamageOverride();
|
||||
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;
|
||||
|
||||
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{};
|
||||
FallDamageOverrideState m_fallDamageOverride{};
|
||||
SwayOverrideState m_swayOverride{};
|
||||
|
|
@ -104,26 +185,21 @@ private:
|
|||
NoMalfunctionOverrideState m_noMalfunctionOverride{};
|
||||
|
||||
|
||||
std::mutex m_mtxEntityList;
|
||||
std::mutex m_mtxExfilList;
|
||||
std::mutex m_mtxItemList;
|
||||
|
||||
std::vector<CPlayer> m_vecEntityList;
|
||||
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;
|
||||
// Compatibility accessors for menu/legacy call sites. New render/aim code consumes immutable snapshots directly.
|
||||
std::vector<CPlayer> GetEntityList() const
|
||||
{
|
||||
auto snapshot = GetDynamicSnapshot();
|
||||
return snapshot ? snapshot->players : std::vector<CPlayer>{};
|
||||
}
|
||||
std::vector<CExfil> GetExfilList() {
|
||||
std::lock_guard<std::mutex> lock(m_mtxExfilList);
|
||||
return m_vecExfilList;
|
||||
std::vector<CExfil> GetExfilList() const
|
||||
{
|
||||
auto snapshot = GetStaticSnapshot();
|
||||
return snapshot ? snapshot->exfils : std::vector<CExfil>{};
|
||||
}
|
||||
std::vector<CItem> GetItemList() {
|
||||
std::lock_guard<std::mutex> lock(m_mtxItemList);
|
||||
return m_vecItemList;
|
||||
std::vector<CItem> GetItemList() const
|
||||
{
|
||||
auto snapshot = GetStaticSnapshot();
|
||||
return snapshot ? snapshot->items : std::vector<CItem>{};
|
||||
}
|
||||
|
||||
// Colors
|
||||
|
|
@ -153,10 +229,10 @@ private:
|
|||
void GetESPInfo(const int& SpawnType, std::string& vOutStr, ImColor& vOutColor);
|
||||
|
||||
// Aim.cpp
|
||||
bool IsValidAimTarget(const int& SpawnType);
|
||||
Vector3 GetAimBone(CPlayer* pEntity);
|
||||
bool IsValidAimTarget(const CPlayer& player);
|
||||
Vector3 GetAimBone(const CPlayer* pEntity);
|
||||
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 偏移
|
||||
|
||||
// Runtime feature backup state is declared above.
|
||||
|
|
@ -192,7 +268,7 @@ private:
|
|||
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 / 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)
|
||||
{
|
||||
|
|
@ -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 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);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include "Framework.h"
|
||||
#include "Framework.h"
|
||||
#include "../Framework/Input/Input.h"
|
||||
|
||||
// ImGui::Combo/ImGui::List等で使う文字列群
|
||||
|
|
@ -13,6 +13,10 @@ void CFramework::RenderMenu()
|
|||
{
|
||||
// Setup
|
||||
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::SetNextWindowSize(ImVec2(725.f, 450.f));
|
||||
|
|
@ -197,9 +201,9 @@ void CFramework::RenderMenu()
|
|||
|
||||
// 数据链路状态: 哪一环断了看这里
|
||||
ImGui::Text("自瞄状态");
|
||||
ImGui::Text("局内 : %s", tarkov->IsInRaid() ? "是" : "否");
|
||||
ImGui::Text("本地 : 0x%llX", (unsigned long long)local.m_address);
|
||||
ImGui::Text("实体 : %zu", GetEntityList().size());
|
||||
ImGui::Text("局内 : %s", runtimeInRaid ? "是" : "否");
|
||||
ImGui::Text("本地 : 0x%llX", (unsigned long long)runtimeLocalAddress);
|
||||
ImGui::Text("实体 : %zu", runtimeEntityCount);
|
||||
ImGui::Text("扫描 : %d 实体 / %d 血量 / %d 跳过", m_diagTotal, m_diagHealth, m_diagSkipped);
|
||||
ImGui::Text("目标 : %s (距 %0.fpx)", m_diagBestAddr ? "锁定" : "无", m_diagBestDist);
|
||||
{
|
||||
|
|
@ -214,9 +218,9 @@ void CFramework::RenderMenu()
|
|||
ImGui::Separator();
|
||||
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)
|
||||
{
|
||||
Vector2 rot = m.Read<Vector2>(mc + (uintptr_t)g.g_AimRotOffset);
|
||||
|
|
@ -316,8 +320,8 @@ void CFramework::RenderMenu()
|
|||
ImGui::Text("功能状态");
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("局内:%s", tarkov->IsInRaid() ? "是" : "否");
|
||||
ImGui::Text("本地:0x%llX", (unsigned long long)local.m_address);
|
||||
ImGui::Text("局内:%s", runtimeInRaid ? "是" : "否");
|
||||
ImGui::Text("本地:0x%llX", (unsigned long long)runtimeLocalAddress);
|
||||
ImGui::TextDisabled("关闭开关或离开局内会恢复原值");
|
||||
ImGui::TextDisabled("非法/不完整指针链不会写入");
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,45 +1,56 @@
|
|||
#include "CExfil.h"
|
||||
#include "CExfil.h"
|
||||
#include <algorithm>
|
||||
|
||||
bool CExfil::GetAddress(uintptr_t& address)
|
||||
{
|
||||
ptr = address;
|
||||
return ptr == NULL ? false : true;
|
||||
ptr = address;
|
||||
return Memory::IsUserAddress(ptr);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
// Name
|
||||
uintptr_t eSetting = m.Read<uintptr_t>(ptr + 0x78);
|
||||
uintptr_t NamePtr = m.Read<uintptr_t>(eSetting + 0x18);
|
||||
uintptr_t settings = 0;
|
||||
uintptr_t namePtr = 0;
|
||||
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)
|
||||
return std::string();
|
||||
|
||||
char pName[64]{};
|
||||
int length = m.Read<int>(NamePtr + 0x10);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
pName[i] = m.Read<char>(NamePtr + 0x14 + (i * 0x2));
|
||||
|
||||
return pName;
|
||||
length = (std::min)(length, 127);
|
||||
std::string name;
|
||||
name.reserve(static_cast<std::size_t>(length));
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
wchar_t ch = 0;
|
||||
if (!m.TryRead(namePtr + offset::ManagedString::Data + static_cast<uintptr_t>(i) * offset::ManagedString::CharStride, ch))
|
||||
break;
|
||||
if (ch >= 0x20 && ch <= 0x7E)
|
||||
name.push_back(static_cast<char>(ch));
|
||||
else if (ch != 0)
|
||||
name.push_back('?');
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
bool CExfil::Update()
|
||||
{
|
||||
if (this->GetStatus() == CLOSE)
|
||||
return false;
|
||||
m_pExfilStatus = GetStatus();
|
||||
if (m_pExfilStatus == CLOSE)
|
||||
return false;
|
||||
|
||||
// Position
|
||||
uintptr_t TransformInternal = m.ReadChain(ptr, { 0x10, 0x30, 0x30, 0x8, 0x28, 0x10 });
|
||||
m_pVecLocation = GetTransformPosition(TransformInternal);
|
||||
|
||||
if (Vec3_Empty(m_pVecLocation))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
const uintptr_t transform = m.ReadChain(ptr,
|
||||
{ offset::UnityComponent::Step0, offset::UnityComponent::Step1, offset::UnityComponent::Step2,
|
||||
offset::UnityComponent::Step3, offset::UnityComponent::Step4, offset::UnityComponent::Transform });
|
||||
m_pVecLocation = GetTransformPosition(transform);
|
||||
return !Vec3_Empty(m_pVecLocation);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,111 +1,47 @@
|
|||
#include "CItem.h"
|
||||
|
||||
// Example
|
||||
struct C_EFT_ItemData
|
||||
{
|
||||
int item_price;
|
||||
std::string item_name;
|
||||
std::string item_id;
|
||||
int item_price;
|
||||
std::string item_name;
|
||||
std::string item_id;
|
||||
};
|
||||
|
||||
// std::map
|
||||
std::vector<C_EFT_ItemData>C_LootList =
|
||||
std::vector<C_EFT_ItemData> C_LootList =
|
||||
{
|
||||
{
|
||||
2500000,
|
||||
"Red Rebel",
|
||||
"5c0126f40db834002a125382"
|
||||
},
|
||||
{
|
||||
20000000,
|
||||
"Labs keycard (Red)",
|
||||
"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"
|
||||
}
|
||||
{ 2500000, "Red Rebel", "5c0126f40db834002a125382" },
|
||||
{ 20000000, "Labs keycard (Red)", "5c1d0efb86f7744baf2e7b7b" },
|
||||
{ 20000000, "Labs keycard (Blue)", "1d0c5f86f7744bb2683cf0" },
|
||||
{ 20000000, "Labs keycard (Green)", "1d0dc586f7744baf2e7b79" },
|
||||
{ 20000000, "Labs keycard (Violet)", "1e495a86f7743109743dfb" },
|
||||
{ 20000000, "Labs keycard (Black)", "1d0f4986f7744bb01837fa" },
|
||||
{ 20000000, "Labs keycard (Yellow)", "1d0d6d86f7744bb2683e1f" },
|
||||
{ 65000, "0.2 BTC", "faff1d86f7746c51718c9c" }
|
||||
};
|
||||
|
||||
bool CItem::GetAddress(uintptr_t& ptr)
|
||||
{
|
||||
address = ptr;
|
||||
return address == NULL ? false : true;
|
||||
address = ptr;
|
||||
return Memory::IsUserAddress(address);
|
||||
}
|
||||
|
||||
bool CItem::Update()
|
||||
{
|
||||
// Position
|
||||
uintptr_t TransformInternal = m.ReadChain(address, { 0x10, 0x30, 0x30, 0x8, 0x28, 0x10 });
|
||||
m_vecOrigin = GetTransformPosition(TransformInternal);
|
||||
m_bIsCorpse = false;
|
||||
m_vecOrigin = {};
|
||||
|
||||
if (Vec3_Empty(m_vecOrigin))
|
||||
return false;
|
||||
|
||||
// ClassName
|
||||
uintptr_t nameAddr = m.ReadChain(address, { 0x0, 0x0, 0x48 });
|
||||
std::string className = m.ReadString(nameAddr, 64);
|
||||
const uintptr_t transform = m.ReadChain(address,
|
||||
{ offset::UnityComponent::Step0, offset::UnityComponent::Step1, offset::UnityComponent::Step2,
|
||||
offset::UnityComponent::Step3, offset::UnityComponent::Step4, offset::UnityComponent::Transform });
|
||||
m_vecOrigin = GetTransformPosition(transform);
|
||||
if (Vec3_Empty(m_vecOrigin))
|
||||
return false;
|
||||
|
||||
// コンテナ/死体ではなかったら
|
||||
if (className.compare("ObservedCorpse") && className.compare("Corpse"))
|
||||
{
|
||||
/*
|
||||
// ID
|
||||
char ItemID[64]{};
|
||||
uintptr_t BsgId = m.ReadChain(address, { 0x10, 0x28, 0xB0, 0x40, 0x50 });
|
||||
// Keep the legacy class-name chain from the original project, but validate every link.
|
||||
const uintptr_t nameAddr = m.ReadChain(address, { 0x0, 0x0, 0x48 });
|
||||
const std::string className = m.ReadString(nameAddr, 64);
|
||||
if (className == "ObservedCorpse" || className == "Corpse")
|
||||
m_bIsCorpse = 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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,78 @@
|
|||
#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
|
||||
{
|
||||
uintptr_t ReadBifacialOriginal(uintptr_t playerBones, uintptr_t fieldOffset)
|
||||
{
|
||||
const uintptr_t bifacialTransform = m.Read<uintptr_t>(playerBones + fieldOffset);
|
||||
return bifacialTransform
|
||||
? m.Read<uintptr_t>(bifacialTransform + offset::BifacialTransform::Original)
|
||||
: 0;
|
||||
uintptr_t bifacialTransform = 0;
|
||||
uintptr_t original = 0;
|
||||
if (!Memory::IsUserAddress(playerBones) ||
|
||||
!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)
|
||||
{
|
||||
if (!array || index < 0 || index > 1)
|
||||
if (!Memory::IsUserAddress(array) || index < 0 || index > 1)
|
||||
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)
|
||||
{
|
||||
if (!transform)
|
||||
if (!Memory::IsUserAddress(transform))
|
||||
return {};
|
||||
|
||||
Vector3 position = GetTransformPosition(transform);
|
||||
if (!Vec3_Empty(position))
|
||||
return position;
|
||||
|
||||
// Keep compatibility with the previous Transform wrapper layout.
|
||||
uintptr_t wrappedTransform = m.Read<uintptr_t>(transform + 0x10);
|
||||
if (!wrappedTransform || wrappedTransform == transform)
|
||||
// Compatibility with older Transform wrappers used by the original project.
|
||||
uintptr_t wrappedTransform = 0;
|
||||
if (!m.TryRead(transform + 0x10, wrappedTransform) || !Memory::IsUserAddress(wrappedTransform) || wrappedTransform == transform)
|
||||
return {};
|
||||
|
||||
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()
|
||||
{
|
||||
m_vecOrigin = GetBonePosition(BONE_ORIGIN);
|
||||
|
||||
return !Vec3_Empty(m_vecOrigin);
|
||||
}
|
||||
|
||||
void CPlayer::UpdateStatic()
|
||||
bool CPlayer::UpdateStatic(CPlayerKind hint)
|
||||
{
|
||||
m_kind = CPlayerKind::Unknown;
|
||||
m_pHealthController = 0;
|
||||
m_pProfile = 0;
|
||||
m_pInfo = 0;
|
||||
|
|
@ -54,102 +80,169 @@ void CPlayer::UpdateStatic()
|
|||
m_pPlayerBody = 0;
|
||||
m_pPlayerBones = 0;
|
||||
m_pWeaponAnimation = 0;
|
||||
m_pObservedTransform = 0;
|
||||
m_iSpawnType = 0;
|
||||
m_iSide = 0;
|
||||
m_bSpawnTypeValid = false;
|
||||
m_bHealthValid = false;
|
||||
m_bIsAiming = false;
|
||||
m_pBoneArray = {};
|
||||
m_vecBoneList.clear();
|
||||
|
||||
if (!m_address)
|
||||
return;
|
||||
if (!Memory::IsUserAddress(m_address))
|
||||
return false;
|
||||
|
||||
// EFT.Player fields from dump.txt.
|
||||
m_pHealthController = m.Read<uintptr_t>(m_address + offset::Player::pHealthController);
|
||||
m_pWeaponAnimation = m.Read<uintptr_t>(m_address + offset::Player::pWeaponAnimation);
|
||||
if (hint == CPlayerKind::EftPlayer || (hint == CPlayerKind::Unknown && LooksLikeEftPlayer(m_address)))
|
||||
m_kind = CPlayerKind::EftPlayer;
|
||||
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_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)
|
||||
if (m_kind == CPlayerKind::EftPlayer)
|
||||
{
|
||||
m_pInfo = m.Read<uintptr_t>(m_pProfile + offset::Profile::pInfoClass);
|
||||
if (m_pInfo)
|
||||
m.TryRead(m_address + offset::Player::pHealthController, m_pHealthController);
|
||||
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);
|
||||
if (m_pSetting)
|
||||
m_iSpawnType = m.Read<int>(m_pSetting + 0x10); // ProfileInfoSettingsClass.Role
|
||||
m.TryRead(m_pInfo + offset::InfoClass::eplayerSide_0, m_iSide);
|
||||
if (m.TryRead(m_pInfo + offset::InfoClass::profileInfoSettingsClass, m_pSetting) && Memory::IsUserAddress(m_pSetting))
|
||||
{
|
||||
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;
|
||||
// List +0x10 = Transform[] data pointer, as described by dump.txt.
|
||||
const uintptr_t boneMatrix = m.ReadChain(m_address, { offset::Player::pPlayerBody, offset::PlayerBody::SkeletonRootJoint, 0x30, 0x10 });
|
||||
if (boneMatrix)
|
||||
m_pBoneArray = m.Read<CBoneArray>(boneMatrix + 0x20);
|
||||
// Skeleton.values fallback. This is optional; direct PlayerBones fields are preferred.
|
||||
if (Memory::IsUserAddress(m_pPlayerBody))
|
||||
{
|
||||
const uintptr_t boneMatrix = m.ReadChain(m_pPlayerBody, { offset::PlayerBody::SkeletonRootJoint, offset::Skeleton::Values, offset::Skeleton::ValuesArray });
|
||||
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()
|
||||
{
|
||||
m_fHealth = 0.f;
|
||||
m_fHealthMax = 0.f;
|
||||
m_bHealthValid = false;
|
||||
|
||||
if (!m_address)
|
||||
if (m_kind != CPlayerKind::EftPlayer || !Memory::IsUserAddress(m_address))
|
||||
return;
|
||||
|
||||
const uintptr_t healthController = m_pHealthController != 0
|
||||
? m_pHealthController
|
||||
: m.Read<uintptr_t>(m_address + offset::Player::pHealthController);
|
||||
if (!healthController)
|
||||
uintptr_t healthController = m_pHealthController;
|
||||
if (!Memory::IsUserAddress(healthController) &&
|
||||
(!m.TryRead(m_address + offset::Player::pHealthController, healthController) || !Memory::IsUserAddress(healthController)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// GClass3009<T>.Dictionary_0_1 contains BodyPartState<T> entries.
|
||||
const uintptr_t bodyParts = m.Read<uintptr_t>(healthController + offset::HealthController::BodyParts);
|
||||
if (!bodyParts)
|
||||
return;
|
||||
|
||||
const uintptr_t entries = m.Read<uintptr_t>(bodyParts + offset::HealthController::DictionaryEntries);
|
||||
const int entryCount = m.Read<int>(bodyParts + offset::HealthController::DictionaryCount);
|
||||
if (!entries || entryCount <= 0 || entryCount > 64)
|
||||
uintptr_t bodyParts = 0;
|
||||
uintptr_t entries = 0;
|
||||
int entryCount = 0;
|
||||
if (!m.TryRead(healthController + offset::HealthController::BodyParts, bodyParts) || !Memory::IsUserAddress(bodyParts) ||
|
||||
!m.TryRead(bodyParts + offset::HealthController::DictionaryEntries, entries) || !Memory::IsUserAddress(entries) ||
|
||||
!m.TryRead(bodyParts + offset::HealthController::DictionaryCount, entryCount) || entryCount <= 0 || entryCount > 64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int validParts = 0;
|
||||
for (int i = 0; i < entryCount; ++i)
|
||||
{
|
||||
const uintptr_t entry = entries + offset::HealthController::ArrayData
|
||||
+ 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;
|
||||
|
||||
const uintptr_t healthValue = m.Read<uintptr_t>(bodyPartState + offset::HealthController::BodyPartHealth);
|
||||
if (!healthValue)
|
||||
continue;
|
||||
|
||||
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_fHealth += current;
|
||||
m_fHealthMax += maximum;
|
||||
++validParts;
|
||||
}
|
||||
|
||||
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.
|
||||
// The list thread is intentionally slow, while animation/weapon objects can be replaced sooner.
|
||||
if (m_address)
|
||||
{
|
||||
uintptr_t playerBody = 0;
|
||||
uintptr_t playerBones = 0;
|
||||
if (m.TryRead(m_address + offset::Player::pPlayerBody, playerBody) && playerBody &&
|
||||
m.TryRead(playerBody + offset::PlayerBody::PlayerBones, playerBones) && playerBones)
|
||||
{
|
||||
m_pPlayerBody = playerBody;
|
||||
m_pPlayerBones = playerBones;
|
||||
}
|
||||
}
|
||||
m_vecHeadOrigin = {};
|
||||
m_vecNeckOrigin = {};
|
||||
m_vecSpineOrigin = {};
|
||||
m_vecBoneList.clear();
|
||||
|
||||
if (!IsValid())
|
||||
return;
|
||||
|
||||
// Animation objects can be replaced. Refresh only the cheap direct bone pointer here.
|
||||
uintptr_t refreshedBones = 0;
|
||||
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_vecNeckOrigin = GetBonePosition(BONE_NECK);
|
||||
m_vecBoneList.clear();
|
||||
m_vecSpineOrigin = GetBonePosition(BONE_SPINE_3);
|
||||
|
||||
if (!g.g_ESP_Skeleton)
|
||||
if (!buildSkeleton)
|
||||
return;
|
||||
|
||||
m_vecBoneList.reserve(PlayerBoneList.size());
|
||||
|
|
@ -161,32 +254,26 @@ void CPlayer::UpdateBone()
|
|||
m_vecBoneList.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
m_vecBoneList.push_back(bone);
|
||||
}
|
||||
}
|
||||
|
||||
bool CPlayer::IsAiming()
|
||||
{
|
||||
return m_pWeaponAnimation != 0 && m.Read<bool>(m_pWeaponAnimation + offset::WeaponAnimations::isAiming);
|
||||
}
|
||||
|
||||
Vector3 CPlayer::GetBonePosition(const int BoneId)
|
||||
Vector3 CPlayer::GetBonePosition(const int BoneId) const
|
||||
{
|
||||
constexpr int kBoneCount = static_cast<int>(sizeof(m_pBoneArray.address) / sizeof(m_pBoneArray.address[0]));
|
||||
if (BoneId < 0 || BoneId >= kBoneCount)
|
||||
return {};
|
||||
|
||||
uintptr_t transform = 0;
|
||||
if (m_pPlayerBones)
|
||||
if (Memory::IsUserAddress(m_pPlayerBones))
|
||||
{
|
||||
switch (BoneId)
|
||||
{
|
||||
case BONE_ORIGIN:
|
||||
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::RootJoint);
|
||||
m.TryRead(m_pPlayerBones + offset::PlayerBones::RootJoint, transform);
|
||||
break;
|
||||
case BONE_NECK:
|
||||
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::Neck);
|
||||
m.TryRead(m_pPlayerBones + offset::PlayerBones::Neck, transform);
|
||||
break;
|
||||
case BONE_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);
|
||||
break;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
case BONE_LEFT_PALM:
|
||||
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::LeftPalm);
|
||||
m.TryRead(m_pPlayerBones + offset::PlayerBones::LeftPalm, transform);
|
||||
break;
|
||||
case BONE_RIGHT_PALM:
|
||||
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::RightPalm);
|
||||
m.TryRead(m_pPlayerBones + offset::PlayerBones::RightPalm, transform);
|
||||
break;
|
||||
case BONE_SPINE_3:
|
||||
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.
|
||||
// The array elements are Transform references; do not dereference +0x10 here.
|
||||
if (!transform)
|
||||
transform = m_pBoneArray.address[BoneId];
|
||||
if (!Memory::IsUserAddress(transform))
|
||||
transform = static_cast<uintptr_t>(m_pBoneArray.address[BoneId]);
|
||||
|
||||
const Vector3 position = ReadTransformPositionWithFallback(transform);
|
||||
if (!Vec3_Empty(position))
|
||||
return position;
|
||||
|
||||
const uintptr_t legacyTransform = m_pBoneArray.address[BoneId];
|
||||
if (!legacyTransform || legacyTransform == transform)
|
||||
const uintptr_t legacyTransform = static_cast<uintptr_t>(m_pBoneArray.address[BoneId]);
|
||||
if (!Memory::IsUserAddress(legacyTransform) || legacyTransform == transform)
|
||||
return {};
|
||||
|
||||
return ReadTransformPositionWithFallback(legacyTransform);
|
||||
|
|
|
|||
|
|
@ -1,40 +1,56 @@
|
|||
#pragma once
|
||||
#include "../GameSDK/GameSDK.h"
|
||||
|
||||
struct CBoneArray {
|
||||
uint64_t address[150]{};
|
||||
struct CBoneArray
|
||||
{
|
||||
uint64_t address[150]{};
|
||||
};
|
||||
|
||||
enum class CPlayerKind : unsigned char
|
||||
{
|
||||
Unknown = 0,
|
||||
EftPlayer,
|
||||
ObservedPlayer
|
||||
};
|
||||
|
||||
class CPlayer
|
||||
{
|
||||
public:
|
||||
uintptr_t m_address{ 0 };
|
||||
uintptr_t m_address{ 0 };
|
||||
|
||||
uintptr_t m_pProfile{ 0 };
|
||||
uintptr_t m_pInfo{ 0 };
|
||||
uintptr_t m_pSetting{ 0 };
|
||||
uintptr_t m_pPlayerBody{ 0 };
|
||||
uintptr_t m_pPlayerBones{ 0 };
|
||||
uintptr_t m_pHealthController{ 0 };
|
||||
uintptr_t m_pWeaponAnimation{ 0 };
|
||||
CPlayerKind m_kind{ CPlayerKind::Unknown };
|
||||
uintptr_t m_pProfile{ 0 };
|
||||
uintptr_t m_pInfo{ 0 };
|
||||
uintptr_t m_pSetting{ 0 };
|
||||
uintptr_t m_pPlayerBody{ 0 };
|
||||
uintptr_t m_pPlayerBones{ 0 };
|
||||
uintptr_t m_pHealthController{ 0 };
|
||||
uintptr_t m_pWeaponAnimation{ 0 };
|
||||
uintptr_t m_pObservedTransform{ 0 };
|
||||
|
||||
// データ
|
||||
float m_fHealth{ 0.f };
|
||||
float m_fHealthMax{ 0.f };
|
||||
int m_iSpawnType{ 0 };
|
||||
Vector3 m_vecOrigin{ 0.f, 0.f, 0.f};
|
||||
Vector3 m_vecHeadOrigin{ 0.f, 0.f, 0.f };
|
||||
Vector3 m_vecNeckOrigin{ 0.f, 0.f, 0.f };
|
||||
CBoneArray m_pBoneArray{};
|
||||
std::vector<Vector3> m_vecBoneList{};
|
||||
float m_fHealth{ 0.f };
|
||||
float m_fHealthMax{ 0.f };
|
||||
int m_iSpawnType{ 0 };
|
||||
int m_iSide{ 0 };
|
||||
bool m_bSpawnTypeValid{ false };
|
||||
bool m_bHealthValid{ false };
|
||||
bool m_bIsAiming{ false };
|
||||
|
||||
// Functions
|
||||
bool Update();
|
||||
void UpdateStatic();
|
||||
void UpdateBone();
|
||||
void UpdateHealth();
|
||||
Vector3 m_vecOrigin{};
|
||||
Vector3 m_vecHeadOrigin{};
|
||||
Vector3 m_vecNeckOrigin{};
|
||||
Vector3 m_vecSpineOrigin{};
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#include "GameSDK.h"
|
||||
#include "GameSDK.h"
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
bool Vec2_Empty(const Vector2& value)
|
||||
{
|
||||
return value == Vector2();
|
||||
return value == Vector2();
|
||||
}
|
||||
|
||||
bool Vec3_Empty(const Vector3& value)
|
||||
|
|
@ -10,232 +12,325 @@ bool Vec3_Empty(const Vector3& value)
|
|||
return value == Vector3();
|
||||
}
|
||||
|
||||
float GetDistance(Vector3 value1, Vector3 value2)
|
||||
float GetDistance(const Vector3& value1, const Vector3& value2)
|
||||
{
|
||||
float num = value1.x - value2.x;
|
||||
float num2 = value1.y - value2.y;
|
||||
float num3 = value1.z - value2.z;
|
||||
|
||||
return sqrt(num * num + num2 * num2 + num3 * num3);
|
||||
const float x = value1.x - value2.x;
|
||||
const float y = value1.y - value2.y;
|
||||
const float z = value1.z - value2.z;
|
||||
return std::sqrt(x * x + y * y + z * z);
|
||||
}
|
||||
|
||||
Vector3 GetTransformPosition(uintptr_t& transform)
|
||||
Vector3 GetTransformPosition(uintptr_t transform)
|
||||
{
|
||||
if (!transform)
|
||||
return {};
|
||||
if (!Memory::IsUserAddress(transform))
|
||||
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 };
|
||||
const __m128 mulVec1 = { 2.000, -2.000, -2.000, 0.000 };
|
||||
const __m128 mulVec2 = { -2.000, -2.000, 2.000, 0.000 };
|
||||
TransformAccessReadOnly access{};
|
||||
unsigned int index = 0;
|
||||
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);
|
||||
unsigned int Index = m.Read<unsigned int>(transform + 0x40);
|
||||
if (!pTransformAccessReadOnly.pTransformData)
|
||||
return {};
|
||||
TransformData data{};
|
||||
if (!m.TryRead(access.pTransformData + offset::UnityTransform::TransformData, data) ||
|
||||
!Memory::IsUserAddress(data.pTransformArray) ||
|
||||
!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)
|
||||
{
|
||||
result = m.Read<__m128>(transform_data.pTransformArray + 0x30 * Index);
|
||||
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);
|
||||
int transformIndex = -1;
|
||||
if (!m.TryRead(data.pTransformIndices + offset::UnityTransform::IndexStride * static_cast<uintptr_t>(index), transformIndex))
|
||||
return {};
|
||||
|
||||
__m128 xxxx = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0x00)); // xxxx
|
||||
__m128 yyyy = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0x55)); // yyyy
|
||||
__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
|
||||
__m128 zzzz = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0xAA)); // zzzz
|
||||
__m128 yxwy = _mm_castsi128_ps(_mm_shuffle_epi32(*(__m128i*)(&matrix.vec1), 0x71)); // yxwy
|
||||
__m128 tmp7 = _mm_mul_ps(*(__m128*)(&matrix.vec2), result);
|
||||
int safe = 0;
|
||||
while (transformIndex >= 0 && safe++ < 200)
|
||||
{
|
||||
Matrix34 matrix{};
|
||||
if (!m.TryRead(data.pTransformArray + offset::UnityTransform::MatrixStride * static_cast<uintptr_t>(transformIndex), matrix))
|
||||
return {};
|
||||
|
||||
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)), *(__m128*)(&matrix.vec0));
|
||||
__m128 xxxx = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0x00));
|
||||
__m128 yyyy = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0x55));
|
||||
__m128 zwxy = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0x8E));
|
||||
__m128 wzyw = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0xDB));
|
||||
__m128 zzzz = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0xAA));
|
||||
__m128 yxwy = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast<const __m128i*>(&matrix.vec1), 0x71));
|
||||
__m128 tmp7 = _mm_mul_ps(*reinterpret_cast<const __m128*>(&matrix.vec2), result);
|
||||
|
||||
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();
|
||||
Vector3 translationVector = Vector3(m._41, m._42, m._43);
|
||||
Vector3 up = Vector3(m._21, m._22, m._23);
|
||||
Vector3 right = Vector3(m._11, m._12, m._13);
|
||||
const Matrix transposed = ViewMatrix.Transpose();
|
||||
const Vector3 translationVector(transposed._41, transposed._42, transposed._43);
|
||||
const Vector3 up(transposed._21, transposed._22, transposed._23);
|
||||
const Vector3 right(transposed._11, transposed._12, transposed._13);
|
||||
|
||||
float w = translationVector.Dot(vIn) + m._44;
|
||||
|
||||
if (w < 0.098f)
|
||||
const float w = translationVector.Dot(vIn) + transposed._44;
|
||||
if (!std::isfinite(w) || w < 0.098f)
|
||||
return false;
|
||||
|
||||
float y = up.Dot(vIn) + m._24;
|
||||
float x = right.Dot(vIn) + m._14;
|
||||
vOut = Vector2((GameSize.x / 2) * (1.f + x / w), (GameSize.y / 2) * (1.f - y / w));
|
||||
const float y = up.Dot(vIn) + transposed._24;
|
||||
const float x = right.Dot(vIn) + transposed._14;
|
||||
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)
|
||||
{
|
||||
if (!listPtr || !objectName)
|
||||
return 0;
|
||||
if (!Memory::IsUserAddress(listPtr) || !Memory::IsUserAddress(lastObjectPtr) || !objectName)
|
||||
return 0;
|
||||
|
||||
char ObjectName[128]{};
|
||||
uintptr_t classNamePtr = 0;
|
||||
BaseObject activeObject = m.Read<BaseObject>(listPtr);
|
||||
BaseObject lastObject = m.Read<BaseObject>(lastObjectPtr);
|
||||
BaseObject activeObject{};
|
||||
BaseObject lastObject{};
|
||||
if (!m.TryRead(listPtr, activeObject) || !m.TryRead(lastObjectPtr, lastObject))
|
||||
return 0;
|
||||
|
||||
if (activeObject.object != NULL)
|
||||
{
|
||||
while (activeObject.object != 0 && activeObject.object != lastObject.object)
|
||||
{
|
||||
classNamePtr = m.Read<uint64_t>(activeObject.object + 0x60);
|
||||
m.ReadString(classNamePtr, ObjectName, sizeof(ObjectName));
|
||||
char objectNameBuffer[128]{};
|
||||
int safe = 0;
|
||||
while (activeObject.object != 0 && activeObject.object != lastObject.object && safe++ < 4096)
|
||||
{
|
||||
uintptr_t classNamePtr = 0;
|
||||
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)
|
||||
return activeObject.object;
|
||||
if (!Memory::IsUserAddress(activeObject.nextObjectLink) || !m.TryRead(activeObject.nextObjectLink, activeObject))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!activeObject.nextObjectLink)
|
||||
break;
|
||||
if (lastObject.object != 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (lastObject.object != NULL)
|
||||
{
|
||||
classNamePtr = m.Read<uint64_t>(lastObject.object + 0x60);
|
||||
m.ReadString(classNamePtr, ObjectName, sizeof(ObjectName));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(ObjectName, objectName) == 0)
|
||||
return lastObject.object;
|
||||
}
|
||||
bool CGame::InitAddressUnlocked()
|
||||
{
|
||||
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()
|
||||
{
|
||||
// GameObjectManager
|
||||
uintptr_t pGOM = m.Read<uintptr_t>(m.m_gBaseAddress + offset::dwGameObjectManager);
|
||||
if (!pGOM)
|
||||
{
|
||||
m_objGameObjectManager = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
m_objGameObjectManager = m.Read<GameObjectManager>(pGOM);
|
||||
|
||||
return m_objGameObjectManager.lastActiveObject != 0;
|
||||
std::lock_guard<std::mutex> lock(m_stateMutex);
|
||||
return InitAddressUnlocked();
|
||||
}
|
||||
|
||||
bool CGame::Update()
|
||||
{
|
||||
m_pGameWorld = 0;
|
||||
m_pLocalGameWorld = 0;
|
||||
|
||||
// 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;
|
||||
std::lock_guard<std::mutex> lock(m_stateMutex);
|
||||
return UpdateWorldUnlocked();
|
||||
}
|
||||
|
||||
bool CGame::UpdateCamera()
|
||||
{
|
||||
m_pFpsCamera = 0;
|
||||
|
||||
uintptr_t all_camera_ptr = m.Read<uintptr_t>(m.m_gBaseAddress + offset::dwAllCamera);
|
||||
if (!all_camera_ptr)
|
||||
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;
|
||||
std::lock_guard<std::mutex> lock(m_stateMutex);
|
||||
if (!DiscoverCameraUnlocked())
|
||||
return false;
|
||||
return UpdateViewMatrixUnlocked();
|
||||
}
|
||||
|
||||
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)
|
||||
return {};
|
||||
std::lock_guard<std::mutex> lock(m_stateMutex);
|
||||
return Memory::IsUserAddress(m_pGameWorld) && Memory::IsUserAddress(m_pLocalGameWorld);
|
||||
}
|
||||
|
||||
// ここ省略できるかチェック
|
||||
uintptr_t dw = m.Read<uintptr_t>(m_pFpsCamera + 0x30);
|
||||
if (!dw)
|
||||
return {};
|
||||
Matrix CGame::GetViewMatrix() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_stateMutex);
|
||||
return m_mxViewMatrix;
|
||||
}
|
||||
|
||||
uintptr_t viewmatrix_ptr = m.Read<uintptr_t>(dw + 0x18);
|
||||
if (!viewmatrix_ptr)
|
||||
return {};
|
||||
|
||||
return m.Read<Matrix>(viewmatrix_ptr + 0x100);
|
||||
}
|
||||
GameStateSnapshot CGame::GetState() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_stateMutex);
|
||||
GameStateSnapshot state{};
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,30 +3,54 @@
|
|||
#include "Struct.h"
|
||||
#include "../../../Framework/Memory/Memory.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
|
||||
{
|
||||
private:
|
||||
GameObjectManager m_objGameObjectManager{};
|
||||
Camera m_pAllCamera{ 0 };
|
||||
mutable std::mutex m_stateMutex;
|
||||
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:
|
||||
uintptr_t m_pGameWorld{ 0 };
|
||||
uintptr_t m_pLocalGameWorld{ 0 };
|
||||
uintptr_t m_pFpsCamera{ 0 };
|
||||
Matrix m_mxViewMatrix{};
|
||||
bool InitAddress();
|
||||
bool Update();
|
||||
bool UpdateCamera();
|
||||
|
||||
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 Update();
|
||||
bool UpdateCamera();
|
||||
Matrix GetViewMatrix();
|
||||
bool IsInRaid() const;
|
||||
Matrix GetViewMatrix() const;
|
||||
GameStateSnapshot GetState() const;
|
||||
};
|
||||
|
||||
extern bool Vec2_Empty(const Vector2& value);
|
||||
extern bool Vec3_Empty(const Vector3& value);
|
||||
extern float GetDistance(Vector3 value1, Vector3 value2);
|
||||
extern Vector3 GetTransformPosition(uintptr_t& transform);
|
||||
extern bool WorldToScreen(Matrix ViewMatrix, const Vector2& GameSize, Vector3& vIn, Vector2& vOut);
|
||||
extern uintptr_t GetObjectFromList(uintptr_t listPtr, uintptr_t lastObjectPtr, const char* objectName);
|
||||
extern float GetDistance(const Vector3& value1, const Vector3& value2);
|
||||
extern Vector3 GetTransformPosition(uintptr_t transform);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,31 +1,128 @@
|
|||
#pragma once
|
||||
|
||||
// by Unispect and UC
|
||||
// Offsets confirmed from the supplied Unispect dump unless explicitly marked native/Unity.
|
||||
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 dwAllCamera = 0x1BF8BC0;
|
||||
|
||||
// EFT.GameWorld / EFT.ClientLocalGameWorld
|
||||
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
|
||||
namespace ManagedArray
|
||||
{
|
||||
constexpr auto pPlayerBody = 0xC0; // _playerBody
|
||||
constexpr auto pMovementContext = 0x58; // <MovementContext>k__BackingField
|
||||
constexpr auto pWeaponAnimation = 0x1E0; // <ProceduralWeaponAnimation>k__BackingField
|
||||
constexpr auto pProfile = 0x618; // <Profile>k__BackingField
|
||||
constexpr auto pPhysics = 0x630; // Physical
|
||||
constexpr auto pHealthController = 0x660; // _healthController
|
||||
constexpr auto Length = 0x18;
|
||||
constexpr auto Data = 0x20;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
{
|
||||
constexpr auto PlayerBones = 0x28;
|
||||
|
|
@ -46,6 +143,7 @@ namespace offset
|
|||
constexpr auto LeftThigh = 0x158;
|
||||
constexpr auto RightThigh = 0x160;
|
||||
constexpr auto Spine3 = 0x168;
|
||||
constexpr auto Fireport = 0x178;
|
||||
constexpr auto TransformArrayData = 0x20;
|
||||
}
|
||||
|
||||
|
|
@ -54,22 +152,21 @@ namespace offset
|
|||
constexpr auto Original = 0x10;
|
||||
}
|
||||
|
||||
// EFT.MovementContext (dump.txt: EFT.MovementContext)
|
||||
namespace MovementContext
|
||||
{
|
||||
// Vector2 (x = yaw, y = pitch), in radians.
|
||||
constexpr auto Rotation = 0x27C; // Rotation_1
|
||||
constexpr auto Rotation = 0x27C;
|
||||
}
|
||||
|
||||
// EFT.Profile
|
||||
namespace Profile
|
||||
{
|
||||
constexpr auto Id = 0x10;
|
||||
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
|
||||
{
|
||||
constexpr auto Nickname = 0x10;
|
||||
|
|
@ -77,59 +174,64 @@ namespace offset
|
|||
constexpr auto MainProfileNickname = 0x20;
|
||||
constexpr auto MGroupId = 0x28;
|
||||
constexpr auto TeamId = 0x30;
|
||||
constexpr auto profileInfoSettingsClass = 0x48; // ProfileInfoSettingsClass
|
||||
constexpr auto profileInfoSettingsClass = 0x48;
|
||||
constexpr auto PrestigeLevel = 0x70;
|
||||
constexpr auto eplayerSide_0 = 0x88;
|
||||
constexpr auto RegistrationDate = 0x8C;
|
||||
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
|
||||
{
|
||||
constexpr auto BodyParts = 0x68; // Dictionary<int, BodyPartState<T>>
|
||||
constexpr auto BodyParts = 0x68;
|
||||
constexpr auto DictionaryEntries = 0x18;
|
||||
constexpr auto DictionaryCount = 0x40;
|
||||
constexpr auto ArrayData = 0x20;
|
||||
constexpr auto EntryStride = 0x18;
|
||||
constexpr auto EntryValue = 0x10;
|
||||
constexpr auto BodyPartHealth = 0x10; // BodyPartState.Health
|
||||
constexpr auto HealthValue = 0x10; // HealthValue.Value
|
||||
constexpr auto BodyPartHealth = 0x10;
|
||||
constexpr auto HealthValue = 0x10;
|
||||
constexpr auto Current = 0x00;
|
||||
constexpr auto Maximum = 0x04;
|
||||
}
|
||||
|
||||
// EFT.Animations.ProceduralWeaponAnimation
|
||||
namespace WeaponAnimations
|
||||
{
|
||||
constexpr auto Breath = 0x30;
|
||||
constexpr auto MotionReact = 0x40; // MotionEffector
|
||||
constexpr auto ShotEffector = 0x50; // ShotEffector / Shootingg
|
||||
constexpr auto MotionReact = 0x40;
|
||||
constexpr auto ShotEffector = 0x50;
|
||||
constexpr auto FirearmController = 0xB0;
|
||||
constexpr auto isAiming = 0x1C5;
|
||||
constexpr auto ShotDirection = 0x22C;
|
||||
constexpr auto Pitch = 0x398;
|
||||
}
|
||||
|
||||
// -.MotionEffector
|
||||
namespace MotionEffector
|
||||
{
|
||||
constexpr auto SwayFactors = 0x94; // UnityEngine.Vector3
|
||||
constexpr auto SwayFactors = 0x94;
|
||||
}
|
||||
|
||||
// -.ShotEffector
|
||||
namespace ShotEffector
|
||||
{
|
||||
constexpr auto MainWeaponInHands = 0x38; // EFT.InventoryLogic.Weapon
|
||||
constexpr auto MainWeaponInHands = 0x38;
|
||||
}
|
||||
|
||||
// EFT.InventoryLogic.Weapon
|
||||
namespace Weapon
|
||||
{
|
||||
constexpr auto Template = 0x40; // CompoundItem.Template
|
||||
constexpr auto MalfState = 0xF0; // WeaponMalfunctionStateClass
|
||||
constexpr auto Template = 0x40;
|
||||
constexpr auto MalfState = 0xF0;
|
||||
}
|
||||
|
||||
// EFT.InventoryLogic.WeaponTemplate
|
||||
namespace WeaponTemplate
|
||||
{
|
||||
constexpr auto WeaponRecoilSettings = 0x180; // WeaponRecoil
|
||||
constexpr auto WeaponRecoilSettings = 0x180;
|
||||
constexpr auto AllowJam = 0x2F0;
|
||||
constexpr auto AllowFeed = 0x2F1;
|
||||
constexpr auto AllowMisfire = 0x2F2;
|
||||
|
|
@ -137,7 +239,6 @@ namespace offset
|
|||
constexpr auto BaseMalfunctionChance = 0x2F4;
|
||||
}
|
||||
|
||||
// -.WeaponRecoil / -.Weapon.WeaponMalfunctionStateClass
|
||||
namespace WeaponRecoil
|
||||
{
|
||||
constexpr auto Enable = 0x18;
|
||||
|
|
@ -148,13 +249,11 @@ namespace offset
|
|||
constexpr auto State = 0x6C;
|
||||
}
|
||||
|
||||
// -.GClass774 (stamina/oxygen state)
|
||||
namespace PhysicalState
|
||||
{
|
||||
constexpr auto Current = 0x48; // Single
|
||||
constexpr auto Current = 0x48;
|
||||
}
|
||||
|
||||
// .BasePhysicalClass
|
||||
namespace Physics
|
||||
{
|
||||
constexpr auto Stamina = 0x38;
|
||||
|
|
@ -166,4 +265,3 @@ namespace offset
|
|||
constexpr auto OxygenRestoreRate = 0xD4;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <SimpleMath.h>
|
||||
using namespace DirectX::SimpleMath;
|
||||
|
||||
|
|
@ -140,7 +141,7 @@ enum WildSpanwType : int
|
|||
SANITOR_BOSS = 17,
|
||||
|
||||
// Global
|
||||
NORMAL_SCAV = 19, // 少し強化されたScav (?????
|
||||
NORMAL_SCAV = 19, // ꂽScav (?????
|
||||
CULTIST_01_SCAV = 20,
|
||||
CULTIST_02_SCAV = 21,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,158 +1,231 @@
|
|||
#include "Framework.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
// いくらかのリストを1RPMで読み取る為の構造体
|
||||
struct CGameObjectList {
|
||||
uint64_t address[64]{};
|
||||
};
|
||||
namespace
|
||||
{
|
||||
constexpr int kMaxPlayers = 512;
|
||||
constexpr int kMaxItems = 8192;
|
||||
constexpr int kMaxGrenades = 256;
|
||||
constexpr std::size_t kMaxExfils = 32;
|
||||
|
||||
// アイテムを1RPMで読み取るための構造体
|
||||
struct CGameItemList {
|
||||
uint64_t address[2048]{};
|
||||
};
|
||||
bool ReadManagedPointerList(uintptr_t listObject, std::vector<uintptr_t>& out, int hardLimit)
|
||||
{
|
||||
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()
|
||||
{
|
||||
// Get GameObjectManager
|
||||
while (!tarkov->InitAddress() || !tarkov->UpdateCamera())
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
// CGame is owned/refreshed by this thread only. Consumers receive immutable snapshots.
|
||||
while (g.process_active && !tarkov->Refresh())
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
|
||||
// Loop
|
||||
while (g.process_active)
|
||||
{
|
||||
// Sleep...
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
// 仮のリスト用変数
|
||||
std::vector<CPlayer> list_player{};
|
||||
std::vector<uintptr_t> list_grenade{};
|
||||
const auto tickStart = std::chrono::steady_clock::now();
|
||||
const WorkerConfigSnapshot config = GetWorkerConfig();
|
||||
auto snapshot = std::make_shared<DynamicWorldSnapshot>();
|
||||
snapshot->generation = ++m_dynamicGeneration;
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
// Failed refreshes must not leave a stale player address available to memory features.
|
||||
local.m_address = 0;
|
||||
|
||||
// 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)
|
||||
uintptr_t localAddress = 0;
|
||||
if (m.TryRead(snapshot->localGameWorld + offset::GameWorld::MainPlayer, localAddress) &&
|
||||
Memory::IsUserAddress(localAddress))
|
||||
{
|
||||
local.UpdateStatic();
|
||||
local.Update();
|
||||
|
||||
auto player_list = m.Read<CGameObjectList>(entity_array.list_address + 0x20);
|
||||
|
||||
// ESP用EntityListを構築
|
||||
for (auto i = 0; i < entity_array.count; i++)
|
||||
snapshot->local.m_address = localAddress;
|
||||
if (snapshot->local.UpdateStatic(CPlayerKind::EftPlayer) && snapshot->local.Update())
|
||||
{
|
||||
CPlayer p = CPlayer();
|
||||
p.m_address = player_list.address[i];
|
||||
if (config.wantHealth)
|
||||
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
|
||||
if (GetDistance(local.m_vecOrigin, p.GetBonePosition(BONE_ORIGIN)) < g.g_ESP_MaxDistance)
|
||||
{
|
||||
list_player.push_back(p);
|
||||
}
|
||||
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 });
|
||||
const Vector3 position = GetTransformPosition(transform);
|
||||
if (Vec3_Empty(position))
|
||||
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);
|
||||
m_vecEntityList = list_player;
|
||||
|
||||
//GrenadeList = list_grenade;
|
||||
PublishDynamicSnapshot(snapshot);
|
||||
|
||||
// 30 Hz data snapshots are smooth enough for overlay/aim selection and keep all RPM off the render thread.
|
||||
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() // C6262 :(
|
||||
void CFramework::UpdateStaticList()
|
||||
{
|
||||
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{};
|
||||
std::vector<CExfil> list_exfil{};
|
||||
tarkov->UpdateCamera();
|
||||
const WorkerConfigSnapshot config = GetWorkerConfig();
|
||||
auto staticSnapshot = std::make_shared<StaticWorldSnapshot>();
|
||||
staticSnapshot->generation = ++m_staticGeneration;
|
||||
|
||||
// ItemList
|
||||
if (tarkov->Update())
|
||||
const auto dynamicSnapshot = GetDynamicSnapshot();
|
||||
if (!dynamicSnapshot || !dynamicSnapshot->inRaid || !Memory::IsUserAddress(dynamicSnapshot->localGameWorld))
|
||||
{
|
||||
const auto GameWorld = tarkov->m_pLocalGameWorld;
|
||||
|
||||
// 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));
|
||||
PublishStaticSnapshot(staticSnapshot);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock1(m_mtxExfilList);
|
||||
std::lock_guard<std::mutex> lock2(m_mtxItemList);
|
||||
m_vecExfilList = list_exfil;
|
||||
m_vecItemList = list_item;
|
||||
const uintptr_t gameWorld = dynamicSnapshot->localGameWorld;
|
||||
|
||||
if (config.wantExfils)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,35 +4,33 @@
|
|||
|
||||
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 false;
|
||||
|
||||
return true;
|
||||
return !(vIn.x < g.GameRect.left || vIn.x > g.GameRect.right ||
|
||||
vIn.y < g.GameRect.top || vIn.y > g.GameRect.bottom);
|
||||
}
|
||||
|
||||
void CFramework::RenderInfo()
|
||||
{
|
||||
// ウォーターマーク
|
||||
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());
|
||||
const auto snapshot = GetDynamicSnapshot();
|
||||
|
||||
// --- 数据链路诊断: ESP/自瞄不工作时看这一行 ---
|
||||
// Init = GOM 初始化(GameObjectManager offset 是否有效)
|
||||
// Raid = 是否在局内(GameWorld 是否找到)
|
||||
// Local = 本地玩家地址(0 说明 MainPlayer offset 失效)
|
||||
// Ent = 实体列表数量(0 说明 RegisteredPlayers/PlayerBody offset 失效)
|
||||
char diag[192]{};
|
||||
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());
|
||||
|
||||
const bool initialized = snapshot && snapshot->initialized;
|
||||
const bool inRaid = snapshot && snapshot->inRaid;
|
||||
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),
|
||||
"Init:%s Raid:%s Local:0x%llX Ent:%d",
|
||||
tarkov->InitAddress() ? "OK" : "FAIL",
|
||||
tarkov->IsInRaid() ? "Y" : "N",
|
||||
(unsigned long long)local.m_address,
|
||||
(int)GetEntityList().size());
|
||||
"Init:%s Raid:%s Cam:%s Local:0x%llX Ent:%zu Gen:%llu",
|
||||
initialized ? "OK" : "FAIL",
|
||||
inRaid ? "Y" : "N",
|
||||
(snapshot && snapshot->cameraValid) ? "OK" : "FAIL",
|
||||
(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);
|
||||
|
||||
// --- 自瞄实时状态 (关菜单也能看) ---
|
||||
// Aim = 自瞄开关; M = 方式(MEM内存/MOU鼠标); Key = 触发键是否按住(含F9)
|
||||
// Tgt = 是否锁定目标; d = 目标离屏幕中心距离px; Hook/Btn = 鼠标钩子状态
|
||||
char aim[192]{};
|
||||
snprintf(aim, sizeof(aim),
|
||||
"Aim:%s M:%s F:%s Tgt:%s d=%.0fpx",
|
||||
|
|
@ -43,20 +41,21 @@ void CFramework::RenderInfo()
|
|||
m_diagBestDist);
|
||||
StringEx(ImVec2(8.f, 42.f), ImColor(1.f, 0.f, 0.f, 1.f), ImGui::GetFontSize(), aim);
|
||||
|
||||
// Crosshair
|
||||
if (g.g_Crosshair && !local.IsAiming())
|
||||
if (g.g_Crosshair && snapshot && snapshot->local.IsValid() && !snapshot->local.IsAiming())
|
||||
{
|
||||
switch (g.g_CrosshairType)
|
||||
{
|
||||
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);
|
||||
ImGui::GetBackgroundDrawList()->AddLine(ImVec2(Center.x, Center.y - g.g_CrosshairSize), ImVec2(Center.x, (Center.y + g.g_CrosshairSize) + 1), CrosshairColor, 1);
|
||||
} break;
|
||||
case 0:
|
||||
{
|
||||
ImVec2 center = ImVec2(g.GameRect.right / 2.f, g.GameRect.bottom / 2.f);
|
||||
ImGui::GetBackgroundDrawList()->AddLine(ImVec2(center.x - g.g_CrosshairSize, center.y), ImVec2(center.x + g.g_CrosshairSize + 1.f, center.y), CrosshairColor, 1.f);
|
||||
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:
|
||||
ImGui::GetBackgroundDrawList()->AddCircle(ImVec2((float)g.GameRect.right / 2.f, (float)g.GameRect.bottom / 2.f), g.g_CrosshairSize, CrosshairColor, NULL);
|
||||
//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);
|
||||
//ImGui::GetBackgroundDrawList()->AddCircleFilled(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);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -64,252 +63,200 @@ void CFramework::RenderInfo()
|
|||
|
||||
void CFramework::RenderESP()
|
||||
{
|
||||
// Localの更新
|
||||
CPlayer* pLocal = &local;
|
||||
|
||||
if (!pLocal->Update())
|
||||
const auto snapshot = GetDynamicSnapshot();
|
||||
if (!snapshot || !snapshot->inRaid || !snapshot->cameraValid || !snapshot->local.IsValid())
|
||||
return;
|
||||
|
||||
// ViewMatrix
|
||||
Matrix ViewMatrix = tarkov->GetViewMatrix(); // WorldToScreenの度に呼び出す必要はない
|
||||
const CPlayer& localPlayer = snapshot->local;
|
||||
const Matrix& viewMatrix = snapshot->viewMatrix;
|
||||
const Vector2 gameSize((float)g.GameRect.right, (float)g.GameRect.bottom);
|
||||
|
||||
// るーぷするよ
|
||||
for (auto& entity : GetEntityList())
|
||||
// Rendering consumes an immutable snapshot: no player/camera RPM is performed in this loop.
|
||||
for (const auto& entity : snapshot->players)
|
||||
{
|
||||
CPlayer* pEntity = &entity;
|
||||
|
||||
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))
|
||||
const CPlayer& player = entity;
|
||||
if (!player.IsValid() || Vec3_Empty(player.m_vecOrigin))
|
||||
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;
|
||||
|
||||
// WorldToScreen
|
||||
pEntity->UpdateBone();
|
||||
if (!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), pEntity->m_vecHeadOrigin, pHead) ||
|
||||
!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), pEntity->m_vecNeckOrigin, pNeck))
|
||||
const float headToNeck = neck.y - head.y;
|
||||
const float height = screen.y - head.y + (headToNeck * 2.5f);
|
||||
const float width = height / 3.5f;
|
||||
if (height <= 0.f || width <= 0.f)
|
||||
continue;
|
||||
|
||||
// 各種サイズの算出等
|
||||
const float HeadToNeck = pNeck.y - pHead.y;
|
||||
const float pHeight = pScreen.y - pHead.y + (HeadToNeck * 2.5f);
|
||||
const float pWidth = pHeight / 3.5f;
|
||||
|
||||
std::string Name = "InValid";
|
||||
ImColor pColor = ImColor(1.f, 1.f, 1.f, 1.f);
|
||||
GetESPInfo(pEntity->m_iSpawnType, Name, pColor);
|
||||
|
||||
// BTR-82
|
||||
if (pEntity->m_iSpawnType == BTR_VEHICLE) {
|
||||
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(pScreen.x, pScreen.y - 2.f), 2.f, ImColor(1.f, 1.f, 1.f, 1.f), 0.f);
|
||||
|
||||
std::string BTR_Text = "BTR-82 [" + std::to_string((int)flDistance) + "m]";
|
||||
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());
|
||||
std::string name = "InValid";
|
||||
ImColor color = ImColor(1.f, 1.f, 1.f, 1.f);
|
||||
if (player.m_bSpawnTypeValid)
|
||||
{
|
||||
GetESPInfo(player.m_iSpawnType, name, color);
|
||||
}
|
||||
else if (player.IsObserved())
|
||||
{
|
||||
if (player.m_iSide == SIDE_SCAV)
|
||||
{
|
||||
name = "Observed SCAV";
|
||||
color = Col_ESP_Scav;
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// Line
|
||||
if (g.g_ESP_Line)
|
||||
DrawLine(ImVec2(g.GameRect.right / 2.f, g.GameRect.bottom), ImVec2(pScreen.x, pScreen.y), pColor, 1.f);
|
||||
|
||||
// 2D Box
|
||||
DrawLine(ImVec2(g.GameRect.right / 2.f, g.GameRect.bottom), ImVec2(screen.x, screen.y), color, 1.f);
|
||||
|
||||
if (g.g_ESP_Box)
|
||||
{
|
||||
static float bScale = 1.75f;
|
||||
|
||||
// Filled
|
||||
static float boxScale = 1.75f;
|
||||
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)
|
||||
{
|
||||
case 0:
|
||||
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y - pHeight), ImVec2(pScreen.x + pWidth, pScreen.y - pHeight), pColor, 1.f);
|
||||
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y), ImVec2(pScreen.x - pWidth, pScreen.y - pHeight), pColor, 1.f);
|
||||
DrawLine(ImVec2(pScreen.x + pWidth, pScreen.y), ImVec2(pScreen.x + pWidth, pScreen.y - pHeight), pColor, 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 - height), ImVec2(screen.x + width, screen.y - height), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x - width, screen.y - height), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x + width, screen.y), ImVec2(screen.x + width, screen.y - height), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x + width, screen.y), color, 1.f);
|
||||
break;
|
||||
case 1:
|
||||
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y - pHeight), ImVec2(pScreen.x - pWidth + (pWidth / bScale), pScreen.y - pHeight), pColor, 1.f);
|
||||
DrawLine(ImVec2(pScreen.x + pWidth, pScreen.y - pHeight), ImVec2(pScreen.x + pWidth - (pWidth / bScale), pScreen.y - pHeight), pColor, 1.f);
|
||||
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y - pHeight), ImVec2(pScreen.x - pWidth, pScreen.y - pHeight + (pWidth / bScale)), pColor, 1.f);
|
||||
DrawLine(ImVec2(pScreen.x + pWidth, pScreen.y - pHeight), ImVec2(pScreen.x + pWidth, pScreen.y - pHeight + (pWidth / bScale)), pColor, 1.f);
|
||||
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y), ImVec2(pScreen.x - pWidth, pScreen.y - (pWidth / bScale)), pColor, 1.f);
|
||||
DrawLine(ImVec2(pScreen.x + pWidth, pScreen.y), ImVec2(pScreen.x + pWidth, pScreen.y - (pWidth / bScale)), pColor, 1.f);
|
||||
DrawLine(ImVec2(pScreen.x - pWidth, pScreen.y), ImVec2(pScreen.x - pWidth + (pWidth / bScale), pScreen.y), pColor, 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 - height), ImVec2(screen.x - width + (width / boxScale), screen.y - height), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x + width, screen.y - height), ImVec2(screen.x + width - (width / boxScale), screen.y - height), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x - width, screen.y - height), ImVec2(screen.x - width, screen.y - height + (width / boxScale)), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x + width, screen.y - height), ImVec2(screen.x + width, screen.y - height + (width / boxScale)), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x - width, screen.y - (width / boxScale)), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x + width, screen.y), ImVec2(screen.x + width, screen.y - (width / boxScale)), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x - width + (width / boxScale), screen.y), color, 1.f);
|
||||
DrawLine(ImVec2(screen.x + width, screen.y), ImVec2(screen.x + width - (width / boxScale), screen.y), color, 1.f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// パフォーマンスの都合上250m以上先のプレイヤーはスキップ
|
||||
if (g.g_ESP_Skeleton && 250.f > flDistance)
|
||||
if (g.g_ESP_Skeleton && distance < 250.f && player.m_vecBoneList.size() >= 9)
|
||||
{
|
||||
// Head Circle
|
||||
ImGui::GetBackgroundDrawList()->AddCircle(ImVec2(pHead.x, pHead.y - (HeadToNeck / 2.f)), HeadToNeck * 1.55f, pColor, 0.f);
|
||||
ImGui::GetBackgroundDrawList()->AddCircle(ImVec2(head.x, head.y - (headToNeck / 2.f)), headToNeck * 1.55f, color, 0);
|
||||
|
||||
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
|
||||
Vector3 list[][2]{
|
||||
{ pEntity->m_vecNeckOrigin, pEntity->m_vecBoneList[CPelvis] },
|
||||
{ pEntity->m_vecNeckOrigin, pEntity->m_vecBoneList[CLeftForearm] },
|
||||
{ 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);
|
||||
}
|
||||
Vector2 a{}, b{};
|
||||
if (!WorldToScreen(viewMatrix, gameSize, pair[0], a) || !WorldToScreen(viewMatrix, gameSize, pair[1], b))
|
||||
continue;
|
||||
DrawLine(ImVec2(a.x, a.y), ImVec2(b.x, b.y), color, 1.f);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if (g.g_ESP_Distance)
|
||||
{
|
||||
// 不明なSpawnTypeだったら値を表示
|
||||
if (!Name.compare("InValid"))
|
||||
Name += "[" + std::to_string(pEntity->m_iSpawnType) + "]";
|
||||
const std::string distanceText = std::to_string((int)distance) + "m";
|
||||
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());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// GrenadeList
|
||||
if (g.g_ESP_Grenade)
|
||||
for (const auto& grenade : snapshot->grenades)
|
||||
{
|
||||
const auto grenade_class = m.Read<uintptr_t>(tarkov->m_pLocalGameWorld + offset::GrenadeList);
|
||||
const auto grenade_array_ptr = m.Read<uintptr_t>(grenade_class + 0x18);
|
||||
const auto grenade_array = m.Read<UnityList>(grenade_array_ptr);
|
||||
const float distance = GetDistance(localPlayer.m_vecOrigin, grenade.position);
|
||||
if (distance > 100.f)
|
||||
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)
|
||||
{
|
||||
for (auto k = 0; k < grenade_array.count; k++)
|
||||
{
|
||||
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);
|
||||
Vector2 screen{};
|
||||
if (!WorldToScreen(viewMatrix, gameSize, grenade.position, screen))
|
||||
continue;
|
||||
|
||||
if (Vec3_Empty(m_pVecLocation))
|
||||
continue;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
const std::string text = "Grenade [" + std::to_string((int)distance) + "m]";
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// Item
|
||||
const auto staticSnapshot = GetStaticSnapshot();
|
||||
if (!staticSnapshot)
|
||||
return;
|
||||
|
||||
if (g.g_ESP_Item)
|
||||
{
|
||||
for (auto& item : GetItemList())
|
||||
for (const auto& item : staticSnapshot->items)
|
||||
{
|
||||
CItem* pItem = &item;
|
||||
|
||||
if (!pItem->m_bIsCorpse && pItem->m_CName.empty())
|
||||
if (!item.m_bIsCorpse && item.m_CName.empty())
|
||||
continue;
|
||||
else if (!g.g_ESP_Corpse && pItem->m_bIsCorpse)
|
||||
if (!g.g_ESP_Corpse && item.m_bIsCorpse)
|
||||
continue;
|
||||
|
||||
float ItemDistance = GetDistance(pLocal->m_vecOrigin, pItem->m_vecOrigin);
|
||||
|
||||
// Distance Check
|
||||
if (ItemDistance > g.g_ESP_MaxItemDistance)
|
||||
const float distance = GetDistance(localPlayer.m_vecOrigin, item.m_vecOrigin);
|
||||
if (distance > g.g_ESP_MaxItemDistance)
|
||||
continue;
|
||||
|
||||
Vector2 pItemScreen{};
|
||||
if (!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), pItem->m_vecOrigin, pItemScreen))
|
||||
Vector2 screen{};
|
||||
if (!WorldToScreen(viewMatrix, gameSize, item.m_vecOrigin, screen) || !InScreen(screen))
|
||||
continue;
|
||||
|
||||
if (!InScreen(pItemScreen))
|
||||
continue;
|
||||
|
||||
std::string vItemTx = pItem->m_bIsCorpse ? "Corpse" : pItem->m_CName;
|
||||
vItemTx +="[" + std::to_string((int)ItemDistance) + "m]";
|
||||
|
||||
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());
|
||||
std::string text = item.m_bIsCorpse ? "Corpse" : item.m_CName;
|
||||
text += "[" + std::to_string((int)distance) + "m]";
|
||||
const ImColor itemColor = item.m_bIsCorpse ? Col_ESP_Corpse : Col_ESP_RareItem;
|
||||
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y - 2.f), 2.f, itemColor, 0);
|
||||
String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y), itemColor, text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Exfil
|
||||
if (g.g_ESP_Exfil)
|
||||
{
|
||||
for (auto& exfil : GetExfilList())
|
||||
for (const auto& exfil : staticSnapshot->exfils)
|
||||
{
|
||||
CExfil* pExfil = &exfil;
|
||||
|
||||
Vector2 pExfilScreen{};
|
||||
if (!WorldToScreen(ViewMatrix, Vector2(g.GameRect.right, g.GameRect.bottom), pExfil->m_pVecLocation, pExfilScreen))
|
||||
Vector2 screen{};
|
||||
if (!WorldToScreen(viewMatrix, gameSize, exfil.m_pVecLocation, screen) || !InScreen(screen))
|
||||
continue;
|
||||
|
||||
if (!InScreen(pExfilScreen))
|
||||
continue;
|
||||
|
||||
ImColor ExfilColor = pExfil->m_pExfilStatus == NOTREADY ? Col_ESP_ExfilClose : Col_ESP_ExfilOpen;
|
||||
|
||||
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());
|
||||
const ImColor exfilColor = exfil.m_pExfilStatus == NOTREADY ? Col_ESP_ExfilClose : Col_ESP_ExfilOpen;
|
||||
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);
|
||||
String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y), exfilColor, text.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#pragma once
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <atomic>
|
||||
|
||||
struct Globals
|
||||
{
|
||||
// System
|
||||
bool process_active = true;
|
||||
std::atomic_bool process_active{ true };
|
||||
bool ShowMenu = false;
|
||||
|
||||
// GameWindow Data
|
||||
|
|
|
|||
|
|
@ -49,7 +49,11 @@ bool Memory::AttachProcess(const char* targetName, int InitMode)
|
|||
|
||||
void Memory::DetachProcess()
|
||||
{
|
||||
CloseHandle(pHandle);
|
||||
if (pHandle)
|
||||
CloseHandle(pHandle);
|
||||
pHandle = nullptr;
|
||||
PID = 0;
|
||||
m_gBaseAddress = 0;
|
||||
}
|
||||
|
||||
uintptr_t Memory::GetModuleBase(const std::string moduleName)
|
||||
|
|
|
|||
|
|
@ -5,116 +5,166 @@
|
|||
#include <psapi.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
|
||||
/*
|
||||
[+] メモリやオーバーレイの初期化モードを設定します
|
||||
|
||||
WINDOW_TITLE : ウィンドウのタイトルを使用します
|
||||
WINDOW_CLASS : ウィンドウのクラス名を使用します
|
||||
PROCESS : 実行ファイル名を使用します */
|
||||
Memory/overlay initialization mode.
|
||||
*/
|
||||
enum InitMode : int
|
||||
{
|
||||
WINDOW_TITLE,
|
||||
WINDOW_CLASS,
|
||||
PROCESS
|
||||
WINDOW_TITLE,
|
||||
WINDOW_CLASS,
|
||||
PROCESS
|
||||
};
|
||||
|
||||
class Memory
|
||||
{
|
||||
private:
|
||||
DWORD PID;
|
||||
HANDLE pHandle;
|
||||
DWORD PID{};
|
||||
HANDLE pHandle{};
|
||||
|
||||
uintptr_t GetModuleBase(const std::string moduleName);
|
||||
PROCESSENTRY32 GetProcess(const std::string processName);
|
||||
uintptr_t GetModuleBase(const std::string moduleName);
|
||||
PROCESSENTRY32 GetProcess(const std::string processName);
|
||||
public:
|
||||
uintptr_t m_gBaseAddress;
|
||||
uintptr_t m_gBaseAddress{};
|
||||
|
||||
bool AttachProcess(const char* targetName, int mode);
|
||||
void DetachProcess();
|
||||
bool AttachProcess(const char* targetName, int mode);
|
||||
void DetachProcess();
|
||||
|
||||
// R/WPM Templates
|
||||
template <typename T>
|
||||
constexpr const T Read(const uintptr_t& address) const noexcept
|
||||
{
|
||||
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);
|
||||
}
|
||||
static bool IsUserAddress(const uintptr_t address) noexcept
|
||||
{
|
||||
return address > 0x10000ULL && address < 0x0000800000000000ULL;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool TryRead(const uintptr_t& address, T& value) const noexcept
|
||||
{
|
||||
value = T{};
|
||||
if (!pHandle || !address)
|
||||
return false;
|
||||
template <typename T>
|
||||
bool TryRead(const uintptr_t& address, T& value) const noexcept
|
||||
{
|
||||
value = T{};
|
||||
if (!pHandle || !IsUserAddress(address))
|
||||
return false;
|
||||
|
||||
SIZE_T sizeRead = 0;
|
||||
return !!ReadProcessMemory(pHandle, reinterpret_cast<const void*>(address), &value, sizeof(T), &sizeRead)
|
||||
&& sizeRead == sizeof(T);
|
||||
}
|
||||
SIZE_T sizeRead = 0;
|
||||
return !!ReadProcessMemory(pHandle, reinterpret_cast<const void*>(address), &value, sizeof(T), &sizeRead)
|
||||
&& sizeRead == sizeof(T);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool TryWrite(const uintptr_t& address, const T& value) const noexcept
|
||||
{
|
||||
if (!pHandle || !address)
|
||||
return false;
|
||||
template <typename T>
|
||||
bool TryWrite(const uintptr_t& address, const T& value) const noexcept
|
||||
{
|
||||
if (!pHandle || !IsUserAddress(address))
|
||||
return false;
|
||||
|
||||
SIZE_T sizeWritten = 0;
|
||||
return !!WriteProcessMemory(pHandle, reinterpret_cast<void*>(address), &value, sizeof(T), &sizeWritten)
|
||||
&& sizeWritten == sizeof(T);
|
||||
}
|
||||
SIZE_T sizeWritten = 0;
|
||||
return !!WriteProcessMemory(pHandle, reinterpret_cast<void*>(address), &value, sizeof(T), &sizeWritten)
|
||||
&& sizeWritten == sizeof(T);
|
||||
}
|
||||
|
||||
uintptr_t ReadChain(uintptr_t address, const std::vector<uintptr_t>& offsets)
|
||||
{
|
||||
if (!address || offsets.empty())
|
||||
return 0;
|
||||
bool TryReadBuffer(const uintptr_t address, void* buffer, const SIZE_T size) const noexcept
|
||||
{
|
||||
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 == size;
|
||||
}
|
||||
|
||||
uintptr_t result = Read<uintptr_t>(address + offsets.front());
|
||||
for (std::size_t i = 1; i < offsets.size(); ++i)
|
||||
{
|
||||
if (!result)
|
||||
return 0;
|
||||
template <typename T>
|
||||
bool TryReadVector(const uintptr_t address, const std::size_t count, std::vector<T>& out,
|
||||
const std::size_t hardLimit = 16384) const noexcept
|
||||
{
|
||||
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;
|
||||
}
|
||||
bool ReadString(uintptr_t address, LPVOID buffer, SIZE_T size) const
|
||||
{
|
||||
SIZE_T size_read;
|
||||
// Compatibility helpers. Failed reads return zero-initialized values, while SDK code
|
||||
// should prefer TryRead when failure needs to be distinguished from a legitimate zero.
|
||||
template <typename T>
|
||||
T Read(const uintptr_t& address) const noexcept
|
||||
{
|
||||
T value{};
|
||||
TryRead(address, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
return !!::ReadProcessMemory(pHandle, LPCVOID(address), buffer, size, &size_read) && size_read > 0;
|
||||
}
|
||||
std::string ReadString(uintptr_t address, int length)
|
||||
{
|
||||
if (!address || length <= 0)
|
||||
return {};
|
||||
template <typename T>
|
||||
void Write(const uintptr_t& address, const T& value) const noexcept
|
||||
{
|
||||
TryWrite(address, value);
|
||||
}
|
||||
|
||||
char Buffer[256]{};
|
||||
if (length >= static_cast<int>(sizeof(Buffer)))
|
||||
length = static_cast<int>(sizeof(Buffer)) - 1;
|
||||
uintptr_t ReadChain(uintptr_t address, const std::vector<uintptr_t>& offsets) const
|
||||
{
|
||||
if (!IsUserAddress(address) || offsets.empty())
|
||||
return 0;
|
||||
|
||||
if (!ReadProcessMemory(pHandle, reinterpret_cast<void*>(address), Buffer, length, nullptr))
|
||||
return {};
|
||||
uintptr_t result = 0;
|
||||
if (!TryRead(address + offsets.front(), result) || !IsUserAddress(result))
|
||||
return 0;
|
||||
|
||||
Buffer[length] = '\0';
|
||||
return std::string(Buffer);
|
||||
};
|
||||
wchar_t* ReadWString(uintptr_t address, int length)
|
||||
{
|
||||
wchar_t Buffer[256]{};
|
||||
ReadProcessMemory(pHandle, (void*)(address), Buffer, (length * 2), nullptr);
|
||||
for (std::size_t i = 1; i < offsets.size(); ++i)
|
||||
{
|
||||
uintptr_t next = 0;
|
||||
if (!TryRead(result + offsets[i], next) || !IsUserAddress(next))
|
||||
return 0;
|
||||
result = next;
|
||||
}
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ auto cheat = std::make_unique<CFramework>();
|
|||
|
||||
void Overlay::OverlayUserFunction()
|
||||
{
|
||||
// Publish UI/config state once per frame; worker threads never read mutable ImGui-bound globals directly.
|
||||
cheat->UpdateWorkerConfigFromUi();
|
||||
cheat->RenderInfo();
|
||||
|
||||
if (g.g_ESP)
|
||||
|
|
@ -89,12 +91,18 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||
}
|
||||
io.Fonts->Build();
|
||||
|
||||
// スレッドを作成
|
||||
std::thread([&]() { cheat->UpdateList(); }).detach();
|
||||
std::thread([&]() { cheat->UpdateStaticList(); }).detach();
|
||||
// Worker threads are joinable. This prevents RPM/WPM from racing with DetachProcess on shutdown.
|
||||
std::thread dynamicSnapshotThread([&]() { cheat->UpdateList(); });
|
||||
std::thread staticSnapshotThread([&]() { cheat->UpdateStaticList(); });
|
||||
|
||||
overlay->OverlayLoop();
|
||||
g.process_active = false;
|
||||
|
||||
if (dynamicSnapshotThread.joinable())
|
||||
dynamicSnapshotThread.join();
|
||||
if (staticSnapshotThread.joinable())
|
||||
staticSnapshotThread.join();
|
||||
|
||||
cheat->ResetFeatureOverrides();
|
||||
overlay->DestroyOverlay();
|
||||
m.DetachProcess();
|
||||
|
|
|
|||
22
README.md
22
README.md
|
|
@ -24,7 +24,6 @@
|
|||
| 尸体 ESP | 尸体标记 | ✅ |
|
||||
| 撤离点 ESP | 开启 / 未开启撤离点分色(绿 / 橙) | ✅ |
|
||||
| 手雷 ESP | 投掷物轨迹标记 + 屏幕顶部 `[ WARNING ] Grenade!!` 警告 | ✅ |
|
||||
| 子弹追踪(只读) | 读取弹道快照(初速 / 速度 / 方向 / 落点),绘制弹道轨迹,**只读不改写** | ✅ |
|
||||
| 最大距离 | `g_ESP_MaxDistance`(默认 500m) | ✅ |
|
||||
|
||||
### 自瞄(AimBot)
|
||||
|
|
@ -72,12 +71,23 @@
|
|||
运行前提:先启动 SPT-AKI 3.11.3 客户端(进程名为 `EscapeFromTarkov`),再以管理员身份运行 `CFramework.exe`。
|
||||
程序通过窗口标题附加进程并创建覆盖层;若附加失败返回码 `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 占用偏高**:原版遗留问题,二次开发中已部分缓解但未根治。
|
||||
- 已改善:实体 / 撤离点 / 物资列表刷新线程已降频(500ms ~ 5s 间隔轮询,见 `UpdateList.cpp`);主渲染循环开启了 VSync(`Present(1, 0)`),不会无限空转。
|
||||
- 仍存在:主循环没有独立帧率上限,VSync 下跟着显示器刷新率跑(144Hz 屏即 144 FPS);每一帧都全量执行 `UpdateFeatures()`(耐力 / 跌落 / 摇摆 / 后座 / 故障五组 override 的内存读链 + 写入)、子弹追踪快照和 ESP 遍历渲染,没有脏标记或事件驱动缓存。高刷新率屏幕上占用依然明显。
|
||||
- 需要进一步优化时,可从给主循环加帧率上限(如 60 FPS + `sleep_for`)或给 `UpdateFeatures` 加每 N 帧执行一次入手。
|
||||
- 当前包是在 Linux 容器中做源码级重构,环境没有 Visual Studio 2022 / Windows SDK,因此没有执行真正的 MSVC Release x64 链接构建。核心重构源文件已用 Clang + Windows API 测试桩做 `-fsyntax-only` 验证;最终仍建议在 VS2022 中执行一次 `Release x64` 全量构建。
|
||||
- `ObservedPlayerView` 的昵称、Role、实时 Health 等字段在当前 dump 中仍是混淆字段;本次实现采取保守降级显示,不猜测未知字段语义。
|
||||
- 主 Overlay 仍跟随 VSync/显示器刷新率;`UpdateFeatures()` 是渲染线程写功能路径,后续如果需要进一步降低 CPU/RPM,可以单独给写功能增加节流/脏标记。
|
||||
|
||||
## 项目结构
|
||||
|
||||
|
|
@ -88,7 +98,7 @@ CFramework/
|
|||
│ ├── main.cpp # 入口:附加进程、初始化 Overlay、字体、后台线程
|
||||
│ ├── Cheat/ # 功能实现
|
||||
│ │ ├── Aim.cpp # 自瞄 + 压枪(Mouse / Memory)
|
||||
│ │ ├── Visual.cpp # ESP 渲染(玩家/物资/撤离点/手雷/子弹轨迹)
|
||||
│ │ ├── Visual.cpp # ESP 渲染(玩家/物资/撤离点/手雷)
|
||||
│ │ ├── Features.cpp # 耐力/跌落/摇摆/后座/故障 override
|
||||
│ │ ├── Menu.cpp # ImGui 菜单(中文)
|
||||
│ │ ├── Render.cpp / UpdateList.cpp
|
||||
|
|
|
|||
|
|
@ -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。
|
||||
Loading…
Reference in New Issue