From 99d599026d18e0d45cad707c7836443b9401f20e Mon Sep 17 00:00:00 2001 From: kedou Date: Fri, 7 Aug 2026 23:37:55 +0800 Subject: [PATCH] feat: add recoil malfunction and aim fixes --- CFramework/Cheat/Aim.cpp | 49 +++++- CFramework/Cheat/Features.cpp | 196 ++++++++++++++++++++++- CFramework/Cheat/Framework.h | 36 +++++ CFramework/Cheat/Menu.cpp | 6 +- CFramework/Cheat/SDK/CPlayer/CPlayer.cpp | 14 ++ CFramework/Cheat/SDK/GameSDK/Offset.h | 36 +++++ CFramework/Framework/Config/Config.h | 8 +- 7 files changed, 331 insertions(+), 14 deletions(-) diff --git a/CFramework/Cheat/Aim.cpp b/CFramework/Cheat/Aim.cpp index 16ea25a..826d2ac 100644 --- a/CFramework/Cheat/Aim.cpp +++ b/CFramework/Cheat/Aim.cpp @@ -43,8 +43,19 @@ namespace return value; } + bool IsFiniteRotation(const Vector2& value) + { + return std::isfinite(value.x) && std::isfinite(value.y); + } + + bool IsUsableAimAddress(const uintptr_t address) + { + return address > 0x10000ULL && address < 0x0000800000000000ULL; + } + float FrameScale() { + float deltaTime = ImGui::GetIO().DeltaTime; if (!(deltaTime > 0.f) || deltaTime > 0.2f) deltaTime = 1.f / 60.f; @@ -382,14 +393,28 @@ void CFramework::AimMouse(const Vector2& target, const Vector2& center) // オフセットは g.g_AimMcOffset / g.g_AimRotOffset (运行时可变) を使用 void CFramework::AimMemory(const Vector3& target, const Vector3& localPos) { - uintptr_t mc = m.Read(local.m_address + (uintptr_t)g.g_AimMcOffset); - if (!mc) + if (!IsUsableAimAddress(local.m_address) || + g.g_AimMcOffset <= 0 || g.g_AimMcOffset > 0x1000 || + g.g_AimRotOffset < 0 || g.g_AimRotOffset > 0x1000) + { return; + } - // 現在の回転 [rad] - Vector2 rot = m.Read(mc + (uintptr_t)g.g_AimRotOffset); + uintptr_t mc = 0; + if (!m.TryRead(local.m_address + static_cast(g.g_AimMcOffset), mc) || + !IsUsableAimAddress(mc)) + { + return; + } + + // MovementContext.Rotation_1 is a dump-confirmed Vector2 at +0x27C in this build. + Vector2 rot{}; + if (!m.TryRead(mc + static_cast(g.g_AimRotOffset), rot) || + !IsFiniteRotation(rot)) + { + return; + } - // 目標までの方向 Vector3 delta = target - localPos; float len2d = sqrtf(delta.x * delta.x + delta.z * delta.z); if (len2d < 0.001f) @@ -401,19 +426,27 @@ void CFramework::AimMemory(const Vector3& target, const Vector3& localPos) yaw = ClampAngle(yaw); pitch = ClampAngle(pitch); - // Distance-adaptive exponential smoothing. It is never 0 or 1, so the - // slider cannot produce either a one-frame snap or a permanently lagging aim. float dyaw = ClampAngle(yaw - rot.x); float dpitch = ClampAngle(pitch - rot.y); float angleError = sqrtf(dyaw * dyaw + dpitch * dpitch); + if (!(angleError > 0.0001f) || !std::isfinite(angleError)) + return; + float t = AimResponse(angleError, g.g_AimSmooth, 0.75f); + // Cap the absolute angular step. AimResponse alone still gives a large + // first-frame move for a far target, which looks like a teleport in memory mode. + 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); + Vector2 newRot{ ClampAngle(rot.x + dyaw * t), ClampAngle(rot.y + dpitch * t) }; - m.Write(mc + (uintptr_t)g.g_AimRotOffset, newRot); + m.TryWrite(mc + static_cast(g.g_AimRotOffset), newRot); } // ============================================================ diff --git a/CFramework/Cheat/Features.cpp b/CFramework/Cheat/Features.cpp index f6b126f..437873c 100644 --- a/CFramework/Cheat/Features.cpp +++ b/CFramework/Cheat/Features.cpp @@ -1,7 +1,7 @@ #include "Framework.h" #include -// 只实现 dump 明确的对象字段;后座/故障链路等类布局确认后再接入,当前不写内存。 +// 只写入 dump 明确的对象字段;后座与故障也只使用本轮已确认的链路,弹道追踪暂不写。 void CFramework::GetESPInfo(const int& SpawnType, std::string& vOutStr, ImColor& vOutColor) { @@ -227,10 +227,162 @@ void CFramework::UpdateFeatures() 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(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(weaponTemplate + offset::WeaponTemplate::AllowJam, false); + m.TryWrite(weaponTemplate + offset::WeaponTemplate::AllowFeed, false); + m.TryWrite(weaponTemplate + offset::WeaponTemplate::AllowMisfire, false); + m.TryWrite(weaponTemplate + offset::WeaponTemplate::AllowSlide, false); + m.TryWrite(weaponTemplate + offset::WeaponTemplate::BaseMalfunctionChance, 0.f); + m.TryWrite(malfunctionState + offset::WeaponMalfunctionState::State, 0); } void CFramework::UpdateStaminaOverride() { + uintptr_t physical = 0; uintptr_t stamina = 0; uintptr_t handsStamina = 0; @@ -370,8 +522,48 @@ void CFramework::UpdateSwayOverride() m.TryWrite(motionEffector + offset::MotionEffector::SwayFactors, Vector3{}); } +void CFramework::ResetNoRecoilOverride() +{ + if (m_noRecoilOverride.captured && IsUsableAddress(m_noRecoilOverride.recoilSettings)) + { + m.TryWrite(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(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::AllowJam, + m_noMalfunctionOverride.allowJam); + m.TryWrite(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::AllowFeed, + m_noMalfunctionOverride.allowFeed); + m.TryWrite(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::AllowMisfire, + m_noMalfunctionOverride.allowMisfire); + m.TryWrite(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::AllowSlide, + m_noMalfunctionOverride.allowSlide); + m.TryWrite(m_noMalfunctionOverride.weaponTemplate + offset::WeaponTemplate::BaseMalfunctionChance, + m_noMalfunctionOverride.baseChance); + } + + if (IsUsableAddress(m_noMalfunctionOverride.malfunctionState)) + { + m.TryWrite(m_noMalfunctionOverride.malfunctionState + offset::WeaponMalfunctionState::State, + m_noMalfunctionOverride.state); + } + } + + m_noMalfunctionOverride = {}; +} + void CFramework::ResetStaminaOverride() { + if (m_staminaOverride.captured) { if (IsUsableAddress(m_staminaOverride.stamina)) @@ -404,4 +596,6 @@ void CFramework::ResetFeatureOverrides() ResetStaminaOverride(); ResetFallDamageOverride(); ResetSwayOverride(); + ResetNoRecoilOverride(); + ResetNoMalfunctionOverride(); } diff --git a/CFramework/Cheat/Framework.h b/CFramework/Cheat/Framework.h index f12931c..cb43edb 100644 --- a/CFramework/Cheat/Framework.h +++ b/CFramework/Cheat/Framework.h @@ -53,6 +53,40 @@ private: bool captured{ false }; }; + struct NoRecoilOverrideState + { + uintptr_t player{ 0 }; + uintptr_t weaponAnimation{ 0 }; + uintptr_t shotEffector{ 0 }; + uintptr_t weapon{ 0 }; + uintptr_t weaponTemplate{ 0 }; + uintptr_t recoilSettings{ 0 }; + bool enable{ false }; + bool captured{ false }; + }; + + struct NoMalfunctionOverrideState + { + uintptr_t player{ 0 }; + uintptr_t weapon{ 0 }; + uintptr_t weaponTemplate{ 0 }; + uintptr_t malfunctionState{ 0 }; + bool allowJam{ false }; + bool allowFeed{ false }; + bool allowMisfire{ false }; + bool allowSlide{ false }; + float baseChance{ 0.f }; + int state{ 0 }; + bool captured{ false }; + }; + + bool ResolveCurrentWeapon(uintptr_t& weaponAnimation, uintptr_t& shotEffector, + uintptr_t& weapon, uintptr_t& weaponTemplate); + void UpdateNoRecoilOverride(); + void UpdateNoMalfunctionOverride(); + void ResetNoRecoilOverride(); + void ResetNoMalfunctionOverride(); + void UpdateStaminaOverride(); void UpdateFallDamageOverride(); void UpdateSwayOverride(); @@ -66,6 +100,8 @@ private: StaminaOverrideState m_staminaOverride{}; FallDamageOverrideState m_fallDamageOverride{}; SwayOverrideState m_swayOverride{}; + NoRecoilOverrideState m_noRecoilOverride{}; + NoMalfunctionOverrideState m_noMalfunctionOverride{}; std::mutex m_mtxEntityList; diff --git a/CFramework/Cheat/Menu.cpp b/CFramework/Cheat/Menu.cpp index 5587ef9..a188896 100644 --- a/CFramework/Cheat/Menu.cpp +++ b/CFramework/Cheat/Menu.cpp @@ -123,11 +123,13 @@ void CFramework::RenderMenu() ImGui::Checkbox("无限耐力", &g.g_InfStamina); ImGui::Checkbox("无跌落伤害", &g.g_NoFallDamage); ImGui::Checkbox("无摇摆", &g.g_NoSway); + ImGui::Checkbox("无后座(内存)", &g.g_NoRecoil); + ImGui::Checkbox("无故障/卡弹", &g.g_NoMalfunction); ImGui::Spacing(); ImGui::Separator(); - ImGui::TextDisabled("仅写入 dump 已确认的对象字段"); - ImGui::TextDisabled("后座 / 故障:等待下一轮类布局确认"); + ImGui::TextDisabled("只写入 dump 已确认的对象字段"); + ImGui::TextDisabled("弹道追踪:暂不写入,等待 runtime 验证"); ImGui::EndChild(); ImGui::BeginChild("##C021", ImVec2(ImGui::GetContentRegionAvail()), true); diff --git a/CFramework/Cheat/SDK/CPlayer/CPlayer.cpp b/CFramework/Cheat/SDK/CPlayer/CPlayer.cpp index b13ee09..d8b7a5e 100644 --- a/CFramework/Cheat/SDK/CPlayer/CPlayer.cpp +++ b/CFramework/Cheat/SDK/CPlayer/CPlayer.cpp @@ -131,6 +131,20 @@ void CPlayer::UpdateHealth() void CPlayer::UpdateBone() { + // 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 = GetBonePosition(BONE_HEAD); m_vecNeckOrigin = GetBonePosition(BONE_NECK); m_vecBoneList.clear(); diff --git a/CFramework/Cheat/SDK/GameSDK/Offset.h b/CFramework/Cheat/SDK/GameSDK/Offset.h index a8219f6..7e3054e 100644 --- a/CFramework/Cheat/SDK/GameSDK/Offset.h +++ b/CFramework/Cheat/SDK/GameSDK/Offset.h @@ -103,6 +103,7 @@ namespace offset { constexpr auto Breath = 0x30; constexpr auto MotionReact = 0x40; // MotionEffector + constexpr auto ShotEffector = 0x50; // ShotEffector / Shootingg constexpr auto isAiming = 0x1C5; } @@ -112,6 +113,41 @@ namespace offset constexpr auto SwayFactors = 0x94; // UnityEngine.Vector3 } + // -.ShotEffector + namespace ShotEffector + { + constexpr auto MainWeaponInHands = 0x38; // EFT.InventoryLogic.Weapon + } + + // EFT.InventoryLogic.Weapon + namespace Weapon + { + constexpr auto Template = 0x40; // CompoundItem.Template + constexpr auto MalfState = 0xF0; // WeaponMalfunctionStateClass + } + + // EFT.InventoryLogic.WeaponTemplate + namespace WeaponTemplate + { + constexpr auto WeaponRecoilSettings = 0x180; // WeaponRecoil + constexpr auto AllowJam = 0x2F0; + constexpr auto AllowFeed = 0x2F1; + constexpr auto AllowMisfire = 0x2F2; + constexpr auto AllowSlide = 0x2F3; + constexpr auto BaseMalfunctionChance = 0x2F4; + } + + // -.WeaponRecoil / -.Weapon.WeaponMalfunctionStateClass + namespace WeaponRecoil + { + constexpr auto Enable = 0x18; + } + + namespace WeaponMalfunctionState + { + constexpr auto State = 0x6C; + } + // -.GClass774 (stamina/oxygen state) namespace PhysicalState { diff --git a/CFramework/Framework/Config/Config.h b/CFramework/Framework/Config/Config.h index cbbd7f8..3ef6188 100644 --- a/CFramework/Framework/Config/Config.h +++ b/CFramework/Framework/Config/Config.h @@ -30,9 +30,11 @@ struct Globals float g_ESP_MaxItemDistance = 50.f; // Misc (仅使用 dump 已确认的对象字段) - bool g_InfStamina = false; - bool g_NoFallDamage = false; - bool g_NoSway = false; + bool g_InfStamina = false; + bool g_NoFallDamage = false; + bool g_NoSway = false; + bool g_NoRecoil = false; + bool g_NoMalfunction = false; // Aim (自瞄)