CFramework/CFramework/Cheat/Features.cpp

605 lines
19 KiB
C++

#include "Framework.h"
#include <cmath>
// 只写入 dump 明确的对象字段;后座与故障也只使用本轮已确认的链路,弹道追踪暂不写。
void CFramework::GetESPInfo(const int& SpawnType, std::string& vOutStr, ImColor& vOutColor)
{
switch (SpawnType)
{
case SCAV:
vOutStr = "Scav";
vOutColor = Col_ESP_Scav;
break;
case SNIPER_SCAV:
vOutStr = "Sniper";
vOutColor = Col_ESP_Scav;
break;
case RESHALA_BOSS:
vOutStr = "Reshala";
vOutColor = Col_ESP_Boss;
break;
case RESHALA_FOLLOW:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case KILLA_BOSS:
vOutStr = "Killa";
vOutColor = Col_ESP_Boss;
break;
case SHTURMAN_BOSS:
vOutStr = "Shturman";
vOutColor = Col_ESP_Boss;
break;
case SHTURMAN_FOLLOW:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case GLUKHAR_BOSS:
vOutStr = "Glukhar";
vOutColor = Col_ESP_Boss;
break;
case GLUKHAR_FOLLOW_01:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case GLUKHAR_FOLLOW_02:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case GLUKHAR_FOLLOW_03:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case SANITOR_BOSS:
vOutStr = "Sanitor";
vOutColor = Col_ESP_Boss;
break;
case SANITOR_FOLLOW:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case NORMAL_SCAV:
vOutStr = "Scav";
vOutColor = Col_ESP_Scav;
break;
case CULTIST_01_SCAV:
vOutStr = "Cultist";
vOutColor = Col_ESP_SpecialScav;
break;
case CULTIST_02_SCAV:
vOutStr = "Cultist";
vOutColor = Col_ESP_SpecialScav;
break;
case TAGILLA_BOSS:
vOutStr = "Tagilla";
vOutColor = Col_ESP_Boss;
break;
case ROGUE_SCAV:
vOutStr = "Rogue";
vOutColor = Col_ESP_SpecialScav;
break;
case ZRYACHIY_BOSS:
vOutStr = "Zryachiy";
vOutColor = Col_ESP_Boss;
break;
case ZRYACHIY_FOLLOW:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case KABAN_BOSS:
vOutStr = "Kaban";
vOutColor = Col_ESP_Boss;
break;
case KABAN_FOLLOW_0:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case KABAN_FOLLOW_1:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case KABAN_FOLLOW_2:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case KABAN_FOLLOW_3:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case KOLLONTAY_BOSS:
vOutStr = "Kollontay";
vOutColor = Col_ESP_Boss;
break;
case KOLLONTAY_FOLLOW_01:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case KOLLONTAY_FOLLOW_02:
vOutStr = "follower";
vOutColor = Col_ESP_SpecialScav;
break;
case BTR_VEHICLE:
vOutStr = "BTR-82";
vOutColor = ImColor(1.f, 1.f, 1.f, 1.f);
break;
case SCAV_PARTISAN:
vOutStr = "Partisan";
vOutColor = Col_ESP_Scav;
break;
case RAIDER_SCAV:
vOutStr = "Raider";
vOutColor = Col_ESP_SpecialScav;
break;
case KNIGHT_SCAV:
vOutStr = "Knight";
vOutColor = Col_ESP_SpecialScav;
break;
case BIGPIPE_SCAV:
vOutStr = "BigPipe";
vOutColor = Col_ESP_SpecialScav;
break;
case BRIDEYE_SCAV:
vOutStr = "Birdeye";
vOutColor = Col_ESP_SpecialScav;
break;
case BLOODHOUND_SCAV:
vOutStr = "Bloodhound";
vOutColor = Col_ESP_SpecialScav;
break;
case PMC_BEAR_PvE:
vOutStr = "BEAR";
vOutColor = Col_ESP_PMC;
break;
case PMC_USEC_PvE:
vOutStr = "USEC";
vOutColor = Col_ESP_PMC;
break;
case PMC_BEAR_NEW:
vOutStr = "BEAR";
vOutColor = Col_ESP_PMC;
break;
case PMC_USEC_NEW:
vOutStr = "USEC";
vOutColor = Col_ESP_PMC;
break;
default:
vOutStr = "InValid";
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()
{
const auto snapshot = GetDynamicSnapshot();
if (!snapshot || !snapshot->inRaid || !snapshot->local.IsValid())
{
local = {};
ResetFeatureOverrides();
return;
}
// 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;
}
if (g.g_InfStamina)
UpdateStaminaOverride();
else
ResetStaminaOverride();
if (g.g_NoFallDamage)
UpdateFallDamageOverride();
else
ResetFallDamageOverride();
if (g.g_NoSway)
UpdateSwayOverride();
else
ResetSwayOverride();
if (g.g_NoRecoil)
UpdateNoRecoilOverride();
else
ResetNoRecoilOverride();
if (g.g_NoMalfunction)
UpdateNoMalfunctionOverride();
else
ResetNoMalfunctionOverride();
}
bool CFramework::ResolveCurrentWeapon(uintptr_t& weaponAnimation, uintptr_t& shotEffector,
uintptr_t& weapon, uintptr_t& weaponTemplate)
{
weaponAnimation = 0;
shotEffector = 0;
weapon = 0;
weaponTemplate = 0;
if (!IsUsableAddress(local.m_address) ||
!m.TryRead(local.m_address + offset::Player::pWeaponAnimation, weaponAnimation) ||
!IsUsableAddress(weaponAnimation) ||
!m.TryRead(weaponAnimation + offset::WeaponAnimations::ShotEffector, shotEffector) ||
!IsUsableAddress(shotEffector) ||
!m.TryRead(shotEffector + offset::ShotEffector::MainWeaponInHands, weapon) ||
!IsUsableAddress(weapon) ||
!m.TryRead(weapon + offset::Weapon::Template, weaponTemplate) ||
!IsUsableAddress(weaponTemplate))
{
return false;
}
return true;
}
void CFramework::UpdateNoRecoilOverride()
{
uintptr_t weaponAnimation = 0;
uintptr_t shotEffector = 0;
uintptr_t weapon = 0;
uintptr_t weaponTemplate = 0;
uintptr_t recoilSettings = 0;
if (!ResolveCurrentWeapon(weaponAnimation, shotEffector, weapon, weaponTemplate) ||
!m.TryRead(weaponTemplate + offset::WeaponTemplate::WeaponRecoilSettings, recoilSettings) ||
!IsUsableAddress(recoilSettings))
{
ResetNoRecoilOverride();
return;
}
if (m_noRecoilOverride.captured &&
(m_noRecoilOverride.player != local.m_address ||
m_noRecoilOverride.weaponAnimation != weaponAnimation ||
m_noRecoilOverride.shotEffector != shotEffector ||
m_noRecoilOverride.weapon != weapon ||
m_noRecoilOverride.weaponTemplate != weaponTemplate ||
m_noRecoilOverride.recoilSettings != recoilSettings))
{
ResetNoRecoilOverride();
}
if (!m_noRecoilOverride.captured)
{
bool enable = false;
if (!m.TryRead(recoilSettings + offset::WeaponRecoil::Enable, enable))
{
ResetNoRecoilOverride();
return;
}
m_noRecoilOverride.player = local.m_address;
m_noRecoilOverride.weaponAnimation = weaponAnimation;
m_noRecoilOverride.shotEffector = shotEffector;
m_noRecoilOverride.weapon = weapon;
m_noRecoilOverride.weaponTemplate = weaponTemplate;
m_noRecoilOverride.recoilSettings = recoilSettings;
m_noRecoilOverride.enable = enable;
m_noRecoilOverride.captured = true;
}
// WeaponRecoil.Enable is a dump-confirmed instance field. Do not touch its Values array yet.
m.TryWrite<bool>(recoilSettings + offset::WeaponRecoil::Enable, false);
}
void CFramework::UpdateNoMalfunctionOverride()
{
uintptr_t weaponAnimation = 0;
uintptr_t shotEffector = 0;
uintptr_t weapon = 0;
uintptr_t weaponTemplate = 0;
uintptr_t malfunctionState = 0;
if (!ResolveCurrentWeapon(weaponAnimation, shotEffector, weapon, weaponTemplate) ||
!m.TryRead(weapon + offset::Weapon::MalfState, malfunctionState) ||
!IsUsableAddress(malfunctionState))
{
ResetNoMalfunctionOverride();
return;
}
if (m_noMalfunctionOverride.captured &&
(m_noMalfunctionOverride.player != local.m_address ||
m_noMalfunctionOverride.weapon != weapon ||
m_noMalfunctionOverride.weaponTemplate != weaponTemplate ||
m_noMalfunctionOverride.malfunctionState != malfunctionState))
{
ResetNoMalfunctionOverride();
}
if (!m_noMalfunctionOverride.captured)
{
bool allowJam = false;
bool allowFeed = false;
bool allowMisfire = false;
bool allowSlide = false;
float baseChance = 0.f;
int state = 0;
if (!m.TryRead(weaponTemplate + offset::WeaponTemplate::AllowJam, allowJam) ||
!m.TryRead(weaponTemplate + offset::WeaponTemplate::AllowFeed, allowFeed) ||
!m.TryRead(weaponTemplate + offset::WeaponTemplate::AllowMisfire, allowMisfire) ||
!m.TryRead(weaponTemplate + offset::WeaponTemplate::AllowSlide, allowSlide) ||
!m.TryRead(weaponTemplate + offset::WeaponTemplate::BaseMalfunctionChance, baseChance) ||
!m.TryRead(malfunctionState + offset::WeaponMalfunctionState::State, state) ||
!std::isfinite(baseChance))
{
ResetNoMalfunctionOverride();
return;
}
m_noMalfunctionOverride.player = local.m_address;
m_noMalfunctionOverride.weapon = weapon;
m_noMalfunctionOverride.weaponTemplate = weaponTemplate;
m_noMalfunctionOverride.malfunctionState = malfunctionState;
m_noMalfunctionOverride.allowJam = allowJam;
m_noMalfunctionOverride.allowFeed = allowFeed;
m_noMalfunctionOverride.allowMisfire = allowMisfire;
m_noMalfunctionOverride.allowSlide = allowSlide;
m_noMalfunctionOverride.baseChance = baseChance;
m_noMalfunctionOverride.state = state;
m_noMalfunctionOverride.captured = true;
}
m.TryWrite<bool>(weaponTemplate + offset::WeaponTemplate::AllowJam, false);
m.TryWrite<bool>(weaponTemplate + offset::WeaponTemplate::AllowFeed, false);
m.TryWrite<bool>(weaponTemplate + offset::WeaponTemplate::AllowMisfire, false);
m.TryWrite<bool>(weaponTemplate + offset::WeaponTemplate::AllowSlide, false);
m.TryWrite<float>(weaponTemplate + offset::WeaponTemplate::BaseMalfunctionChance, 0.f);
m.TryWrite<int>(malfunctionState + offset::WeaponMalfunctionState::State, 0);
}
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::ResetNoRecoilOverride()
{
if (m_noRecoilOverride.captured && IsUsableAddress(m_noRecoilOverride.recoilSettings))
{
m.TryWrite<bool>(m_noRecoilOverride.recoilSettings + offset::WeaponRecoil::Enable,
m_noRecoilOverride.enable);
}
m_noRecoilOverride = {};
}
void CFramework::ResetNoMalfunctionOverride()
{
if (m_noMalfunctionOverride.captured)
{
if (IsUsableAddress(m_noMalfunctionOverride.weaponTemplate))
{
m.TryWrite<bool>(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::AllowJam,
m_noMalfunctionOverride.allowJam);
m.TryWrite<bool>(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::AllowFeed,
m_noMalfunctionOverride.allowFeed);
m.TryWrite<bool>(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::AllowMisfire,
m_noMalfunctionOverride.allowMisfire);
m.TryWrite<bool>(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::AllowSlide,
m_noMalfunctionOverride.allowSlide);
m.TryWrite<float>(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::BaseMalfunctionChance,
m_noMalfunctionOverride.baseChance);
}
if (IsUsableAddress(m_noMalfunctionOverride.malfunctionState))
{
m.TryWrite<int>(m_noMalfunctionOverride.malfunctionState + offset::WeaponMalfunctionState::State,
m_noMalfunctionOverride.state);
}
}
m_noMalfunctionOverride = {};
}
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();
ResetNoRecoilOverride();
ResetNoMalfunctionOverride();
}