diff --git a/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Features/CMisc/CMisc.cpp b/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Features/CMisc/CMisc.cpp index 5639058..6305d52 100644 --- a/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Features/CMisc/CMisc.cpp +++ b/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Features/CMisc/CMisc.cpp @@ -1,7 +1,9 @@ #include "CMisc.hpp" #include +#include #include +#include #include #include @@ -30,6 +32,298 @@ #include +namespace +{ + // Deterministic Valve RNG (matches the game's math::random_seed generator). + class CValveRng + { + public: + auto Seed( int s ) -> void + { + m_State = -std::abs( s ); + m_Index = 0; + m_bSeeded = false; + } + + auto Generate() -> int + { + if ( !m_bSeeded ) + { + auto v = -m_State; + + if ( v < 1 ) + v = 1; + + for ( int j = 39; j >= 0; --j ) + { + v = LCG( v ); + + if ( j < 32 ) + m_Table[ j ] = v; + } + + m_State = v; + m_Index = m_Table[ 0 ]; + m_bSeeded = true; + } + + m_State = LCG( m_State ); + + const auto Index = m_Index / 0x4000000; + m_Index = m_Table[ Index ]; + m_Table[ Index ] = m_State; + + return m_Index; + } + + auto RandomFloat( float Min = 0.f , float Max = 1.f ) -> float + { + const auto Raw = Generate(); + const auto Norm = std::fminf( 0.99999988f , static_cast( Raw ) * 4.6566129e-10f ); + + return Min + Norm * ( Max - Min ); + } + + private: + static auto LCG( int State ) -> int + { + const auto k = State / 127773; + auto Result = 16807 * ( State - k * 127773 ) - 2836 * k; + + if ( Result < 0 ) + Result += 2147483647; + + return Result; + } + + int m_State = 0; + int m_Index = 0; + int m_Table[ 32 ]{}; + bool m_bSeeded = false; + }; + + // Minimal SHA-1 (for computing the spread seed from angles + tick). + class CSha1 + { + public: + auto Reset() -> void + { + m_State[ 0 ] = 0x67452301; + m_State[ 1 ] = 0xEFCDAB89; + m_State[ 2 ] = 0x98BADCFE; + m_State[ 3 ] = 0x10325476; + m_State[ 4 ] = 0xC3D2E1F0; + m_Count = 0; + } + + auto Update( const void* pData , std::size_t Len ) -> void + { + const auto* pBytes = static_cast( pData ); + auto Index = static_cast( m_Count & 63 ); + m_Count += Len; + + std::size_t i = 0; + + if ( Index ) + { + auto PartLen = 64 - Index; + + if ( Len >= PartLen ) + { + std::memcpy( m_Buffer + Index , pBytes , PartLen ); + Transform( m_Buffer ); + i = PartLen; + } + else + { + std::memcpy( m_Buffer + Index , pBytes , Len ); + return; + } + } + + for ( ; i + 64 <= Len; i += 64 ) + Transform( pBytes + i ); + + if ( i < Len ) + std::memcpy( m_Buffer , pBytes + i , Len - i ); + } + + auto Final() -> void + { + uint8_t Padding[ 64 ]{}; + Padding[ 0 ] = 0x80; + + const auto Index = static_cast( m_Count & 63 ); + const auto PadLen = ( Index < 56 ) ? ( 56 - Index ) : ( 120 - Index ); + const auto BitCount = m_Count * 8; + + Update( Padding , PadLen ); + + uint8_t Bits[ 8 ]{}; + + for ( int i = 0; i < 8; ++i ) + Bits[ 7 - i ] = static_cast( BitCount >> ( i * 8 ) ); + + Update( Bits , 8 ); + + for ( int i = 0; i < 5; ++i ) + { + m_Digest[ i * 4 + 0 ] = static_cast( m_State[ i ] >> 24 ); + m_Digest[ i * 4 + 1 ] = static_cast( m_State[ i ] >> 16 ); + m_Digest[ i * 4 + 2 ] = static_cast( m_State[ i ] >> 8 ); + m_Digest[ i * 4 + 3 ] = static_cast( m_State[ i ] ); + } + } + + auto GetFirstUInt32() const -> uint32_t + { + uint32_t Result; + std::memcpy( &Result , m_Digest , sizeof Result ); + return Result; + } + + private: + static auto RotL( uint32_t v , int n ) -> uint32_t + { + return ( v << n ) | ( v >> ( 32 - n ) ); + } + + auto Transform( const uint8_t* pBlock ) -> void + { + uint32_t w[ 80 ]{}; + + for ( int i = 0; i < 16; ++i ) + w[ i ] = static_cast( pBlock[ i * 4 ] ) << 24 | static_cast( pBlock[ i * 4 + 1 ] ) << 16 | static_cast( pBlock[ i * 4 + 2 ] ) << 8 | static_cast( pBlock[ i * 4 + 3 ] ); + + for ( int i = 16; i < 80; ++i ) + w[ i ] = RotL( w[ i - 3 ] ^ w[ i - 8 ] ^ w[ i - 14 ] ^ w[ i - 16 ] , 1 ); + + auto a = m_State[ 0 ]; + auto b = m_State[ 1 ]; + auto c = m_State[ 2 ]; + auto d = m_State[ 3 ]; + auto e = m_State[ 4 ]; + + for ( int i = 0; i < 80; ++i ) + { + uint32_t f , k; + + if ( i < 20 ) + { + f = ( b & c ) | ( ( ~b ) & d ); + k = 0x5A827999; + } + else if ( i < 40 ) + { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if ( i < 60 ) + { + f = ( b & c ) | ( b & d ) | ( c & d ); + k = 0x8F1BBCDC; + } + else + { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + const auto Temp = RotL( a , 5 ) + f + e + k + w[ i ]; + e = d; + d = c; + c = RotL( b , 30 ); + b = a; + a = Temp; + } + + m_State[ 0 ] += a; + m_State[ 1 ] += b; + m_State[ 2 ] += c; + m_State[ 3 ] += d; + m_State[ 4 ] += e; + } + + uint32_t m_State[ 5 ]{}; + std::uint64_t m_Count = 0; + uint8_t m_Buffer[ 64 ]{}; + uint8_t m_Digest[ 20 ]{}; + }; + + auto NormalizeAngle( float Angle ) -> float + { + return Angle - std::floorf( Angle * 0.0027777778f + 0.5f ) * 360.0f; + } + + auto QuantizeAngle( float Angle ) -> float + { + return std::floorf( NormalizeAngle( Angle ) * 2.0f ) * 0.5f; + } + + auto GetSpreadSeed( const QAngle& ViewAngles , int Tick ) -> uint32_t + { + struct + { + float Pitch; + float Yaw; + int PlayerRenderTick; + } Buffer{}; + + Buffer.Pitch = QuantizeAngle( ViewAngles.m_x ); + Buffer.Yaw = QuantizeAngle( ViewAngles.m_y ); + Buffer.PlayerRenderTick = Tick; + + CSha1 Hash; + Hash.Reset(); + Hash.Update( &Buffer , 12 ); + Hash.Final(); + + return Hash.GetFirstUInt32(); + } + + struct SeedSpread2_t + { + float x = 0.f; + float y = 0.f; + }; + + auto CalculateSeedSpread( int Seed , float Inaccuracy , float Spread , int ItemDefIdx ) -> SeedSpread2_t + { + constexpr auto k_RevolverId = 64; + constexpr auto k_NegevId = 28; + constexpr auto k_TwoPi = 6.2831853f; + + CValveRng Rng; + Rng.Seed( Seed ); + + auto InacR = Rng.RandomFloat( 0.f , 1.f ); + const auto InacA = Rng.RandomFloat( 0.f , k_TwoPi ); + + if ( ItemDefIdx == k_RevolverId ) + InacR = 1.f - ( InacR * InacR ); + else if ( ItemDefIdx == k_NegevId ) + InacR = 1.f - ( InacR * InacR ); + + InacR *= Inaccuracy; + + auto SprR = Rng.RandomFloat( 0.f , 1.f ); + const auto SprA = Rng.RandomFloat( 0.f , k_TwoPi ); + + if ( ItemDefIdx == k_RevolverId ) + SprR = 1.f - ( SprR * SprR ); + else if ( ItemDefIdx == k_NegevId ) + SprR = 1.f - ( SprR * SprR ); + + SprR *= Spread; + + return + { + std::cosf( SprA ) * SprR + std::cosf( InacA ) * InacR, + std::sinf( SprA ) * SprR + std::sinf( InacA ) * InacR + }; + } +} + static CMisc g_CMisc{}; auto CMisc::OnRender() -> void @@ -278,7 +572,19 @@ auto CMisc::RunTriggerbot( CCSGOInput* pInput , CUserCmd* pUserCmd ) -> void return; } - const auto Result = GetCL_Trace()->TraceToHitGroup( pInput ); + TriggerTraceResult_t Result; + + if ( Settings::Triggerbot::SeedTrigger ) + { + Vector3 vDirection; + + if ( CalculateSeedDirection( pInput , pUserCmd , &vDirection ) ) + Result = GetCL_Trace()->TraceDirectionToHitGroup( &vDirection ); + } + else + { + Result = GetCL_Trace()->TraceToHitGroup( pInput ); + } auto* pTargetPawn = reinterpret_cast( Result.pHitEntity ); @@ -300,7 +606,7 @@ auto CMisc::RunTriggerbot( CCSGOInput* pInput , CUserCmd* pUserCmd ) -> void default: bTargetValid = true; break; } - if ( bTargetValid && Settings::Triggerbot::Hitchance > 0 ) + if ( bTargetValid && !Settings::Triggerbot::SeedTrigger && Settings::Triggerbot::Hitchance > 0 ) { const QAngle ViewAngles = *CCSGOInput_GetViewAngles( pInput , 0 ); @@ -410,6 +716,50 @@ auto CMisc::CalculateHitchance( CCSGOInput* pInput , C_CSPlayerPawn* pTarget , c return static_cast( Hits ) / static_cast( Samples ); } +auto CMisc::CalculateSeedDirection( CCSGOInput* pInput , CUserCmd* pUserCmd , Vector3* vOutDirection ) -> bool +{ + if ( !pInput || !vOutDirection ) + return false; + + auto* pWeapon = GetCL_Weapons()->GetLocalActiveWeapon(); + + if ( !pWeapon ) + return false; + + const QAngle ViewAngles = *CCSGOInput_GetViewAngles( pInput , 0 ); + + int iSeed = 0; + + if ( pUserCmd && pUserCmd->cmd.base().has_random_seed() ) + { + iSeed = pUserCmd->cmd.base().random_seed(); + } + else + { + int iTick = 0; + + if ( pUserCmd && pUserCmd->cmd.base().has_client_tick() ) + iTick = pUserCmd->cmd.base().client_tick(); + + iSeed = static_cast( GetSpreadSeed( ViewAngles , iTick ) ); + } + + const float flInaccuracy = C_CSWeaponBaseGun_GetInaccuracy( pWeapon , nullptr , nullptr ); + const float flSpread = C_CSWeaponBaseGun_GetSpread( pWeapon ); + + const auto ItemDefIdx = GetCL_Weapons()->GetLocalWeaponDefinitionIndex(); + + const auto sv = CalculateSeedSpread( iSeed + 1 , flInaccuracy , flSpread , ItemDefIdx ); + + Vector3 vForward , vRight , vUp; + + Math::AngleVectors( ViewAngles , vForward , vRight , vUp ); + + *vOutDirection = ( vForward + vRight * -sv.x + vUp * sv.y ).Normalized(); + + return true; +} + auto CMisc::OnFrameStageNotify( int FrameStage ) -> void { if ( FrameStage != 6 ) diff --git a/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Features/CMisc/CMisc.hpp b/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Features/CMisc/CMisc.hpp index 9ca816e..9077943 100644 --- a/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Features/CMisc/CMisc.hpp +++ b/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Features/CMisc/CMisc.hpp @@ -35,6 +35,7 @@ public: private: auto RunTriggerbot( CCSGOInput* pInput , CUserCmd* pUserCmd ) -> void; auto CalculateHitchance( CCSGOInput* pInput , C_CSPlayerPawn* pTarget , const QAngle& ViewAngles , const int Samples ) -> float; + auto CalculateSeedDirection( CCSGOInput* pInput , CUserCmd* pUserCmd , Vector3* vOutDirection ) -> bool; private: std::chrono::steady_clock::time_point m_HitTime{}; diff --git a/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/GUI/CAndromedaMenu.cpp b/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/GUI/CAndromedaMenu.cpp index fbb400c..b76d836 100644 --- a/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/GUI/CAndromedaMenu.cpp +++ b/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/GUI/CAndromedaMenu.cpp @@ -393,10 +393,15 @@ auto CAndromedaMenu::RenderMiscPanel() -> void ImGui::Separator(); - SliderInt( "Hitchance (%)" , Settings::Triggerbot::Hitchance , 0 , 100 , Settings::Triggerbot::Hitchance > 0 ? "%d%%" : "%d (aus)" ); + Checkbox( "Seed-Modus (deterministisch)" , Settings::Triggerbot::SeedTrigger ); - if ( Settings::Triggerbot::Hitchance > 0 ) - SliderInt( "Hitchance Samples" , Settings::Triggerbot::HitchanceSamples , 16 , 128 ); + if ( !Settings::Triggerbot::SeedTrigger ) + { + SliderInt( "Hitchance (%)" , Settings::Triggerbot::Hitchance , 0 , 100 , Settings::Triggerbot::Hitchance > 0 ? "%d%%" : "%d (aus)" ); + + if ( Settings::Triggerbot::Hitchance > 0 ) + SliderInt( "Hitchance Samples" , Settings::Triggerbot::HitchanceSamples , 16 , 128 ); + } ImGui::Separator(); diff --git a/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Settings/Settings.hpp b/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Settings/Settings.hpp index d1e8051..c38da72 100644 --- a/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Settings/Settings.hpp +++ b/Andromeda-CS2-Base/Andromeda-CS2-Base/AndromedaClient/Settings/Settings.hpp @@ -69,6 +69,7 @@ namespace Settings inline auto DrawIndicator = true; inline auto Hitchance = 0; // percent, 0 = off inline auto HitchanceSamples = 64; + inline auto SeedTrigger = false; // deterministic: only fire if the real bullet (current random_seed) would hit } namespace Colors { diff --git a/Andromeda-CS2-Base/Andromeda-CS2-Base/GameClient/CL_Trace.cpp b/Andromeda-CS2-Base/Andromeda-CS2-Base/GameClient/CL_Trace.cpp index 9981a7c..7d2f473 100644 --- a/Andromeda-CS2-Base/Andromeda-CS2-Base/GameClient/CL_Trace.cpp +++ b/Andromeda-CS2-Base/Andromeda-CS2-Base/GameClient/CL_Trace.cpp @@ -120,6 +120,35 @@ auto CL_Trace::TraceToHitGroup( CCSGOInput* pInput ) -> TriggerTraceResult_t return Result; } +auto CL_Trace::TraceDirectionToHitGroup( const Vector3* vDirection ) -> TriggerTraceResult_t +{ + TriggerTraceResult_t Result; + + auto pLocalPlayerPawn = GetCL_Players()->GetLocalPlayerPawn(); + + if ( !pLocalPlayerPawn || !vDirection ) + return Result; + + Ray_t Ray; + CGameTrace GameTrace; + CTraceFilter Filter( 0x1C1003 , pLocalPlayerPawn , 3 , 15 ); + + const auto vStart = GetCL_Players()->GetLocalEyeOrigin(); + + const auto vEnd = vStart + ( *vDirection * 8192.f ); + + if ( IGamePhysicsQuery_TraceShape( SDK::Pointers::CVPhys2World() , Ray , vStart , vEnd , &Filter , &GameTrace ) ) + { + if ( GameTrace.pHitEntity && GameTrace.pHitBox ) + { + Result.pHitEntity = GameTrace.pHitEntity; + Result.nHitGroup = GameTrace.pHitBox->nGroupId; + } + } + + return Result; +} + auto GetCL_Trace() -> CL_Trace* { return &g_CL_Trace; diff --git a/Andromeda-CS2-Base/Andromeda-CS2-Base/GameClient/CL_Trace.hpp b/Andromeda-CS2-Base/Andromeda-CS2-Base/GameClient/CL_Trace.hpp index b6fb02b..bdd571c 100644 --- a/Andromeda-CS2-Base/Andromeda-CS2-Base/GameClient/CL_Trace.hpp +++ b/Andromeda-CS2-Base/Andromeda-CS2-Base/GameClient/CL_Trace.hpp @@ -23,6 +23,7 @@ public: auto TraceToBoneEntity( CCSGOInput* pInput , const QAngle* AngleCorrection , QAngle* ViewAngleCorrection ) -> std::pair; auto TraceToEntityEndPos( const Vector3* vEnd ) -> C_BaseEntity*; auto TraceToHitGroup( CCSGOInput* pInput ) -> TriggerTraceResult_t; + auto TraceDirectionToHitGroup( const Vector3* vDirection ) -> TriggerTraceResult_t; }; auto GetCL_Trace() -> CL_Trace*;