From 632dc46116dd97bb2ef7166ad007e9ba5eef840a Mon Sep 17 00:00:00 2001 From: kedou Date: Fri, 7 Aug 2026 22:44:57 +0800 Subject: [PATCH] feat: add dump-backed runtime modifiers --- CFramework/Cheat/Features.cpp | 242 +++++++++++++++++++++++++- CFramework/Cheat/Framework.h | 45 +++++ CFramework/Cheat/Menu.cpp | 21 ++- CFramework/Cheat/SDK/GameSDK/Offset.h | 16 ++ CFramework/Cheat/UpdateList.cpp | 6 +- CFramework/Framework/Config/Config.h | 6 +- CFramework/Framework/Memory/Memory.h | 25 +++ CFramework/main.cpp | 6 +- 8 files changed, 358 insertions(+), 9 deletions(-) diff --git a/CFramework/Cheat/Features.cpp b/CFramework/Cheat/Features.cpp index 738d938..f6b126f 100644 --- a/CFramework/Cheat/Features.cpp +++ b/CFramework/Cheat/Features.cpp @@ -1,7 +1,7 @@ #include "Framework.h" +#include -// 内存修改类功能 (NoSway/InfStamina/NoFallDmg) 已移除: -// 它们依赖 IL2CPP 版偏移, 在 EXFIL DELTA (Mono 0.16.9) 上偏移不兼容, 会乱写内存。 +// 只实现 dump 明确的对象字段;后座/故障链路等类布局确认后再接入,当前不写内存。 void CFramework::GetESPInfo(const int& SpawnType, std::string& vOutStr, ImColor& vOutColor) { @@ -168,4 +168,240 @@ void CFramework::GetESPInfo(const int& SpawnType, std::string& vOutStr, ImColor& vOutColor = ImColor(1.f, 1.f, 1.f, 1.f); break; }; -} \ No newline at end of file +} + +namespace +{ + bool IsUsableAddress(const uintptr_t address) + { + // x64 user-mode canonical range; reject null/obviously bogus chains before any write. + return address > 0x10000ULL && address < 0x0000800000000000ULL; + } + + bool IsFiniteValue(const float value) + { + return std::isfinite(value); + } + + bool IsFiniteVector(const Vector3& value) + { + return IsFiniteValue(value.x) && IsFiniteValue(value.y) && IsFiniteValue(value.z); + } + + bool IsUsableCapacity(const float value) + { + return IsFiniteValue(value) && value > 0.f && value < 100000.f; + } +} + +void CFramework::UpdateFeatures() +{ + if (!tarkov->IsInRaid() || !IsUsableAddress(tarkov->m_pLocalGameWorld)) + { + 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)) + { + ResetFeatureOverrides(); + return; + } + local.m_address = currentLocal; + + if (g.g_InfStamina) + + UpdateStaminaOverride(); + else + ResetStaminaOverride(); + + if (g.g_NoFallDamage) + UpdateFallDamageOverride(); + else + ResetFallDamageOverride(); + + if (g.g_NoSway) + UpdateSwayOverride(); + else + ResetSwayOverride(); +} + +void CFramework::UpdateStaminaOverride() +{ + uintptr_t physical = 0; + uintptr_t stamina = 0; + uintptr_t handsStamina = 0; + + if (!m.TryRead(local.m_address + offset::Player::pPhysics, physical) || + !IsUsableAddress(physical) || + !m.TryRead(physical + offset::Physics::Stamina, stamina) || + !m.TryRead(physical + offset::Physics::HandsStamina, handsStamina) || + !IsUsableAddress(stamina) || + !IsUsableAddress(handsStamina)) + { + ResetStaminaOverride(); + return; + } + + if (m_staminaOverride.captured && + (m_staminaOverride.player != local.m_address || + m_staminaOverride.physical != physical || + m_staminaOverride.stamina != stamina || + m_staminaOverride.handsStamina != handsStamina)) + { + ResetStaminaOverride(); + } + + float staminaCapacity = 0.f; + float handsCapacity = 0.f; + if (!m.TryRead(physical + offset::Physics::StaminaCapacity, staminaCapacity) || + !m.TryRead(physical + offset::Physics::HandsCapacity, handsCapacity) || + !IsUsableCapacity(staminaCapacity) || + !IsUsableCapacity(handsCapacity)) + { + ResetStaminaOverride(); + return; + } + + if (!m_staminaOverride.captured) + { + float staminaCurrent = 0.f; + float handsCurrent = 0.f; + if (!m.TryRead(stamina + offset::PhysicalState::Current, staminaCurrent) || + !m.TryRead(handsStamina + offset::PhysicalState::Current, handsCurrent) || + !IsFiniteValue(staminaCurrent) || + !IsFiniteValue(handsCurrent)) + { + ResetStaminaOverride(); + return; + } + + m_staminaOverride.player = local.m_address; + m_staminaOverride.physical = physical; + m_staminaOverride.stamina = stamina; + m_staminaOverride.handsStamina = handsStamina; + m_staminaOverride.staminaCurrent = staminaCurrent; + m_staminaOverride.handsCurrent = handsCurrent; + m_staminaOverride.captured = true; + } + + // Both current fields and both capacities are dump-confirmed object fields. + m.TryWrite(stamina + offset::PhysicalState::Current, staminaCapacity); + m.TryWrite(handsStamina + offset::PhysicalState::Current, handsCapacity); +} + +void CFramework::UpdateFallDamageOverride() +{ + uintptr_t physical = 0; + if (!m.TryRead(local.m_address + offset::Player::pPhysics, physical) || + !IsUsableAddress(physical)) + { + ResetFallDamageOverride(); + return; + } + + if (m_fallDamageOverride.captured && + (m_fallDamageOverride.player != local.m_address || + m_fallDamageOverride.physical != physical)) + { + ResetFallDamageOverride(); + } + + if (!m_fallDamageOverride.captured) + { + float multiplier = 0.f; + if (!m.TryRead(physical + offset::Physics::FallDamageMultiplier, multiplier) || + !IsFiniteValue(multiplier)) + { + ResetFallDamageOverride(); + return; + } + + m_fallDamageOverride.player = local.m_address; + m_fallDamageOverride.physical = physical; + m_fallDamageOverride.multiplier = multiplier; + m_fallDamageOverride.captured = true; + } + + m.TryWrite(physical + offset::Physics::FallDamageMultiplier, 0.f); +} + +void CFramework::UpdateSwayOverride() +{ + uintptr_t weaponAnimation = 0; + uintptr_t motionEffector = 0; + if (!m.TryRead(local.m_address + offset::Player::pWeaponAnimation, weaponAnimation) || + !IsUsableAddress(weaponAnimation) || + !m.TryRead(weaponAnimation + offset::WeaponAnimations::MotionReact, motionEffector) || + !IsUsableAddress(motionEffector)) + { + ResetSwayOverride(); + return; + } + + if (m_swayOverride.captured && + (m_swayOverride.player != local.m_address || + m_swayOverride.weaponAnimation != weaponAnimation || + m_swayOverride.motionEffector != motionEffector)) + { + ResetSwayOverride(); + } + + if (!m_swayOverride.captured) + { + Vector3 swayFactors{}; + if (!m.TryRead(motionEffector + offset::MotionEffector::SwayFactors, swayFactors) || + !IsFiniteVector(swayFactors)) + { + ResetSwayOverride(); + return; + } + + m_swayOverride.player = local.m_address; + m_swayOverride.weaponAnimation = weaponAnimation; + m_swayOverride.motionEffector = motionEffector; + m_swayOverride.swayFactors = swayFactors; + m_swayOverride.captured = true; + } + + m.TryWrite(motionEffector + offset::MotionEffector::SwayFactors, Vector3{}); +} + +void CFramework::ResetStaminaOverride() +{ + if (m_staminaOverride.captured) + { + if (IsUsableAddress(m_staminaOverride.stamina)) + m.TryWrite(m_staminaOverride.stamina + offset::PhysicalState::Current, m_staminaOverride.staminaCurrent); + if (IsUsableAddress(m_staminaOverride.handsStamina)) + m.TryWrite(m_staminaOverride.handsStamina + offset::PhysicalState::Current, m_staminaOverride.handsCurrent); + } + + m_staminaOverride = {}; +} + +void CFramework::ResetFallDamageOverride() +{ + if (m_fallDamageOverride.captured && IsUsableAddress(m_fallDamageOverride.physical)) + m.TryWrite(m_fallDamageOverride.physical + offset::Physics::FallDamageMultiplier, m_fallDamageOverride.multiplier); + + m_fallDamageOverride = {}; +} + +void CFramework::ResetSwayOverride() +{ + if (m_swayOverride.captured && IsUsableAddress(m_swayOverride.motionEffector)) + m.TryWrite(m_swayOverride.motionEffector + offset::MotionEffector::SwayFactors, m_swayOverride.swayFactors); + + m_swayOverride = {}; +} + +void CFramework::ResetFeatureOverrides() +{ + ResetStaminaOverride(); + ResetFallDamageOverride(); + ResetSwayOverride(); +} diff --git a/CFramework/Cheat/Framework.h b/CFramework/Cheat/Framework.h index 020b1d7..f12931c 100644 --- a/CFramework/Cheat/Framework.h +++ b/CFramework/Cheat/Framework.h @@ -22,10 +22,52 @@ public: void RenderMenu(); void RenderESP(); void AimAll(); + void UpdateFeatures(); + void ResetFeatureOverrides(); private: + struct StaminaOverrideState + { + uintptr_t player{ 0 }; + uintptr_t physical{ 0 }; + uintptr_t stamina{ 0 }; + uintptr_t handsStamina{ 0 }; + float staminaCurrent{ 0.f }; + float handsCurrent{ 0.f }; + bool captured{ false }; + }; + + struct FallDamageOverrideState + { + uintptr_t player{ 0 }; + uintptr_t physical{ 0 }; + float multiplier{ 0.f }; + bool captured{ false }; + }; + + struct SwayOverrideState + { + uintptr_t player{ 0 }; + uintptr_t weaponAnimation{ 0 }; + uintptr_t motionEffector{ 0 }; + Vector3 swayFactors{}; + bool captured{ false }; + }; + + void UpdateStaminaOverride(); + void UpdateFallDamageOverride(); + void UpdateSwayOverride(); + void ResetStaminaOverride(); + void ResetFallDamageOverride(); + void ResetSwayOverride(); + CGame* tarkov = new CGame(); CPlayer local; + StaminaOverrideState m_staminaOverride{}; + FallDamageOverrideState m_fallDamageOverride{}; + SwayOverrideState m_swayOverride{}; + + std::mutex m_mtxEntityList; std::mutex m_mtxExfilList; std::mutex m_mtxItemList; @@ -81,6 +123,9 @@ private: void AimMemory(const Vector3& target, const Vector3& localPos); void ScanRotationOffset(); // 2阶段扫描: Player->MC 与 MC->Rotation 偏移 + // Runtime feature backup state is declared above. + + // 旋转偏移扫描器状态 int m_scanPhase = 0; // 0 = 待机/Step1, 1 = 等待转动鼠标后 Step2 char m_scanResult[192]{ 0 }; diff --git a/CFramework/Cheat/Menu.cpp b/CFramework/Cheat/Menu.cpp index 64d1a08..5587ef9 100644 --- a/CFramework/Cheat/Menu.cpp +++ b/CFramework/Cheat/Menu.cpp @@ -120,10 +120,16 @@ void CFramework::RenderMenu() ImGui::Separator(); ImGui::Spacing(); - ImGui::TextDisabled("内存修改类功能已移除"); - ImGui::TextDisabled("(Mono 版偏移不兼容, 防止乱写内存)"); + ImGui::Checkbox("无限耐力", &g.g_InfStamina); + ImGui::Checkbox("无跌落伤害", &g.g_NoFallDamage); + ImGui::Checkbox("无摇摆", &g.g_NoSway); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::TextDisabled("仅写入 dump 已确认的对象字段"); + ImGui::TextDisabled("后座 / 故障:等待下一轮类布局确认"); ImGui::EndChild(); - + ImGui::BeginChild("##C021", ImVec2(ImGui::GetContentRegionAvail()), true); ImGui::EndChild(); @@ -305,9 +311,18 @@ void CFramework::RenderMenu() case 2: // Misc ImGui::BeginChild("##C200", ImVec2(ImGui::GetContentRegionAvail()), true); + ImGui::Text("功能状态"); + ImGui::Separator(); + ImGui::Spacing(); + ImGui::Text("局内:%s", tarkov->IsInRaid() ? "是" : "否"); + ImGui::Text("本地:0x%llX", (unsigned long long)local.m_address); + ImGui::TextDisabled("关闭开关或离开局内会恢复原值"); + ImGui::TextDisabled("非法/不完整指针链不会写入"); + ImGui::EndChild(); break; case 3: // Setting + ImGui::BeginChild("##C300", ImVec2(ImGui::GetContentRegionAvail()), true); ImGui::Text("退出"); diff --git a/CFramework/Cheat/SDK/GameSDK/Offset.h b/CFramework/Cheat/SDK/GameSDK/Offset.h index ecfd4d3..a8219f6 100644 --- a/CFramework/Cheat/SDK/GameSDK/Offset.h +++ b/CFramework/Cheat/SDK/GameSDK/Offset.h @@ -102,9 +102,22 @@ namespace offset namespace WeaponAnimations { constexpr auto Breath = 0x30; + constexpr auto MotionReact = 0x40; // MotionEffector constexpr auto isAiming = 0x1C5; } + // -.MotionEffector + namespace MotionEffector + { + constexpr auto SwayFactors = 0x94; // UnityEngine.Vector3 + } + + // -.GClass774 (stamina/oxygen state) + namespace PhysicalState + { + constexpr auto Current = 0x48; // Single + } + // .BasePhysicalClass namespace Physics { @@ -112,6 +125,9 @@ namespace offset constexpr auto HandsStamina = 0x40; constexpr auto Oxygen = 0x48; constexpr auto FallDamageMultiplier = 0xBC; + constexpr auto StaminaCapacity = 0xC0; + constexpr auto HandsCapacity = 0xC8; constexpr auto OxygenRestoreRate = 0xD4; } } + diff --git a/CFramework/Cheat/UpdateList.cpp b/CFramework/Cheat/UpdateList.cpp index ee77edd..b7dac80 100644 --- a/CFramework/Cheat/UpdateList.cpp +++ b/CFramework/Cheat/UpdateList.cpp @@ -26,8 +26,12 @@ void CFramework::UpdateList() std::vector list_player{}; std::vector list_grenade{}; - if (g.g_ESP || g.g_Aim) + if (g.g_ESP || g.g_Aim || g.g_InfStamina || g.g_NoFallDamage || g.g_NoSway) { + + // Failed refreshes must not leave a stale player address available to memory features. + local.m_address = 0; + // not In raid if (!tarkov->Update()) continue; diff --git a/CFramework/Framework/Config/Config.h b/CFramework/Framework/Config/Config.h index 8008ea6..cbbd7f8 100644 --- a/CFramework/Framework/Config/Config.h +++ b/CFramework/Framework/Config/Config.h @@ -29,7 +29,11 @@ struct Globals float g_ESP_MaxDistance = 500.f; float g_ESP_MaxItemDistance = 50.f; - // Misc (内存修改类功能已移除: Mono 版偏移不兼容, 防止乱写内存) + // Misc (仅使用 dump 已确认的对象字段) + bool g_InfStamina = false; + bool g_NoFallDamage = false; + bool g_NoSway = false; + // Aim (自瞄) bool g_Aim = false; diff --git a/CFramework/Framework/Memory/Memory.h b/CFramework/Framework/Memory/Memory.h index d6b9959..d9ee8c9 100644 --- a/CFramework/Framework/Memory/Memory.h +++ b/CFramework/Framework/Memory/Memory.h @@ -46,11 +46,36 @@ public: { WriteProcessMemory(pHandle, reinterpret_cast(address), &value, sizeof(T), NULL); } + + template + bool TryRead(const uintptr_t& address, T& value) const noexcept + { + value = T{}; + if (!pHandle || !address) + return false; + + 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; + + 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; + uintptr_t result = Read(address + offsets.front()); for (std::size_t i = 1; i < offsets.size(); ++i) { diff --git a/CFramework/main.cpp b/CFramework/main.cpp index 23b92f6..7356534 100644 --- a/CFramework/main.cpp +++ b/CFramework/main.cpp @@ -23,6 +23,9 @@ void Overlay::OverlayUserFunction() if (g.g_ESP) cheat->RenderESP(); + // 只在局内且指针链完整时更新 dump 已确认的运行时字段。 + cheat->UpdateFeatures(); + // 自瞄/压枪 (Aim.cpp) cheat->AimAll(); @@ -91,9 +94,10 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine std::thread([&]() { cheat->UpdateStaticList(); }).detach(); overlay->OverlayLoop(); + g.process_active = false; + cheat->ResetFeatureOverrides(); overlay->DestroyOverlay(); m.DetachProcess(); - g.process_active = false; return 0; } \ No newline at end of file