feat: add dump-backed runtime modifiers

This commit is contained in:
kedou 2026-08-07 22:44:57 +08:00
parent 6fc8d8c16b
commit 632dc46116
8 changed files with 358 additions and 9 deletions

View File

@ -1,7 +1,7 @@
#include "Framework.h"
#include <cmath>
// 内存修改类功能 (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;
};
}
}
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<float>(stamina + offset::PhysicalState::Current, staminaCapacity);
m.TryWrite<float>(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<float>(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<Vector3>(motionEffector + offset::MotionEffector::SwayFactors, Vector3{});
}
void CFramework::ResetStaminaOverride()
{
if (m_staminaOverride.captured)
{
if (IsUsableAddress(m_staminaOverride.stamina))
m.TryWrite<float>(m_staminaOverride.stamina + offset::PhysicalState::Current, m_staminaOverride.staminaCurrent);
if (IsUsableAddress(m_staminaOverride.handsStamina))
m.TryWrite<float>(m_staminaOverride.handsStamina + offset::PhysicalState::Current, m_staminaOverride.handsCurrent);
}
m_staminaOverride = {};
}
void CFramework::ResetFallDamageOverride()
{
if (m_fallDamageOverride.captured && IsUsableAddress(m_fallDamageOverride.physical))
m.TryWrite<float>(m_fallDamageOverride.physical + offset::Physics::FallDamageMultiplier, m_fallDamageOverride.multiplier);
m_fallDamageOverride = {};
}
void CFramework::ResetSwayOverride()
{
if (m_swayOverride.captured && IsUsableAddress(m_swayOverride.motionEffector))
m.TryWrite<Vector3>(m_swayOverride.motionEffector + offset::MotionEffector::SwayFactors, m_swayOverride.swayFactors);
m_swayOverride = {};
}
void CFramework::ResetFeatureOverrides()
{
ResetStaminaOverride();
ResetFallDamageOverride();
ResetSwayOverride();
}

View File

@ -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 };

View File

@ -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("退出");

View File

@ -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;
}
}

View File

@ -26,8 +26,12 @@ void CFramework::UpdateList()
std::vector<CPlayer> list_player{};
std::vector<uintptr_t> 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;

View File

@ -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;

View File

@ -46,11 +46,36 @@ public:
{
WriteProcessMemory(pHandle, reinterpret_cast<void*>(address), &value, sizeof(T), NULL);
}
template <typename T>
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<const void*>(address), &value, sizeof(T), &sizeRead)
&& sizeRead == sizeof(T);
}
template <typename T>
bool TryWrite(const uintptr_t& address, const T& value) const noexcept
{
if (!pHandle || !address)
return false;
SIZE_T sizeWritten = 0;
return !!WriteProcessMemory(pHandle, reinterpret_cast<void*>(address), &value, sizeof(T), &sizeWritten)
&& sizeWritten == sizeof(T);
}
uintptr_t ReadChain(uintptr_t address, const std::vector<uintptr_t>& offsets)
{
if (!address || offsets.empty())
return 0;
uintptr_t result = Read<uintptr_t>(address + offsets.front());
for (std::size_t i = 1; i < offsets.size(); ++i)
{

View File

@ -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;
}