diff --git a/CFramework/Cheat/Aim.cpp b/CFramework/Cheat/Aim.cpp index 826d2ac..665126a 100644 --- a/CFramework/Cheat/Aim.cpp +++ b/CFramework/Cheat/Aim.cpp @@ -1,4 +1,4 @@ -#include "Framework.h" +#include "Framework.h" #include "../Framework/Input/Input.h" #include #include @@ -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(g.g_AimMcOffset), mc) || + if (!m.TryRead(localAddress + static_cast(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 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(local.m_address + mcOff); + uintptr_t ptr = m.Read(localAddress + mcOff); if (!ptr) continue; @@ -524,7 +504,7 @@ void CFramework::ScanRotationOffset() for (auto& c : s_cands) { - uintptr_t ptr = m.Read(local.m_address + c.mcOff); + uintptr_t ptr = m.Read(localAddress + c.mcOff); if (!ptr) continue; diff --git a/CFramework/Cheat/Features.cpp b/CFramework/Cheat/Features.cpp index 437873c..c400ce1 100644 --- a/CFramework/Cheat/Features.cpp +++ b/CFramework/Cheat/Features.cpp @@ -1,4 +1,4 @@ -#include "Framework.h" +#include "Framework.h" #include // 只写入 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) diff --git a/CFramework/Cheat/Framework.h b/CFramework/Cheat/Framework.h index cb43edb..c586727 100644 --- a/CFramework/Cheat/Framework.h +++ b/CFramework/Cheat/Framework.h @@ -1,4 +1,5 @@ #pragma once +#include #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 +#include +#include +#include +#include + +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 players{}; + std::vector 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 exfils{}; + std::vector 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 tarkov{ std::make_unique() }; + // Render/features keep this as a render-thread-only scratch object. The list thread never mutates it. CPlayer local; + std::shared_ptr m_dynamicSnapshot{ std::make_shared() }; + std::shared_ptr m_staticSnapshot{ std::make_shared() }; + 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 GetDynamicSnapshot() const + { + return std::atomic_load(&m_dynamicSnapshot); + } + + std::shared_ptr GetStaticSnapshot() const + { + return std::atomic_load(&m_staticSnapshot); + } + + void PublishDynamicSnapshot(std::shared_ptr snapshot) + { + std::atomic_store(&m_dynamicSnapshot, std::move(snapshot)); + } + + void PublishStaticSnapshot(std::shared_ptr 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 m_vecEntityList; - std::vector m_vecExfilList; - std::vector m_vecItemList; - std::vector m_vecGrenadeList; - - std::vector GetEntityList() { - std::lock_guard lock(m_mtxEntityList); - return m_vecEntityList; + // Compatibility accessors for menu/legacy call sites. New render/aim code consumes immutable snapshots directly. + std::vector GetEntityList() const + { + auto snapshot = GetDynamicSnapshot(); + return snapshot ? snapshot->players : std::vector{}; } - std::vector GetExfilList() { - std::lock_guard lock(m_mtxExfilList); - return m_vecExfilList; + std::vector GetExfilList() const + { + auto snapshot = GetStaticSnapshot(); + return snapshot ? snapshot->exfils : std::vector{}; } - std::vector GetItemList() { - std::lock_guard lock(m_mtxItemList); - return m_vecItemList; + std::vector GetItemList() const + { + auto snapshot = GetStaticSnapshot(); + return snapshot ? snapshot->items : std::vector{}; } // 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); -}; \ No newline at end of file +}; diff --git a/CFramework/Cheat/Menu.cpp b/CFramework/Cheat/Menu.cpp index a188896..5c21bc7 100644 --- a/CFramework/Cheat/Menu.cpp +++ b/CFramework/Cheat/Menu.cpp @@ -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(local.m_address + (uintptr_t)g.g_AimMcOffset); + uintptr_t mc = m.Read(runtimeLocalAddress + (uintptr_t)g.g_AimMcOffset); if (mc) { Vector2 rot = m.Read(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("非法/不完整指针链不会写入"); diff --git a/CFramework/Cheat/Render.cpp b/CFramework/Cheat/Render.cpp index a6367e5..13d1ba8 100644 --- a/CFramework/Cheat/Render.cpp +++ b/CFramework/Cheat/Render.cpp @@ -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) diff --git a/CFramework/Cheat/SDK/CExfil/CExfil.cpp b/CFramework/Cheat/SDK/CExfil/CExfil.cpp index a492d05..d577d7d 100644 --- a/CFramework/Cheat/SDK/CExfil/CExfil.cpp +++ b/CFramework/Cheat/SDK/CExfil/CExfil.cpp @@ -1,45 +1,56 @@ -#include "CExfil.h" +#include "CExfil.h" +#include 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(ptr + 0xC8); + int status = CLOSE; + m.TryRead(ptr + offset::Exfil::Status, status); + return status; } std::string CExfil::GetName() { - // Name - uintptr_t eSetting = m.Read(ptr + 0x78); - uintptr_t NamePtr = m.Read(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(NamePtr + 0x10); - - for (int i = 0; i < length; i++) - pName[i] = m.Read(NamePtr + 0x14 + (i * 0x2)); - - return pName; + length = (std::min)(length, 127); + std::string name; + name.reserve(static_cast(length)); + for (int i = 0; i < length; ++i) + { + wchar_t ch = 0; + if (!m.TryRead(namePtr + offset::ManagedString::Data + static_cast(i) * offset::ManagedString::CharStride, ch)) + break; + if (ch >= 0x20 && ch <= 0x7E) + name.push_back(static_cast(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; -} \ No newline at end of file + 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); +} diff --git a/CFramework/Cheat/SDK/CItem/CItem.cpp b/CFramework/Cheat/SDK/CItem/CItem.cpp index 9f8a27c..3209086 100644 --- a/CFramework/Cheat/SDK/CItem/CItem.cpp +++ b/CFramework/Cheat/SDK/CItem/CItem.cpp @@ -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::vectorC_LootList = +std::vector 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; - // ƒRƒ“ƒeƒi/Ž€‘̂ł͂Ȃ©‚Á‚½‚ç - 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; - // —v‰ü‘P - const int length = m.Read(BsgId + 0x10); - - for (int j = 0; j < length; j++) - ItemID[j] = m.Read(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; -} \ No newline at end of file + return true; +} diff --git a/CFramework/Cheat/SDK/CPlayer/CPlayer.cpp b/CFramework/Cheat/SDK/CPlayer/CPlayer.cpp index d8b7a5e..b7ad2a4 100644 --- a/CFramework/Cheat/SDK/CPlayer/CPlayer.cpp +++ b/CFramework/Cheat/SDK/CPlayer/CPlayer.cpp @@ -1,52 +1,78 @@ #include "CPlayer.h" +#include -std::vector 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 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(playerBones + fieldOffset); - return bifacialTransform - ? m.Read(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(array + offset::PlayerBones::TransformArrayData + static_cast(index) * sizeof(uintptr_t)); + uintptr_t transform = 0; + if (!m.TryRead(array + offset::PlayerBones::TransformArrayData + static_cast(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(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(m_address + offset::Player::pHealthController); - m_pWeaponAnimation = m.Read(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(m_address + offset::Player::pPlayerBody); - if (m_pPlayerBody) - m_pPlayerBones = m.Read(m_pPlayerBody + offset::PlayerBody::PlayerBones); - - m_pProfile = m.Read(m_address + offset::Player::pProfile); - if (m_pProfile) + if (m_kind == CPlayerKind::EftPlayer) { - m_pInfo = m.Read(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(m_pInfo + offset::InfoClass::profileInfoSettingsClass); - if (m_pSetting) - m_iSpawnType = m.Read(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(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(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.Dictionary_0_1 contains BodyPartState entries. - const uintptr_t bodyParts = m.Read(healthController + offset::HealthController::BodyParts); - if (!bodyParts) - return; - - const uintptr_t entries = m.Read(bodyParts + offset::HealthController::DictionaryEntries); - const int entryCount = m.Read(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(i) * offset::HealthController::EntryStride; - const uintptr_t bodyPartState = m.Read(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(bodyPartState + offset::HealthController::BodyPartHealth); - if (!healthValue) - continue; - - m_fHealth += m.Read(healthValue + offset::HealthController::HealthValue + offset::HealthController::Current); - m_fHealthMax += m.Read(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(m_pWeaponAnimation + offset::WeaponAnimations::isAiming); -} - -Vector3 CPlayer::GetBonePosition(const int BoneId) +Vector3 CPlayer::GetBonePosition(const int BoneId) const { constexpr int kBoneCount = static_cast(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(m_pPlayerBones + offset::PlayerBones::RootJoint); + m.TryRead(m_pPlayerBones + offset::PlayerBones::RootJoint, transform); break; case BONE_NECK: - transform = m.Read(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(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(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(m_pPlayerBones + offset::PlayerBones::LeftPalm); + m.TryRead(m_pPlayerBones + offset::PlayerBones::LeftPalm, transform); break; case BONE_RIGHT_PALM: - transform = m.Read(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(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(m_pBoneArray.address[BoneId]); + if (!Memory::IsUserAddress(legacyTransform) || legacyTransform == transform) return {}; return ReadTransformPositionWithFallback(legacyTransform); diff --git a/CFramework/Cheat/SDK/CPlayer/CPlayer.h b/CFramework/Cheat/SDK/CPlayer/CPlayer.h index 23e23cb..ab35a86 100644 --- a/CFramework/Cheat/SDK/CPlayer/CPlayer.h +++ b/CFramework/Cheat/SDK/CPlayer/CPlayer.h @@ -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 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 m_vecBoneList{}; - bool IsAiming(); + bool Update(); + bool UpdateStatic(CPlayerKind hint = CPlayerKind::Unknown); + void UpdateBone(bool buildSkeleton = false); + void UpdateHealth(); - Vector3 GetBonePosition(const int BoneId); -}; \ No newline at end of file + 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; +}; diff --git a/CFramework/Cheat/SDK/GameSDK/GameSDK.cpp b/CFramework/Cheat/SDK/GameSDK/GameSDK.cpp index 2f54d4a..5f13778 100644 --- a/CFramework/Cheat/SDK/GameSDK/GameSDK.cpp +++ b/CFramework/Cheat/SDK/GameSDK/GameSDK.cpp @@ -1,8 +1,10 @@ -#include "GameSDK.h" +#include "GameSDK.h" +#include +#include 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(transform + 0x38); - unsigned int Index = m.Read(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(pTransformAccessReadOnly.pTransformData + 0x18); + if (!m.TryRead(data.pTransformArray + offset::UnityTransform::MatrixStride * static_cast(index), result)) + return {}; - if (transform_data.pTransformArray && transform_data.pTransformIndices) - { - result = m.Read<__m128>(transform_data.pTransformArray + 0x30 * Index); - int transform_index = m.Read(transform_data.pTransformIndices + 0x4 * Index); - int safe = 0; - while (transform_index >= 0 && safe++ < 200) - { - Matrix34 matrix = m.Read(transform_data.pTransformArray + 0x30 * transform_index); + int transformIndex = -1; + if (!m.TryRead(data.pTransformIndices + offset::UnityTransform::IndexStride * static_cast(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(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(&matrix.vec1), 0x00)); + __m128 yyyy = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast(&matrix.vec1), 0x55)); + __m128 zwxy = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast(&matrix.vec1), 0x8E)); + __m128 wzyw = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast(&matrix.vec1), 0xDB)); + __m128 zzzz = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast(&matrix.vec1), 0xAA)); + __m128 yxwy = _mm_castsi128_ps(_mm_shuffle_epi32(*reinterpret_cast(&matrix.vec1), 0x71)); + __m128 tmp7 = _mm_mul_ps(*reinterpret_cast(&matrix.vec2), result); - transform_index = m.Read(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(&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(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(listPtr); - BaseObject lastObject = m.Read(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(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(activeObject.nextObjectLink); - } - } - if (lastObject.object != NULL) - { - classNamePtr = m.Read(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(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(m.m_gBaseAddress + offset::dwGameObjectManager); - if (!pGOM) - { - m_objGameObjectManager = {}; - return false; - } - - m_objGameObjectManager = m.Read(pGOM); - - return m_objGameObjectManager.lastActiveObject != 0; + std::lock_guard lock(m_stateMutex); + return InitAddressUnlocked(); } bool CGame::Update() { - m_pGameWorld = 0; - m_pLocalGameWorld = 0; - - // GameWorld - uintptr_t activeNodes = m.Read(m_objGameObjectManager.ActiveNodes); - uintptr_t lastActiveNode = m.Read(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 lock(m_stateMutex); + return UpdateWorldUnlocked(); } bool CGame::UpdateCamera() { - m_pFpsCamera = 0; - - uintptr_t all_camera_ptr = m.Read(m.m_gBaseAddress + offset::dwAllCamera); - if (!all_camera_ptr) - return false; - - Camera all_cameras = m.Read(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(all_cameras.list + (i * 0x8)); - if (camera != 0) - { - uintptr_t camera_obj = m.Read(camera + 0x30); - if (!camera_obj) - continue; - - uintptr_t camera_name_ptr = m.Read(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 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 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 lock(m_stateMutex); + return Memory::IsUserAddress(m_pGameWorld) && Memory::IsUserAddress(m_pLocalGameWorld); +} - // ‚±‚±�ȗª‚ł«‚邩ƒ`ƒFƒbƒN - uintptr_t dw = m.Read(m_pFpsCamera + 0x30); - if (!dw) - return {}; +Matrix CGame::GetViewMatrix() const +{ + std::lock_guard lock(m_stateMutex); + return m_mxViewMatrix; +} - uintptr_t viewmatrix_ptr = m.Read(dw + 0x18); - if (!viewmatrix_ptr) - return {}; - - return m.Read(viewmatrix_ptr + 0x100); -} \ No newline at end of file +GameStateSnapshot CGame::GetState() const +{ + std::lock_guard 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; +} diff --git a/CFramework/Cheat/SDK/GameSDK/GameSDK.h b/CFramework/Cheat/SDK/GameSDK/GameSDK.h index 63942cd..f505b6c 100644 --- a/CFramework/Cheat/SDK/GameSDK/GameSDK.h +++ b/CFramework/Cheat/SDK/GameSDK/GameSDK.h @@ -3,30 +3,54 @@ #include "Struct.h" #include "../../../Framework/Memory/Memory.h" #include "../../../Framework/Config/Config.h" +#include + +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); \ No newline at end of file +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); diff --git a/CFramework/Cheat/SDK/GameSDK/Offset.h b/CFramework/Cheat/SDK/GameSDK/Offset.h index 7e3054e..d467339 100644 --- a/CFramework/Cheat/SDK/GameSDK/Offset.h +++ b/CFramework/Cheat/SDK/GameSDK/Offset.h @@ -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; // k__BackingField - constexpr auto pWeaponAnimation = 0x1E0; // k__BackingField - constexpr auto pProfile = 0x618; // 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 namespace HealthController { - constexpr auto BodyParts = 0x68; // Dictionary> + 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; } } - diff --git a/CFramework/Cheat/SDK/GameSDK/Struct.h b/CFramework/Cheat/SDK/GameSDK/Struct.h index 5e25381..5d67272 100644 --- a/CFramework/Cheat/SDK/GameSDK/Struct.h +++ b/CFramework/Cheat/SDK/GameSDK/Struct.h @@ -1,5 +1,6 @@ -#pragma once +#pragma once #include +#include #include 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, diff --git a/CFramework/Cheat/UpdateList.cpp b/CFramework/Cheat/UpdateList.cpp index b7dac80..be1c1e7 100644 --- a/CFramework/Cheat/UpdateList.cpp +++ b/CFramework/Cheat/UpdateList.cpp @@ -1,158 +1,231 @@ #include "Framework.h" +#include +#include +#include -// ‚¢‚­‚炩‚̃ŠƒXƒg‚ð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; -// ƒAƒCƒeƒ€‚ð1RPM‚œǂݎæ‚邽‚߂̍\‘¢‘Ì -struct CGameItemList { - uint64_t address[2048]{}; -}; + bool ReadManagedPointerList(uintptr_t listObject, std::vector& 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(list.list_address + offset::ManagedArray::Data, + static_cast(list.count), out, static_cast(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)); - - // ‰¼‚̃ŠƒXƒg—p•ϐ” - std::vector list_player{}; - std::vector list_grenade{}; + const auto tickStart = std::chrono::steady_clock::now(); + const WorkerConfigSnapshot config = GetWorkerConfig(); + auto snapshot = std::make_shared(); + 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(GameWorld + offset::RegisteredPlayers); - const auto entity_array = m.Read(registered_player); - - if (entity_array.count <= 0) - continue; - else if (entity_array.list_address == NULL) - continue; - - local.m_address = m.Read(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(entity_array.list_address + 0x20); - - // ESP—pEntityList‚ð�\’z - 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 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 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 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); } } -// —v‰ü‘P -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 list_item{}; - std::vector list_exfil{}; - tarkov->UpdateCamera(); + const WorkerConfigSnapshot config = GetWorkerConfig(); + auto staticSnapshot = std::make_shared(); + 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(GameWorld + offset::ExfilController); - const auto exfil_array = m.Read(exfil_controller + 0x28); - auto exfil_list = m.Read(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(tarkov->m_pLocalGameWorld + offset::LootList); - const auto ItemArray = m.Read(LootList); - - if (ItemArray.count != 0) - { - auto item_list = m.Read(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 lock1(m_mtxExfilList); - std::lock_guard 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 exfilAddresses; + if (m.TryRead(exfilArray + offset::ManagedArray::Length, exfilCount) && exfilCount > 0 && exfilCount <= kMaxExfils && + m.TryReadVector(exfilArray + offset::ManagedArray::Data, static_cast(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 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); } -} \ No newline at end of file +} diff --git a/CFramework/Cheat/Visual.cpp b/CFramework/Cheat/Visual.cpp index 0e6906d..1af4e22 100644 --- a/CFramework/Cheat/Visual.cpp +++ b/CFramework/Cheat/Visual.cpp @@ -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(tarkov->m_pLocalGameWorld + offset::GrenadeList); - const auto grenade_array_ptr = m.Read(grenade_class + 0x18); - const auto grenade_array = m.Read(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(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()); } } -} \ No newline at end of file +} diff --git a/CFramework/Framework/Config/Config.h b/CFramework/Framework/Config/Config.h index 3ef6188..8892e75 100644 --- a/CFramework/Framework/Config/Config.h +++ b/CFramework/Framework/Config/Config.h @@ -1,10 +1,11 @@ -#pragma once +#pragma once #include +#include struct Globals { // System - bool process_active = true; + std::atomic_bool process_active{ true }; bool ShowMenu = false; // GameWindow Data diff --git a/CFramework/Framework/Memory/Memory.cpp b/CFramework/Framework/Memory/Memory.cpp index de888f2..20ecfb8 100644 --- a/CFramework/Framework/Memory/Memory.cpp +++ b/CFramework/Framework/Memory/Memory.cpp @@ -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) diff --git a/CFramework/Framework/Memory/Memory.h b/CFramework/Framework/Memory/Memory.h index d9ee8c9..b170f7d 100644 --- a/CFramework/Framework/Memory/Memory.h +++ b/CFramework/Framework/Memory/Memory.h @@ -5,116 +5,166 @@ #include #include #include +#include +#include /* - [+] メモリやオーバーレイの初期化モードを設定します - - 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 - constexpr const T Read(const uintptr_t& address) const noexcept - { - T value{}; - ReadProcessMemory(pHandle, reinterpret_cast(address), &value, sizeof(T), NULL); - return value; - } - template - constexpr void Write(const uintptr_t& address, const T& value) const noexcept - { - WriteProcessMemory(pHandle, reinterpret_cast(address), &value, sizeof(T), NULL); - } + static bool IsUserAddress(const uintptr_t address) noexcept + { + return address > 0x10000ULL && address < 0x0000800000000000ULL; + } - template - bool TryRead(const uintptr_t& address, T& value) const noexcept - { - value = T{}; - if (!pHandle || !address) - return false; + template + 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(address), &value, sizeof(T), &sizeRead) - && sizeRead == sizeof(T); - } + SIZE_T sizeRead = 0; + return !!ReadProcessMemory(pHandle, reinterpret_cast(address), &value, sizeof(T), &sizeRead) + && sizeRead == sizeof(T); + } - template - bool TryWrite(const uintptr_t& address, const T& value) const noexcept - { - if (!pHandle || !address) - return false; + template + 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(address), &value, sizeof(T), &sizeWritten) - && sizeWritten == sizeof(T); - } + SIZE_T sizeWritten = 0; + return !!WriteProcessMemory(pHandle, reinterpret_cast(address), &value, sizeof(T), &sizeWritten) + && sizeWritten == sizeof(T); + } - uintptr_t ReadChain(uintptr_t address, const std::vector& 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(address), buffer, size, &sizeRead) + && sizeRead == size; + } - uintptr_t result = Read(address + offsets.front()); - for (std::size_t i = 1; i < offsets.size(); ++i) - { - if (!result) - return 0; + template + bool TryReadVector(const uintptr_t address, const std::size_t count, std::vector& 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::max)() / sizeof(T)) + return false; - result = Read(result + offsets[i]); - } + out.resize(count); + if (!TryReadBuffer(address, out.data(), static_cast(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 + 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 + void Write(const uintptr_t& address, const T& value) const noexcept + { + TryWrite(address, value); + } - char Buffer[256]{}; - if (length >= static_cast(sizeof(Buffer))) - length = static_cast(sizeof(Buffer)) - 1; + uintptr_t ReadChain(uintptr_t address, const std::vector& offsets) const + { + if (!IsUserAddress(address) || offsets.empty()) + return 0; - if (!ReadProcessMemory(pHandle, reinterpret_cast(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(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 buffer(static_cast(length) + 1, '\0'); + SIZE_T sizeRead = 0; + if (!pHandle || !ReadProcessMemory(pHandle, reinterpret_cast(address), buffer.data(), length, &sizeRead) || sizeRead == 0) + return {}; + + buffer[(std::min)(static_cast(length), static_cast(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 buffer(static_cast(length) + 1, L'\0'); + const SIZE_T bytes = static_cast(length) * sizeof(wchar_t); + SIZE_T sizeRead = 0; + if (!pHandle || !ReadProcessMemory(pHandle, reinterpret_cast(address), buffer.data(), bytes, &sizeRead) || sizeRead == 0) + return {}; + + buffer[(std::min)(static_cast(length), static_cast(sizeRead / sizeof(wchar_t)))] = L'\0'; + return std::wstring(buffer.data()); + } }; -extern Memory m; \ No newline at end of file +extern Memory m; diff --git a/CFramework/main.cpp b/CFramework/main.cpp index 7356534..a089d50 100644 --- a/CFramework/main.cpp +++ b/CFramework/main.cpp @@ -18,6 +18,8 @@ auto cheat = std::make_unique(); 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(); diff --git a/README.md b/README.md index 4d8b458..56c178d 100644 --- a/README.md +++ b/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 diff --git a/REFACTOR_NOTES.md b/REFACTOR_NOTES.md new file mode 100644 index 0000000..02da663 --- /dev/null +++ b/REFACTOR_NOTES.md @@ -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` + `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。